Prevent field name conflicts (#13280)
Fixes https://github.com/twentyhq/twenty/issues/13184
This commit is contained in:
@ -17,6 +17,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { View } from '@/views/types/View';
|
import { View } from '@/views/types/View';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@ -149,14 +150,8 @@ export const SettingsObjectNewFieldConfigure = () => {
|
|||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
const isDuplicateFieldNameInObject = (error as Error).message.includes(
|
|
||||||
'duplicate key value violates unique constraint "IndexOnNameObjectMetadataIdAndWorkspaceIdUnique"',
|
|
||||||
);
|
|
||||||
|
|
||||||
enqueueErrorSnackBar({
|
enqueueErrorSnackBar({
|
||||||
message: isDuplicateFieldNameInObject
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
? t`Please use different names for your source and destination fields`
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export class FieldMetadataMorphRelationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const relationFieldMetadataForCreate =
|
const relationFieldMetadataForCreate =
|
||||||
await this.fieldMetadataRelationService.addCustomRelationFieldMetadataForCreation(
|
this.fieldMetadataRelationService.computeCustomRelationFieldMetadataForCreation(
|
||||||
{
|
{
|
||||||
fieldMetadataInput: fieldMetadataForCreate,
|
fieldMetadataInput: fieldMetadataForCreate,
|
||||||
relationCreationPayload: relation,
|
relationCreationPayload: relation,
|
||||||
@ -94,6 +94,7 @@ export class FieldMetadataMorphRelationService {
|
|||||||
fieldMetadataInput: relationFieldMetadataForCreate,
|
fieldMetadataInput: relationFieldMetadataForCreate,
|
||||||
fieldMetadataType: relationFieldMetadataForCreate.type,
|
fieldMetadataType: relationFieldMetadataForCreate.type,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
objectMetadata,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ export class FieldMetadataMorphRelationService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const targetFieldMetadataToCreateWithRelation =
|
const targetFieldMetadataToCreateWithRelation =
|
||||||
await this.fieldMetadataRelationService.addCustomRelationFieldMetadataForCreation(
|
this.fieldMetadataRelationService.computeCustomRelationFieldMetadataForCreation(
|
||||||
{
|
{
|
||||||
fieldMetadataInput: targetFieldMetadataToCreate,
|
fieldMetadataInput: targetFieldMetadataToCreate,
|
||||||
relationCreationPayload: {
|
relationCreationPayload: {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Injectable, ValidationError } from '@nestjs/common';
|
import { Injectable, ValidationError } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
import { IsEnum, IsString, IsUUID, validateOrReject } from 'class-validator';
|
import { IsEnum, IsString, IsUUID, validateOrReject } from 'class-validator';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
@ -17,6 +18,7 @@ import {
|
|||||||
FieldMetadataException,
|
FieldMetadataException,
|
||||||
FieldMetadataExceptionCode,
|
FieldMetadataExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
|
import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util';
|
||||||
import { prepareCustomFieldMetadataForCreation } from 'src/engine/metadata-modules/field-metadata/utils/prepare-field-metadata-for-creation.util';
|
import { prepareCustomFieldMetadataForCreation } from 'src/engine/metadata-modules/field-metadata/utils/prepare-field-metadata-for-creation.util';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
|
||||||
@ -28,7 +30,6 @@ import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/v
|
|||||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||||
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util';
|
|
||||||
|
|
||||||
export class RelationCreationPayloadValidation {
|
export class RelationCreationPayloadValidation {
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
@ -93,7 +94,7 @@ export class FieldMetadataRelationService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const targetFieldMetadataToCreateWithRelation =
|
const targetFieldMetadataToCreateWithRelation =
|
||||||
await this.addCustomRelationFieldMetadataForCreation({
|
this.computeCustomRelationFieldMetadataForCreation({
|
||||||
fieldMetadataInput: targetFieldMetadataToCreate,
|
fieldMetadataInput: targetFieldMetadataToCreate,
|
||||||
relationCreationPayload: {
|
relationCreationPayload: {
|
||||||
targetObjectMetadataId: objectMetadata.id,
|
targetObjectMetadataId: objectMetadata.id,
|
||||||
@ -134,9 +135,13 @@ export class FieldMetadataRelationService {
|
|||||||
fieldMetadataInput,
|
fieldMetadataInput,
|
||||||
fieldMetadataType,
|
fieldMetadataType,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
objectMetadata,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
ValidateFieldMetadataArgs<T>,
|
ValidateFieldMetadataArgs<T>,
|
||||||
'fieldMetadataInput' | 'fieldMetadataType' | 'objectMetadataMaps'
|
| 'fieldMetadataInput'
|
||||||
|
| 'fieldMetadataType'
|
||||||
|
| 'objectMetadataMaps'
|
||||||
|
| 'objectMetadata'
|
||||||
>): Promise<T> {
|
>): Promise<T> {
|
||||||
// TODO: clean typings, we should try to validate both update and create inputs in the same function
|
// TODO: clean typings, we should try to validate both update and create inputs in the same function
|
||||||
const isRelation =
|
const isRelation =
|
||||||
@ -150,6 +155,11 @@ export class FieldMetadataRelationService {
|
|||||||
.relationCreationPayload,
|
.relationCreationPayload,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
validateFieldNameAvailabilityOrThrow(
|
||||||
|
`${fieldMetadataInput.name}Id`,
|
||||||
|
objectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
const relationCreationPayload = (
|
const relationCreationPayload = (
|
||||||
fieldMetadataInput as unknown as CreateFieldInput
|
fieldMetadataInput as unknown as CreateFieldInput
|
||||||
).relationCreationPayload;
|
).relationCreationPayload;
|
||||||
@ -180,6 +190,24 @@ export class FieldMetadataRelationService {
|
|||||||
computedMetadataNameFromLabel,
|
computedMetadataNameFromLabel,
|
||||||
objectMetadataTarget,
|
objectMetadataTarget,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
validateFieldNameAvailabilityOrThrow(
|
||||||
|
`${computedMetadataNameFromLabel}Id`,
|
||||||
|
objectMetadataTarget,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
computedMetadataNameFromLabel === fieldMetadataInput.name &&
|
||||||
|
objectMetadata.id === objectMetadataTarget.id
|
||||||
|
) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
`Name "${computedMetadataNameFromLabel}" cannot be the same on both side of the relation`,
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Name "${computedMetadataNameFromLabel}" cannot be the same on both side of the relation`,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +313,7 @@ export class FieldMetadataRelationService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addCustomRelationFieldMetadataForCreation({
|
computeCustomRelationFieldMetadataForCreation({
|
||||||
fieldMetadataInput,
|
fieldMetadataInput,
|
||||||
relationCreationPayload,
|
relationCreationPayload,
|
||||||
joinColumnName,
|
joinColumnName,
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import {
|
|||||||
computeColumnName,
|
computeColumnName,
|
||||||
computeCompositeColumnName,
|
computeCompositeColumnName,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
|
import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util';
|
||||||
import { createMigrationActions } from 'src/engine/metadata-modules/field-metadata/utils/create-migration-actions.util';
|
import { createMigrationActions } from 'src/engine/metadata-modules/field-metadata/utils/create-migration-actions.util';
|
||||||
import { generateRatingOptions } from 'src/engine/metadata-modules/field-metadata/utils/generate-rating-optionts.util';
|
import { generateRatingOptions } from 'src/engine/metadata-modules/field-metadata/utils/generate-rating-optionts.util';
|
||||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||||
@ -57,7 +58,6 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.
|
|||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||||
import { ViewService } from 'src/modules/view/services/view.service';
|
import { ViewService } from 'src/modules/view/services/view.service';
|
||||||
import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntity> {
|
export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntity> {
|
||||||
@ -671,7 +671,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
|
|
||||||
if (fieldMetadataInput.type === FieldMetadataType.RELATION) {
|
if (fieldMetadataInput.type === FieldMetadataType.RELATION) {
|
||||||
const relationFieldMetadataForCreate =
|
const relationFieldMetadataForCreate =
|
||||||
await this.fieldMetadataRelationService.addCustomRelationFieldMetadataForCreation(
|
this.fieldMetadataRelationService.computeCustomRelationFieldMetadataForCreation(
|
||||||
{
|
{
|
||||||
fieldMetadataInput: fieldMetadataForCreate,
|
fieldMetadataInput: fieldMetadataForCreate,
|
||||||
relationCreationPayload: fieldMetadataInput.relationCreationPayload,
|
relationCreationPayload: fieldMetadataInput.relationCreationPayload,
|
||||||
@ -686,6 +686,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
fieldMetadataInput: relationFieldMetadataForCreate,
|
fieldMetadataInput: relationFieldMetadataForCreate,
|
||||||
fieldMetadataType: fieldMetadataForCreate.type,
|
fieldMetadataType: fieldMetadataForCreate.type,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
objectMetadata,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exce
|
|||||||
|
|
||||||
export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||||
if (error instanceof InvalidMetadataException) {
|
if (error instanceof InvalidMetadataException) {
|
||||||
throw new UserInputError(error.message);
|
throw new UserInputError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof FieldMetadataException) {
|
if (error instanceof FieldMetadataException) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
@ -39,14 +40,17 @@ export const validateFieldNameAvailabilityOrThrow = (
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
Object.values(objectMetadata.fieldsById).some(
|
Object.values(objectMetadata.fieldsById).some(
|
||||||
(field) => field.name === name,
|
(field) =>
|
||||||
|
field.name === name ||
|
||||||
|
(field.type === FieldMetadataType.RELATION &&
|
||||||
|
`${field.name}Id` === name),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new InvalidMetadataException(
|
throw new InvalidMetadataException(
|
||||||
`Name "${name}" is not available`,
|
`Name "${name}" is not available as it is already used by another field`,
|
||||||
InvalidMetadataExceptionCode.NOT_AVAILABLE,
|
InvalidMetadataExceptionCode.NOT_AVAILABLE,
|
||||||
{
|
{
|
||||||
userFriendlyMessage: t`This name is not available.`,
|
userFriendlyMessage: t`This name is not available as it is already used by another field`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,10 @@ exports[`Field metadata morph relation creation should fail relation MANY_TO_ONE
|
|||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"subCode": "Name not available",
|
||||||
|
"userFriendlyMessage": "This name is not available as it is already used by another field",
|
||||||
},
|
},
|
||||||
"message": "Name "collisionfieldlabel" is not available",
|
"message": "Name "collisionfieldlabel" is not available as it is already used by another field",
|
||||||
"name": "UserInputError",
|
"name": "UserInputError",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -18,6 +19,7 @@ exports[`Field metadata morph relation creation should fail relation MANY_TO_ONE
|
|||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Invalid label",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Invalid label: " "",
|
"message": "Invalid label: " "",
|
||||||
@ -31,6 +33,7 @@ exports[`Field metadata morph relation creation should fail relation MANY_TO_ONE
|
|||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Exceeds max length",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Name is too long: it exceeds the 63 characters limit.",
|
"message": "Name is too long: it exceeds the 63 characters limit.",
|
||||||
@ -44,6 +47,7 @@ exports[`Field metadata morph relation creation should fail relation MANY_TO_ONE
|
|||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Input too short",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Input is too short: """,
|
"message": "Input is too short: """,
|
||||||
@ -98,9 +102,10 @@ exports[`Field metadata morph relation creation should fail relation ONE_TO_MANY
|
|||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"subCode": "Name not available",
|
||||||
|
"userFriendlyMessage": "This name is not available as it is already used by another field",
|
||||||
},
|
},
|
||||||
"message": "Name "collisionfieldlabel" is not available",
|
"message": "Name "collisionfieldlabel" is not available as it is already used by another field",
|
||||||
"name": "UserInputError",
|
"name": "UserInputError",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -111,6 +116,7 @@ exports[`Field metadata morph relation creation should fail relation ONE_TO_MANY
|
|||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Invalid label",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Invalid label: " "",
|
"message": "Invalid label: " "",
|
||||||
@ -124,6 +130,7 @@ exports[`Field metadata morph relation creation should fail relation ONE_TO_MANY
|
|||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Exceeds max length",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Name is too long: it exceeds the 63 characters limit.",
|
"message": "Name is too long: it exceeds the 63 characters limit.",
|
||||||
@ -137,6 +144,7 @@ exports[`Field metadata morph relation creation should fail relation ONE_TO_MANY
|
|||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Input too short",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Input is too short: """,
|
"message": "Input is too short: """,
|
||||||
|
|||||||
@ -1,23 +1,39 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when targetFieldLabel conflicts with an existing field on target object metadata id 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when targetFieldLabel conflicts with an existing {name}Id on target object metadata id 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"subCode": "Name not available",
|
||||||
|
"userFriendlyMessage": "This name is not available as it is already used by another field",
|
||||||
},
|
},
|
||||||
"message": "Name "collisionfieldlabel" is not available",
|
"message": "Name "fieldNameBisId" is not available as it is already used by another field",
|
||||||
"name": "UserInputError",
|
"name": "UserInputError",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when targetFieldLabel contains only whitespace 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when targetFieldLabel conflicts with an existing field on target object metadata id 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Name not available",
|
||||||
|
"userFriendlyMessage": "This name is not available as it is already used by another field",
|
||||||
|
},
|
||||||
|
"message": "Name "fieldName" is not available as it is already used by another field",
|
||||||
|
"name": "UserInputError",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when targetFieldLabel contains only whitespace 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Invalid label",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Invalid label: " "",
|
"message": "Invalid label: " "",
|
||||||
@ -26,11 +42,12 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when targetFieldLabel exceeds maximum length 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when targetFieldLabel exceeds maximum length 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Exceeds max length",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Name is too long: it exceeds the 63 characters limit.",
|
"message": "Name is too long: it exceeds the 63 characters limit.",
|
||||||
@ -39,11 +56,12 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when targetFieldLabel is empty 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when targetFieldLabel is empty 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Input too short",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Input is too short: """,
|
"message": "Input is too short: """,
|
||||||
@ -52,7 +70,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when targetObjectMetadataId is unknown 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when targetObjectMetadataId is unknown 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@ -65,7 +83,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when type is a wrong value 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when type is a wrong value 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@ -79,7 +97,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when type is not provided 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when type is not provided 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@ -93,24 +111,68 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when targetFieldLabel conflicts with an existing field on target object metadata id 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when {name}Id is already used 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"subCode": "INVALID_FIELD_INPUT",
|
||||||
|
"userFriendlyMessage": "Name is not available, it may be duplicating another field's name.",
|
||||||
},
|
},
|
||||||
"message": "Name "collisionfieldlabel" is not available",
|
"message": "Name "fieldNameBisId" is not available, check that it is not duplicating another field's name.",
|
||||||
"name": "UserInputError",
|
"name": "UserInputError",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when targetFieldLabel contains only whitespace 1`] = `
|
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when target and source are the same object and name are the same 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "INVALID_FIELD_INPUT",
|
||||||
|
"userFriendlyMessage": "An error occurred.",
|
||||||
|
},
|
||||||
|
"message": "Relation creation payload is invalid: targetObjectMetadataId must be a UUID",
|
||||||
|
"name": "UserInputError",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when targetFieldLabel conflicts with an existing {name}Id on target object metadata id 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Name not available",
|
||||||
|
"userFriendlyMessage": "This name is not available as it is already used by another field",
|
||||||
|
},
|
||||||
|
"message": "Name "fieldNameBisId" is not available as it is already used by another field",
|
||||||
|
"name": "UserInputError",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when targetFieldLabel conflicts with an existing field on target object metadata id 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Name not available",
|
||||||
|
"userFriendlyMessage": "This name is not available as it is already used by another field",
|
||||||
|
},
|
||||||
|
"message": "Name "fieldName" is not available as it is already used by another field",
|
||||||
|
"name": "UserInputError",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when targetFieldLabel contains only whitespace 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Invalid label",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Invalid label: " "",
|
"message": "Invalid label: " "",
|
||||||
@ -119,11 +181,12 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when targetFieldLabel exceeds maximum length 1`] = `
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when targetFieldLabel exceeds maximum length 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Exceeds max length",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Name is too long: it exceeds the 63 characters limit.",
|
"message": "Name is too long: it exceeds the 63 characters limit.",
|
||||||
@ -132,11 +195,12 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when targetFieldLabel is empty 1`] = `
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when targetFieldLabel is empty 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"code": "BAD_USER_INPUT",
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "Input too short",
|
||||||
"userFriendlyMessage": "An error occurred.",
|
"userFriendlyMessage": "An error occurred.",
|
||||||
},
|
},
|
||||||
"message": "Input is too short: """,
|
"message": "Input is too short: """,
|
||||||
@ -145,7 +209,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when targetObjectMetadataId is unknown 1`] = `
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when targetObjectMetadataId is unknown 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@ -158,7 +222,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when type is a wrong value 1`] = `
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when type is a wrong value 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@ -172,7 +236,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when type is not provided 1`] = `
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when type is not provided 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@ -185,3 +249,31 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when {name}Id is already used 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "INVALID_FIELD_INPUT",
|
||||||
|
"userFriendlyMessage": "Name is not available, it may be duplicating another field's name.",
|
||||||
|
},
|
||||||
|
"message": "Name "fieldNameBisId" is not available, check that it is not duplicating another field's name.",
|
||||||
|
"name": "UserInputError",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when target and source are the same object and name are the same 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"code": "BAD_USER_INPUT",
|
||||||
|
"subCode": "INVALID_FIELD_INPUT",
|
||||||
|
"userFriendlyMessage": "An error occurred.",
|
||||||
|
},
|
||||||
|
"message": "Relation creation payload is invalid: targetObjectMetadataId must be a UUID",
|
||||||
|
"name": "UserInputError",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|||||||
@ -15,60 +15,138 @@ type GlobalTestContext = {
|
|||||||
targetObjectId: string;
|
targetObjectId: string;
|
||||||
sourceObjectId: string;
|
sourceObjectId: string;
|
||||||
};
|
};
|
||||||
|
collisionFieldName: string;
|
||||||
|
collisionFieldNameWithId: string;
|
||||||
collisionFieldLabel: string;
|
collisionFieldLabel: string;
|
||||||
|
collisionFieldLabelWithId: string;
|
||||||
};
|
};
|
||||||
const globalTestContext: GlobalTestContext = {
|
const globalTestContext: GlobalTestContext = {
|
||||||
objectMetadataIds: {
|
objectMetadataIds: {
|
||||||
targetObjectId: '',
|
targetObjectId: '',
|
||||||
sourceObjectId: '',
|
sourceObjectId: '',
|
||||||
},
|
},
|
||||||
collisionFieldLabel: 'collisionfieldlabel',
|
collisionFieldLabel: 'Field Name',
|
||||||
|
collisionFieldName: 'fieldName',
|
||||||
|
collisionFieldNameWithId: 'fieldNameBisId',
|
||||||
|
collisionFieldLabelWithId: 'Field Name Bis Id',
|
||||||
};
|
};
|
||||||
|
|
||||||
type TestedRelationCreationPayload = Partial<
|
type TestedRelationCreationPayload = Partial<
|
||||||
NonNullable<CreateFieldInput['relationCreationPayload']>
|
NonNullable<CreateFieldInput['relationCreationPayload']>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type TestedContext = {
|
||||||
|
input: {
|
||||||
|
name?: string;
|
||||||
|
relationCreationPayload?: TestedRelationCreationPayload;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type CreateOneObjectMetadataItemTestingContext = EachTestingContext<
|
type CreateOneObjectMetadataItemTestingContext = EachTestingContext<
|
||||||
| TestedRelationCreationPayload
|
TestedContext | ((context: GlobalTestContext) => TestedContext)
|
||||||
| ((context: GlobalTestContext) => TestedRelationCreationPayload)
|
|
||||||
>[];
|
>[];
|
||||||
describe('Field metadata relation creation should fail', () => {
|
describe('Field metadata relation creation should fail', () => {
|
||||||
const failingLabelsCreationTestsUseCase: CreateOneObjectMetadataItemTestingContext =
|
const failingLabelsCreationTestsUseCase: CreateOneObjectMetadataItemTestingContext =
|
||||||
[
|
[
|
||||||
// TODO @prastoin add coverage other fields such as the Type, icon etc etc ( using edge cases fuzzing etc )
|
// TODO @prastoin add coverage other fields such as the Type, icon etc etc ( using edge cases fuzzing etc )
|
||||||
{
|
{
|
||||||
title: 'when targetFieldLabel is empty',
|
title: '(relationCreationPayload) when targetFieldLabel is empty',
|
||||||
context: { targetFieldLabel: '' },
|
context: {
|
||||||
},
|
input: { relationCreationPayload: { targetFieldLabel: '' } },
|
||||||
{
|
},
|
||||||
title: 'when targetFieldLabel exceeds maximum length',
|
|
||||||
context: { targetFieldLabel: 'A'.repeat(64) },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Not handled gracefully should be refactored
|
|
||||||
title: 'when targetObjectMetadataId is unknown',
|
|
||||||
context: { targetObjectMetadataId: faker.string.uuid() },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'when targetFieldLabel contains only whitespace',
|
|
||||||
context: { targetFieldLabel: ' ' },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title:
|
title:
|
||||||
'when targetFieldLabel conflicts with an existing field on target object metadata id',
|
'(relationCreationPayload) when targetFieldLabel exceeds maximum length',
|
||||||
|
context: {
|
||||||
|
input: {
|
||||||
|
relationCreationPayload: { targetFieldLabel: 'A'.repeat(64) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Not handled gracefully should be refactored
|
||||||
|
title:
|
||||||
|
'(relationCreationPayload) when targetObjectMetadataId is unknown',
|
||||||
|
context: {
|
||||||
|
input: {
|
||||||
|
relationCreationPayload: {
|
||||||
|
targetObjectMetadataId: faker.string.uuid(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
'(relationCreationPayload) when targetFieldLabel contains only whitespace',
|
||||||
|
context: {
|
||||||
|
input: {
|
||||||
|
relationCreationPayload: { targetFieldLabel: ' ' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
'(relationCreationPayload) when targetFieldLabel conflicts with an existing field on target object metadata id',
|
||||||
context: ({ collisionFieldLabel, objectMetadataIds }) => ({
|
context: ({ collisionFieldLabel, objectMetadataIds }) => ({
|
||||||
targetObjectMetadataId: objectMetadataIds.targetObjectId,
|
input: {
|
||||||
targetFieldLabel: collisionFieldLabel,
|
relationCreationPayload: {
|
||||||
|
targetObjectMetadataId: objectMetadataIds.targetObjectId,
|
||||||
|
targetFieldLabel: collisionFieldLabel,
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'when type is not provided',
|
title:
|
||||||
context: { type: undefined },
|
'(relationCreationPayload) when targetFieldLabel conflicts with an existing {name}Id on target object metadata id',
|
||||||
|
context: ({ collisionFieldLabelWithId, objectMetadataIds }) => ({
|
||||||
|
input: {
|
||||||
|
relationCreationPayload: {
|
||||||
|
targetObjectMetadataId: objectMetadataIds.targetObjectId,
|
||||||
|
targetFieldLabel: collisionFieldLabelWithId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'when type is a wrong value',
|
title: '(relationCreationPayload) when type is not provided',
|
||||||
context: { type: 'wrong' as RelationType },
|
context: {
|
||||||
|
input: {
|
||||||
|
relationCreationPayload: { type: undefined },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '(relationCreationPayload) when type is a wrong value',
|
||||||
|
context: {
|
||||||
|
input: {
|
||||||
|
relationCreationPayload: { type: 'wrong' as RelationType },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'when {name}Id is already used',
|
||||||
|
context: ({ collisionFieldNameWithId }) => ({
|
||||||
|
input: {
|
||||||
|
name: collisionFieldNameWithId,
|
||||||
|
relationCreationPayload: { targetFieldIcon: '' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
'when target and source are the same object and name are the same',
|
||||||
|
context: {
|
||||||
|
input: {
|
||||||
|
name: 'relationName',
|
||||||
|
relationCreationPayload: {
|
||||||
|
targetObjectMetadataId:
|
||||||
|
globalTestContext.objectMetadataIds.sourceObjectId,
|
||||||
|
targetFieldLabel: 'Relation Name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -79,8 +157,8 @@ describe('Field metadata relation creation should fail', () => {
|
|||||||
},
|
},
|
||||||
} = await createOneObjectMetadata({
|
} = await createOneObjectMetadata({
|
||||||
input: getMockCreateObjectInput({
|
input: getMockCreateObjectInput({
|
||||||
namePlural: 'collisionRelations',
|
namePlural: 'sourceObjects',
|
||||||
nameSingular: 'collisionRelation',
|
nameSingular: 'sourceObject',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,8 +168,8 @@ describe('Field metadata relation creation should fail', () => {
|
|||||||
},
|
},
|
||||||
} = await createOneObjectMetadata({
|
} = await createOneObjectMetadata({
|
||||||
input: getMockCreateObjectInput({
|
input: getMockCreateObjectInput({
|
||||||
namePlural: 'collisionRelationTargets',
|
namePlural: 'targetObjects',
|
||||||
nameSingular: 'collisionRelationTarget',
|
nameSingular: 'targetObject',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,17 +178,54 @@ describe('Field metadata relation creation should fail', () => {
|
|||||||
targetObjectId,
|
targetObjectId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data } = await createOneFieldMetadata({
|
const { data: collisionFieldWithLabelTargetData } =
|
||||||
input: {
|
await createOneFieldMetadata({
|
||||||
objectMetadataId: targetObjectId,
|
input: {
|
||||||
name: globalTestContext.collisionFieldLabel,
|
objectMetadataId: targetObjectId,
|
||||||
label: 'LabelThatCouldBeAnything',
|
name: globalTestContext.collisionFieldName,
|
||||||
isLabelSyncedWithName: false,
|
label: 'LabelThatCouldBeAnything',
|
||||||
type: FieldMetadataType.TEXT,
|
isLabelSyncedWithName: false,
|
||||||
},
|
type: FieldMetadataType.TEXT,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
expect(data).toBeDefined();
|
const { data: collisionFieldWithIdTargetData } =
|
||||||
|
await createOneFieldMetadata({
|
||||||
|
input: {
|
||||||
|
objectMetadataId: targetObjectId,
|
||||||
|
name: globalTestContext.collisionFieldNameWithId,
|
||||||
|
label: 'LabelThatCouldBeAnything',
|
||||||
|
isLabelSyncedWithName: false,
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: collisionFieldWithLabelSourceData } =
|
||||||
|
await createOneFieldMetadata({
|
||||||
|
input: {
|
||||||
|
objectMetadataId: sourceObjectId,
|
||||||
|
name: globalTestContext.collisionFieldName,
|
||||||
|
label: 'LabelThatCouldBeAnything',
|
||||||
|
isLabelSyncedWithName: false,
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: collisionFieldWithIdSourceData } =
|
||||||
|
await createOneFieldMetadata({
|
||||||
|
input: {
|
||||||
|
objectMetadataId: sourceObjectId,
|
||||||
|
name: globalTestContext.collisionFieldNameWithId,
|
||||||
|
label: 'LabelThatCouldBeAnything',
|
||||||
|
isLabelSyncedWithName: false,
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(collisionFieldWithLabelTargetData).toBeDefined();
|
||||||
|
expect(collisionFieldWithIdTargetData).toBeDefined();
|
||||||
|
expect(collisionFieldWithLabelSourceData).toBeDefined();
|
||||||
|
expect(collisionFieldWithIdSourceData).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -128,14 +243,21 @@ describe('Field metadata relation creation should fail', () => {
|
|||||||
it.each(failingLabelsCreationTestsUseCase)(
|
it.each(failingLabelsCreationTestsUseCase)(
|
||||||
'relation ONE_TO_MANY $title',
|
'relation ONE_TO_MANY $title',
|
||||||
async ({ context }) => {
|
async ({ context }) => {
|
||||||
const computedContext =
|
const computedRelationCreationPayload =
|
||||||
typeof context === 'function' ? context(globalTestContext) : context;
|
typeof context === 'function'
|
||||||
|
? context(globalTestContext).input.relationCreationPayload
|
||||||
|
: context.input.relationCreationPayload;
|
||||||
|
|
||||||
|
const computedName =
|
||||||
|
typeof context === 'function'
|
||||||
|
? context(globalTestContext).input.name
|
||||||
|
: context.input.name;
|
||||||
|
|
||||||
const { errors } = await createOneFieldMetadata({
|
const { errors } = await createOneFieldMetadata({
|
||||||
expectToFail: true,
|
expectToFail: true,
|
||||||
input: {
|
input: {
|
||||||
objectMetadataId: globalTestContext.objectMetadataIds.sourceObjectId,
|
objectMetadataId: globalTestContext.objectMetadataIds.sourceObjectId,
|
||||||
name: 'fieldname',
|
name: computedName ?? 'fieldname',
|
||||||
label: 'Relation field',
|
label: 'Relation field',
|
||||||
isLabelSyncedWithName: false,
|
isLabelSyncedWithName: false,
|
||||||
type: FieldMetadataType.RELATION,
|
type: FieldMetadataType.RELATION,
|
||||||
@ -145,7 +267,7 @@ describe('Field metadata relation creation should fail', () => {
|
|||||||
targetObjectMetadataId:
|
targetObjectMetadataId:
|
||||||
globalTestContext.objectMetadataIds.targetObjectId,
|
globalTestContext.objectMetadataIds.targetObjectId,
|
||||||
targetFieldIcon: 'IconBuildingSkyscraper',
|
targetFieldIcon: 'IconBuildingSkyscraper',
|
||||||
...computedContext,
|
...computedRelationCreationPayload,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -158,14 +280,21 @@ describe('Field metadata relation creation should fail', () => {
|
|||||||
it.each(failingLabelsCreationTestsUseCase)(
|
it.each(failingLabelsCreationTestsUseCase)(
|
||||||
'relation MANY_TO_ONE $title',
|
'relation MANY_TO_ONE $title',
|
||||||
async ({ context }) => {
|
async ({ context }) => {
|
||||||
const computedContext =
|
const computedRelationCreationPayload =
|
||||||
typeof context === 'function' ? context(globalTestContext) : context;
|
typeof context === 'function'
|
||||||
|
? context(globalTestContext).input.relationCreationPayload
|
||||||
|
: context.input.relationCreationPayload;
|
||||||
|
|
||||||
|
const computedName =
|
||||||
|
typeof context === 'function'
|
||||||
|
? context(globalTestContext).input.name
|
||||||
|
: context.input.name;
|
||||||
|
|
||||||
const { errors } = await createOneFieldMetadata({
|
const { errors } = await createOneFieldMetadata({
|
||||||
expectToFail: true,
|
expectToFail: true,
|
||||||
input: {
|
input: {
|
||||||
objectMetadataId: globalTestContext.objectMetadataIds.sourceObjectId,
|
objectMetadataId: globalTestContext.objectMetadataIds.sourceObjectId,
|
||||||
name: 'fieldname',
|
name: computedName ?? 'fieldname',
|
||||||
label: 'Relation field',
|
label: 'Relation field',
|
||||||
isLabelSyncedWithName: false,
|
isLabelSyncedWithName: false,
|
||||||
type: FieldMetadataType.RELATION,
|
type: FieldMetadataType.RELATION,
|
||||||
@ -175,7 +304,7 @@ describe('Field metadata relation creation should fail', () => {
|
|||||||
targetObjectMetadataId:
|
targetObjectMetadataId:
|
||||||
globalTestContext.objectMetadataIds.targetObjectId,
|
globalTestContext.objectMetadataIds.targetObjectId,
|
||||||
targetFieldIcon: 'IconBuildingSkyscraper',
|
targetFieldIcon: 'IconBuildingSkyscraper',
|
||||||
...computedContext,
|
...computedRelationCreationPayload,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import { createOneObjectMetadata } from 'test/integration/metadata/suites/object
|
|||||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
describe('updateOne', () => {
|
describe('updateOne', () => {
|
||||||
describe('FieldMetadataService name/label sync', () => {
|
describe('FieldMetadataService name/label sync', () => {
|
||||||
let listingObjectId = '';
|
let listingObjectId = '';
|
||||||
@ -115,5 +117,59 @@ describe('updateOne', () => {
|
|||||||
'Name is not synced with label. Expected name: "testName", got newName',
|
'Name is not synced with label. Expected name: "testName", got newName',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw if the field name is not available because of other field with the same name', async () => {
|
||||||
|
await createOneFieldMetadata({
|
||||||
|
input: {
|
||||||
|
objectMetadataId: listingObjectId,
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
name: 'otherTestName',
|
||||||
|
label: 'Test name',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { errors } = await updateOneFieldMetadata({
|
||||||
|
input: {
|
||||||
|
idToUpdate: testFieldId,
|
||||||
|
updatePayload: { name: 'testName' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(errors[0].message).toBe(
|
||||||
|
'Name "testName" is not available, check that it is not duplicating another field\'s name.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the field name is not available because of other relation field using the same {name}Id', async () => {
|
||||||
|
// Arrange
|
||||||
|
await createOneFieldMetadata({
|
||||||
|
input: {
|
||||||
|
objectMetadataId: listingObjectId,
|
||||||
|
type: FieldMetadataType.RELATION,
|
||||||
|
name: 'children',
|
||||||
|
label: 'Children',
|
||||||
|
relationCreationPayload: {
|
||||||
|
targetObjectMetadataId: listingObjectId,
|
||||||
|
targetFieldLabel: 'parent',
|
||||||
|
targetFieldIcon: 'IconBuildingSkyscraper',
|
||||||
|
type: RelationType.ONE_TO_MANY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const { errors } = await updateOneFieldMetadata({
|
||||||
|
input: {
|
||||||
|
idToUpdate: testFieldId,
|
||||||
|
updatePayload: { name: 'parentId' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(errors[0].message).toBe(
|
||||||
|
'Name "parentId" is not available, check that it is not duplicating another field\'s name.',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user