Add manifest integration tests (#18203)

- add integration test on sync manifest endpoint
- fix invalid standard uuid + migration command
This commit is contained in:
martmull
2026-02-25 10:37:50 +01:00
committed by GitHub
parent b84a8588be
commit f080b952eb
30 changed files with 1916 additions and 473 deletions
@@ -18,6 +18,7 @@ export class CreateFieldInput extends OmitType(
'standardOverrides',
'applicationId',
'morphId',
'universalIdentifier',
] as const,
InputType,
) {
@@ -64,6 +64,11 @@ export class FieldMetadataDTO<T extends FieldMetadataType = FieldMetadataType> {
@IDField(() => UUIDScalarType)
id: string;
@IsUUID()
@IsNotEmpty()
@IDField(() => UUIDScalarType)
universalIdentifier: string;
@IsEnum(FieldMetadataType)
@IsNotEmpty()
@Field(() => FieldMetadataType)
@@ -14,6 +14,7 @@ export const fromFlatFieldMetadataToFieldMetadataDto = (
isUnique,
settings,
id,
universalIdentifier,
label,
name,
objectMetadataId,
@@ -32,6 +33,7 @@ export const fromFlatFieldMetadataToFieldMetadataDto = (
return {
id,
universalIdentifier,
label,
name,
objectMetadataId,
@@ -0,0 +1,143 @@
import { areFlatObjectMetadataNamesSyncedWithLabels } from 'src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-builder-options.type';
const THIRD_PARTY_BUILD_OPTIONS: WorkspaceMigrationBuilderOptions = {
isSystemBuild: false,
applicationUniversalIdentifier: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
};
const TWENTY_STANDARD_BUILD_OPTIONS: WorkspaceMigrationBuilderOptions = {
isSystemBuild: false,
applicationUniversalIdentifier:
TWENTY_STANDARD_APPLICATION.universalIdentifier,
};
describe('areFlatObjectMetadataNamesSyncedWithLabels', () => {
it('should return true when names match computed names from labels', () => {
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'ticket',
namePlural: 'tickets',
labelSingular: 'Ticket',
labelPlural: 'Tickets',
},
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
});
expect(result).toBe(true);
});
it('should return false when singular name does not match', () => {
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'wrongName',
namePlural: 'tickets',
labelSingular: 'Ticket',
labelPlural: 'Tickets',
},
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
});
expect(result).toBe(false);
});
it('should return false when plural name does not match', () => {
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'ticket',
namePlural: 'wrongPlural',
labelSingular: 'Ticket',
labelPlural: 'Tickets',
},
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
});
expect(result).toBe(false);
});
it('should apply custom suffix for reserved words when caller is a third-party app', () => {
// "Event" computes to "event" which is reserved, so suffix "Custom" is appended
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'eventCustom',
namePlural: 'eventsCustom',
labelSingular: 'Event',
labelPlural: 'Events',
},
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
});
expect(result).toBe(true);
});
it('should return false for reserved words without custom suffix when caller is a third-party app', () => {
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'event',
namePlural: 'events',
labelSingular: 'Event',
labelPlural: 'Events',
},
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
});
expect(result).toBe(false);
});
it('should not apply custom suffix for reserved words when caller is the Twenty Standard app', () => {
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'event',
namePlural: 'events',
labelSingular: 'Event',
labelPlural: 'Events',
},
buildOptions: TWENTY_STANDARD_BUILD_OPTIONS,
});
expect(result).toBe(true);
});
it('should handle multi-word labels by computing camelCase names', () => {
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'supportTicket',
namePlural: 'supportTickets',
labelSingular: 'Support Ticket',
labelPlural: 'Support Tickets',
},
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
});
expect(result).toBe(true);
});
it('should return false when both names do not match', () => {
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'foo',
namePlural: 'bar',
labelSingular: 'Ticket',
labelPlural: 'Tickets',
},
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
});
expect(result).toBe(false);
});
it('should return true with complex labels', () => {
const result = areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectMetadata: {
nameSingular: 'wrongCreatedAtObject',
namePlural: 'wrongCreatedAtObjects',
labelSingular: 'Wrong CreatedAt Object',
labelPlural: 'Wrong CreatedAt Objects',
},
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
});
expect(result).toBe(true);
});
});
@@ -5,18 +5,18 @@ import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/w
import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-builder-options.type';
export const areFlatObjectMetadataNamesSyncedWithLabels = ({
flatObjectdMetadata,
flatObjectMetadata,
buildOptions,
}: {
buildOptions: WorkspaceMigrationBuilderOptions;
flatObjectdMetadata: Pick<
flatObjectMetadata: Pick<
UniversalFlatObjectMetadata,
'namePlural' | 'nameSingular' | 'labelPlural' | 'labelSingular'
>;
}) => {
const [computedSingularName, computedPluralName] = [
flatObjectdMetadata.labelSingular,
flatObjectdMetadata.labelPlural,
flatObjectMetadata.labelSingular,
flatObjectMetadata.labelPlural,
].map((label) =>
computeMetadataNameFromLabel({
label,
@@ -25,7 +25,7 @@ export const areFlatObjectMetadataNamesSyncedWithLabels = ({
);
return (
flatObjectdMetadata.nameSingular === computedSingularName &&
flatObjectdMetadata.namePlural === computedPluralName
flatObjectMetadata.nameSingular === computedSingularName &&
flatObjectMetadata.namePlural === computedPluralName
);
};
@@ -13,6 +13,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
shortcut,
duplicateCriteria,
id,
universalIdentifier,
isActive,
isCustom,
isLabelSyncedWithName,
@@ -32,6 +33,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
return {
id,
universalIdentifier,
isActive,
isCustom,
isLabelSyncedWithName,
@@ -66,7 +66,7 @@ export const validateFlatObjectMetadataNameAndLabels = ({
if (
universalFlatObjectMetadataToValidate.isLabelSyncedWithName &&
!areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectdMetadata: universalFlatObjectMetadataToValidate,
flatObjectMetadata: universalFlatObjectMetadataToValidate,
buildOptions,
})
) {
@@ -32,6 +32,9 @@ export class ObjectMetadataDTO {
@IDField(() => UUIDScalarType)
id: string;
@IDField(() => UUIDScalarType)
universalIdentifier: string;
@Field()
nameSingular: string;
@@ -5,6 +5,7 @@ export const fromRoleEntityToRoleDto = (role: RoleEntity): RoleDTO => {
return {
id: role.id,
label: role.label,
universalIdentifier: role.universalIdentifier,
canUpdateAllSettings: role.canUpdateAllSettings,
canAccessAllTools: role.canAccessAllTools,
description: role.description ?? undefined,