diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts index 1dedcc3a2c5..94761c77b65 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts @@ -160,7 +160,7 @@ export class FieldMetadataRelationService { ) { validateFieldNameAvailabilityOrThrow({ name: `${fieldMetadataInput.name}Id`, - objectMetadata, + fieldMetadataMapById: objectMetadata.fieldsById, }); const relationCreationPayload = ( @@ -189,12 +189,12 @@ export class FieldMetadataRelationService { validateFieldNameAvailabilityOrThrow({ name: computedMetadataNameFromLabel, - objectMetadata: objectMetadataTarget, + fieldMetadataMapById: objectMetadataTarget.fieldsById, }); validateFieldNameAvailabilityOrThrow({ name: `${computedMetadataNameFromLabel}Id`, - objectMetadata: objectMetadataTarget, + fieldMetadataMapById: objectMetadataTarget.fieldsById, }); if ( diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service.ts index 52b732dbd83..9635b6281f6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service.ts @@ -170,7 +170,7 @@ export class FieldMetadataValidationService { try { validateFieldNameAvailabilityOrThrow({ name: fieldMetadataInput.name, - objectMetadata, + fieldMetadataMapById: objectMetadata.fieldsById, }); } catch (error) { if (error instanceof InvalidMetadataException) { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts index 61aba60c68f..e24e6b3af0a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts @@ -10,4 +10,5 @@ export enum ObjectMetadataExceptionCode { MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD = 'MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD', INVALID_ORM_OUTPUT = 'INVALID_ORM_OUTPUT', INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', + NAME_CONFLICT = 'NAME_CONFLICT', } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index 5fc6a6dbbcd..66c334a447e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -40,6 +40,7 @@ import { } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { validateMetadataIdentifierFieldMetadataIds } from 'src/engine/metadata-modules/utils/validate-metadata-identifier-field-metadata-id.utils'; import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; import { validatesNoOtherObjectWithSameNameExistsOrThrows } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util'; @@ -221,10 +222,18 @@ export class ObjectMetadataService extends TypeOrmQueryService ({ + ...acc, + [field.id]: field, + }), + {}, + ); + const createdRelatedObjectMetadataCollection = await this.objectMetadataFieldRelationService.createRelationsAndForeignKeysMetadata( createObjectInput.workspaceId, - createdObjectMetadata, + { ...createdObjectMetadata, fieldsById }, objectMetadataMaps, queryRunner, ); @@ -374,12 +383,14 @@ export class ObjectMetadataService extends TypeOrmQueryService, + >; objectMetadataForUpdate: Pick< ObjectMetadataItemWithFieldMaps, | 'nameSingular' @@ -617,10 +634,11 @@ export class ObjectMetadataService extends TypeOrmQueryService, - inputPayload: UpdateObjectPayload, - queryRunner: QueryRunner, - ): Promise<{ didUpdateLabelOrIcon: boolean }> { + >; + inputPayload: UpdateObjectPayload; + queryRunner: QueryRunner; + objectMetadataMaps: ObjectMetadataMaps; + }): Promise<{ didUpdateLabelOrIcon: boolean }> { const newTargetTableName = computeObjectTargetTable( objectMetadataForUpdate, ); @@ -638,9 +656,12 @@ export class ObjectMetadataService extends TypeOrmQueryService +> & + Required, 'name'>>; + @Injectable() export class ObjectMetadataFieldRelationService { constructor( @@ -52,7 +60,7 @@ export class ObjectMetadataFieldRelationService { workspaceId: string, sourceObjectMetadata: Pick< ObjectMetadataItemWithFieldMaps, - 'id' | 'nameSingular' | 'labelSingular' + 'id' | 'nameSingular' | 'labelSingular' | 'fieldsById' >, objectMetadataMaps: ObjectMetadataMaps, queryRunner?: QueryRunner, @@ -83,8 +91,9 @@ export class ObjectMetadataFieldRelationService { workspaceId: string; sourceObjectMetadata: Pick< ObjectMetadataItemWithFieldMaps, - 'id' | 'nameSingular' | 'labelSingular' + 'id' | 'nameSingular' | 'labelSingular' | 'fieldsById' >; + objectMetadataMaps: ObjectMetadataMaps; relationObjectMetadataStandardId: string; queryRunner?: QueryRunner; @@ -116,7 +125,7 @@ export class ObjectMetadataFieldRelationService { workspaceId: string, sourceObjectMetadata: Pick< ObjectMetadataItemWithFieldMaps, - 'id' | 'nameSingular' | 'labelSingular' + 'id' | 'nameSingular' | 'labelSingular' | 'fieldsById' >, targetObjectMetadata: ObjectMetadataItemWithFieldMaps, queryRunner?: QueryRunner, @@ -127,12 +136,22 @@ export class ObjectMetadataFieldRelationService { targetObjectMetadata, ); + this.validateFieldNameAvailabilityOrThrow({ + name: sourceFieldMetadata.name, + fieldMetadataMapById: sourceObjectMetadata.fieldsById, + }); + const targetFieldMetadata = this.createTargetFieldMetadata( workspaceId, sourceObjectMetadata, targetObjectMetadata, ); + this.validateFieldNameAvailabilityOrThrow({ + name: targetFieldMetadata.name, + fieldMetadataMapById: sourceObjectMetadata.fieldsById, + }); + const fieldMetadataRepository = queryRunner ? queryRunner.manager.getRepository(FieldMetadataEntity) : this.fieldMetadataRepository; @@ -159,16 +178,22 @@ export class ObjectMetadataFieldRelationService { ]); } - public async updateRelationsAndForeignKeysMetadata( - workspaceId: string, + public async updateRelationsAndForeignKeysMetadata({ + workspaceId, + updatedObjectMetadata, + objectMetadataMaps, + queryRunner, + }: { + workspaceId: string; updatedObjectMetadata: Pick< ObjectMetadataEntity, 'nameSingular' | 'isCustom' | 'id' | 'labelSingular' - >, - queryRunner?: QueryRunner, - ): Promise< + >; + objectMetadataMaps: ObjectMetadataMaps; + queryRunner?: QueryRunner; + }): Promise< { - targetObjectMetadata: ObjectMetadataEntity; + targetObjectMetadata: ObjectMetadataItemWithFieldMaps; targetFieldMetadata: FieldMetadataEntity; sourceFieldMetadata: FieldMetadataEntity; }[] @@ -176,39 +201,61 @@ export class ObjectMetadataFieldRelationService { return await Promise.all( DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS.map( async (relationObjectMetadataStandardId) => - this.updateRelationAndForeignKeyMetadata( + this.updateRelationAndForeignKeyMetadata({ workspaceId, - updatedObjectMetadata, - relationObjectMetadataStandardId, + sourceObjectMetadata: updatedObjectMetadata, + targetObjectMetadataStandardId: relationObjectMetadataStandardId, + objectMetadataMaps, queryRunner, - ), + }), ), ); } - private async updateRelationAndForeignKeyMetadata( - workspaceId: string, + private async updateRelationAndForeignKeyMetadata({ + workspaceId, + sourceObjectMetadata, + targetObjectMetadataStandardId, + objectMetadataMaps, + queryRunner, + }: { + workspaceId: string; sourceObjectMetadata: Pick< ObjectMetadataEntity, 'nameSingular' | 'id' | 'isCustom' | 'labelSingular' - >, - targetObjectMetadataStandardId: string, - queryRunner?: QueryRunner, - ) { - const objectMetadataRepository = queryRunner - ? queryRunner.manager.getRepository(ObjectMetadataEntity) - : this.objectMetadataRepository; + >; + targetObjectMetadataStandardId: string; + objectMetadataMaps: ObjectMetadataMaps; + queryRunner?: QueryRunner; + }): Promise<{ + targetObjectMetadata: ObjectMetadataItemWithFieldMaps; + targetFieldMetadata: FieldMetadataEntity; + sourceFieldMetadata: FieldMetadataEntity; + }> { const fieldMetadataRepository = queryRunner ? queryRunner.manager.getRepository(FieldMetadataEntity) : this.fieldMetadataRepository; - const targetObjectMetadata = await objectMetadataRepository.findOneByOrFail( - { - standardId: targetObjectMetadataStandardId, - workspaceId: workspaceId, - isCustom: false, - }, - ); + const targetObjectMetadataId = Object.values(objectMetadataMaps.byId).find( + (objectMetadata) => + objectMetadata?.standardId === targetObjectMetadataStandardId && + objectMetadata.isCustom === false, + )?.id; + + if (!targetObjectMetadataId) { + throw new Error( + `Target object metadata id not found for standard ID: ${targetObjectMetadataStandardId}`, + ); + } + + const targetObjectMetadata = + objectMetadataMaps.byId[targetObjectMetadataId]; + + if (!targetObjectMetadata) { + throw new Error( + `Target object metadata not found for id: ${targetObjectMetadataId}`, + ); + } const targetFieldMetadataUpdateData = this.updateTargetFieldMetadata( sourceObjectMetadata, @@ -226,6 +273,25 @@ export class ObjectMetadataFieldRelationService { workspaceId: workspaceId, }); + const nameIsUpdated = + targetFieldMetadataUpdateData.name !== targetFieldMetadataToUpdate.name; + + if (nameIsUpdated) { + const targetObjectMetadataFieldsById = + objectMetadataMaps.byId[targetObjectMetadata.id]?.fieldsById; + + if (!targetObjectMetadataFieldsById) { + throw new Error( + `Target object metadata fields not found for ${targetObjectMetadata.id}`, + ); + } + + this.validateFieldNameAvailabilityOrThrow({ + name: targetFieldMetadataUpdateData.name, + fieldMetadataMapById: targetObjectMetadataFieldsById, + }); + } + const isTargetFieldMetadataManyToOneRelation = ( targetFieldMetadataToUpdate as FieldMetadataEntity @@ -293,7 +359,7 @@ export class ObjectMetadataFieldRelationService { ObjectMetadataItemWithFieldMaps, 'namePlural' | 'labelSingular' >, - ): Partial> { + ): PartialRelationFieldMetadata { const relationObjectMetadataNamePlural = targetObjectMetadata.namePlural; const { description } = buildDescriptionForRelationFieldMetadataOnFromField( @@ -353,7 +419,7 @@ export class ObjectMetadataFieldRelationService { ObjectMetadataItemWithFieldMaps, 'namePlural' | 'labelSingular' | 'id' | 'nameSingular' >, - ): Partial> { + ): PartialRelationFieldMetadata { const customStandardFieldId = // @ts-expect-error legacy noImplicitAny STANDARD_OBJECT_FIELD_IDS[targetObjectMetadata.nameSingular].custom; @@ -524,4 +590,30 @@ export class ObjectMetadataFieldRelationService { return morphRelationFieldMetadataToUpdateWithNewJoinColumnName; } + + private validateFieldNameAvailabilityOrThrow({ + name, + fieldMetadataMapById, + }: { + name: string; + fieldMetadataMapById: FieldMetadataMap; + }) { + try { + validateFieldNameAvailabilityOrThrow({ + name, + fieldMetadataMapById, + }); + } catch (error) { + if (error instanceof InvalidMetadataException) { + throw new ObjectMetadataException( + `Name "${name}" is not available.`, + ObjectMetadataExceptionCode.NAME_CONFLICT, + { + userFriendlyMessage: `Name "${name}" is not available.`, + }, + ); + } + throw error; + } + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service.ts index 5422c8de6be..0f98b416d35 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service.ts @@ -142,7 +142,7 @@ export class ObjectMetadataMigrationService { currentObjectMetadata: Pick, alteredObjectMetadata: Pick, relationMetadataCollection: { - targetObjectMetadata: ObjectMetadataEntity; + targetObjectMetadata: ObjectMetadataItemWithFieldMaps; targetFieldMetadata: FieldMetadataEntity; sourceFieldMetadata: FieldMetadataEntity; }[], diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util.ts index 12f97b3697c..07896013d5d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util.ts @@ -9,7 +9,7 @@ import { export const buildDefaultFieldsForCustomObject = ( workspaceId: string, -): Partial[] => [ +): Array & { id: string }> => [ { id: v4(), standardId: BASE_OBJECT_STANDARD_FIELD_IDS.id, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts index 60ec04b1ce7..34fb41a9875 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts @@ -31,6 +31,7 @@ export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => { case ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT: throw new UserInputError(error); case ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED: + case ObjectMetadataExceptionCode.NAME_CONFLICT: throw new ForbiddenError(error); case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS: throw new ConflictError(error); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-field-name-availability.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-field-name-availability.spec.ts index 4c82c1b4ea2..5cb9a490093 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-field-name-availability.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-field-name-availability.spec.ts @@ -64,14 +64,14 @@ describe('validateFieldNameAvailabilityOrThrow', () => { expect(() => validateFieldNameAvailabilityOrThrow({ name: input, - objectMetadata: objectMetadataMapItemMock, + fieldMetadataMapById: objectMetadataMapItemMock.fieldsById, }), ).not.toThrow(); } else { expect(() => validateFieldNameAvailabilityOrThrow({ name: input, - objectMetadata: objectMetadataMapItemMock, + fieldMetadataMapById: objectMetadataMapItemMock.fieldsById, }), ).toThrowErrorMatchingSnapshot(); } diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts index 5773ae8de3a..4ab69a95a17 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts @@ -4,18 +4,18 @@ import { FieldMetadataType } from 'twenty-shared/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 { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { type FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; import { InvalidMetadataException, InvalidMetadataExceptionCode, } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; const getReservedCompositeFieldNames = ( - objectMetadata: ObjectMetadataItemWithFieldMaps, + fieldMetadataMapById: FieldMetadataMap, ) => { const reservedCompositeFieldsNames: string[] = []; - for (const field of Object.values(objectMetadata.fieldsById)) { + for (const field of Object.values(fieldMetadataMapById)) { if (isCompositeFieldMetadataType(field.type)) { const base = field.name; const compositeType = compositeTypeDefinitions.get(field.type); @@ -33,17 +33,17 @@ const getReservedCompositeFieldNames = ( type ValidateFieldNameAvailabilityOrThrowArgs = { name: string; - objectMetadata: ObjectMetadataItemWithFieldMaps; + fieldMetadataMapById: FieldMetadataMap; }; export const validateFieldNameAvailabilityOrThrow = ({ name, - objectMetadata, + fieldMetadataMapById, }: ValidateFieldNameAvailabilityOrThrowArgs) => { const reservedCompositeFieldsNames = - getReservedCompositeFieldNames(objectMetadata); + getReservedCompositeFieldNames(fieldMetadataMapById); if ( - Object.values(objectMetadata.fieldsById).some( + Object.values(fieldMetadataMapById).some( (field) => field.name === name || (field.type === FieldMetadataType.RELATION && @@ -54,7 +54,7 @@ export const validateFieldNameAvailabilityOrThrow = ({ `Name "${name}" is not available as it is already used by another field`, InvalidMetadataExceptionCode.NOT_AVAILABLE, { - userFriendlyMessage: t`This name is not available as it is already used by another field`, + userFriendlyMessage: t`This name is not available as it is already used by another field.`, }, ); } diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-field-metadata-morph-relation-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-field-metadata-morph-relation-creation.integration-spec.ts.snap index 6d27f0e0ce8..d2e7d522210 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-field-metadata-morph-relation-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-field-metadata-morph-relation-creation.integration-spec.ts.snap @@ -6,7 +6,7 @@ exports[`Field metadata morph relation creation should fail relation MANY_TO_ONE "extensions": { "code": "BAD_USER_INPUT", "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field", + "userFriendlyMessage": "This name is not available as it is already used by another field.", }, "message": "Name "collisionfieldlabel" is not available as it is already used by another field", "name": "UserInputError", @@ -103,7 +103,7 @@ exports[`Field metadata morph relation creation should fail relation ONE_TO_MANY "extensions": { "code": "BAD_USER_INPUT", "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field", + "userFriendlyMessage": "This name is not available as it is already used by another field.", }, "message": "Name "collisionfieldlabel" is not available as it is already used by another field", "name": "UserInputError", diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap index 88c69194bb6..f8796369e96 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap @@ -6,7 +6,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "BAD_USER_INPUT", "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field", + "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", @@ -20,7 +20,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "BAD_USER_INPUT", "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field", + "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", @@ -145,7 +145,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "BAD_USER_INPUT", "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field", + "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", @@ -159,7 +159,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "BAD_USER_INPUT", "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field", + "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", diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts index 666761997d5..58d8a626c81 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts @@ -188,7 +188,33 @@ describe('Custom object renaming', () => { }); }); - it('3. should delete custom object', async () => { + it('3. should fail when trying to rename object to "name"', async () => { + // Act & Assert + const { errors } = await updateOneObjectMetadata({ + gqlFields: ` + nameSingular + labelSingular + namePlural + labelPlural + `, + input: { + idToUpdate: listingObjectId, + updatePayload: { + nameSingular: 'name', + }, + }, + expectToFail: true, + }); + + // Assert that an error was thrown + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors[0].extensions.userFriendlyMessage).toContain( + 'Name "name" is not available', + ); + }); + + it('4. should delete custom object', async () => { const { data } = await deleteOneObjectMetadata({ input: { idToDelete: listingObjectId,