Files
twenty/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-object-system-fields.integration-spec.ts
T
Paul RastoinandGitHub f0a625c3f8 Cleanup application and app registration test util (#19981)
## Introduction
Centralizing integ test app cleanup
Role will be deleted by the app uninstall if exists
2026-04-22 14:39:58 +00:00

404 lines
12 KiB
TypeScript

import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util';
import { buildDefaultObjectManifest } from 'test/integration/metadata/suites/application/utils/build-default-object-manifest.util';
import { cleanupApplicationAndAppRegistration } from 'test/integration/metadata/suites/application/utils/cleanup-application-and-app-registration.util';
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
import { type Manifest, type ObjectManifest } from 'twenty-shared/application';
import {
type EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { FieldMetadataType } from 'twenty-shared/types';
import { v4 as uuidv4 } from 'uuid';
const TEST_APP_ID = uuidv4();
const TEST_ROLE_ID = uuidv4();
type TestContext = {
manifest: Manifest;
};
type SyncApplicationTestingContext = EachTestingContext<TestContext>[];
const buildManifest = (overrides: Pick<Manifest, 'objects' | 'fields'>) =>
buildBaseManifest({
appId: TEST_APP_ID,
roleId: TEST_ROLE_ID,
overrides,
});
const buildObjectWithLabelField = ({
nameSingular,
namePlural,
labelSingular,
labelPlural,
description,
additionalFields = [],
}: {
nameSingular: string;
namePlural: string;
labelSingular: string;
labelPlural: string;
description: string;
additionalFields?: ObjectManifest['fields'];
}): Pick<Manifest, 'objects' | 'fields'> => {
const objectId = uuidv4();
const labelFieldId = uuidv4();
return {
objects: [
{
universalIdentifier: objectId,
labelIdentifierFieldMetadataUniversalIdentifier: labelFieldId,
nameSingular,
namePlural,
labelSingular,
labelPlural,
description,
icon: 'IconTicket',
fields: [
{
universalIdentifier: labelFieldId,
type: FieldMetadataType.TEXT,
name: 'title',
label: 'Title',
description: 'Label identifier field',
icon: 'IconTextCaption',
},
...additionalFields,
],
},
],
fields: [],
};
};
const buildDefaultObjectWithModifiedSearchVector = ({
nameSingular,
namePlural,
labelSingular,
labelPlural,
description,
searchVectorOverrides,
}: {
nameSingular: string;
namePlural: string;
labelSingular: string;
labelPlural: string;
description: string;
searchVectorOverrides: Partial<ObjectManifest['fields'][number]>;
}): Pick<Manifest, 'objects' | 'fields'> => {
const defaultObject = buildDefaultObjectManifest({
nameSingular,
namePlural,
labelSingular,
labelPlural,
description,
});
return {
objects: [
{
...defaultObject,
fields: defaultObject.fields.map((field) =>
field.name === 'searchVector'
? ({
...field,
...searchVectorOverrides,
} as (typeof defaultObject.fields)[number])
: field,
),
},
],
fields: [],
};
};
const failingSyncApplicationSystemFieldsTestCases: SyncApplicationTestingContext =
[
{
title:
'when object is created without any system fields (missing all 8 system fields)',
context: {
manifest: buildManifest(
buildObjectWithLabelField({
nameSingular: 'noSystemFieldsObject',
namePlural: 'noSystemFieldsObjects',
labelSingular: 'No System Fields Object',
labelPlural: 'No System Fields Objects',
description: 'Object with no system fields',
}),
),
},
},
{
title: 'when object has id field with wrong type (TEXT instead of UUID)',
context: {
manifest: buildManifest(
buildObjectWithLabelField({
nameSingular: 'wrongIdTypeObject',
namePlural: 'wrongIdTypeObjects',
labelSingular: 'Wrong Id Type Object',
labelPlural: 'Wrong Id Type Objects',
description: 'Object with wrong id field type',
additionalFields: [
{
universalIdentifier: uuidv4(),
type: FieldMetadataType.TEXT,
name: 'id',
label: 'Id',
description: 'Id field with wrong type',
icon: 'IconKey',
},
],
}),
),
},
},
{
title: 'when object miss default fields',
context: {
manifest: buildManifest(
buildObjectWithLabelField({
nameSingular: 'wrongCreatedAtObject',
namePlural: 'wrongCreatedAtObjects',
labelSingular: 'Wrong CreatedAt Object',
labelPlural: 'Wrong CreatedAt Objects',
description: 'Object with wrong createdAt field type',
}),
),
},
},
{
title:
'when object has position field with wrong type (TEXT instead of POSITION)',
context: {
manifest: buildManifest(
buildObjectWithLabelField({
nameSingular: 'wrongPositionObject',
namePlural: 'wrongPositionObjects',
labelSingular: 'Wrong Position Object',
labelPlural: 'Wrong Position Objects',
description: 'Object with wrong position field type',
additionalFields: [
{
universalIdentifier: uuidv4(),
type: FieldMetadataType.TEXT,
name: 'position',
label: 'Position',
description: 'Position field with wrong type',
icon: 'IconArrowsSort',
},
],
}),
),
},
},
{
title:
'when object has searchVector field with wrong type (TEXT instead of TS_VECTOR)',
context: {
manifest: buildManifest(
buildDefaultObjectWithModifiedSearchVector({
nameSingular: 'wrongSearchVectorType',
namePlural: 'wrongSearchVectorTypes',
labelSingular: 'Wrong SearchVector Type',
labelPlural: 'Wrong SearchVector Types',
description: 'Object with wrong searchVector field type',
searchVectorOverrides: {
type: FieldMetadataType.TEXT,
},
}),
),
},
},
{
title:
'when object has TS_VECTOR field with wrong name (not searchVector)',
context: {
manifest: buildManifest(
buildDefaultObjectWithModifiedSearchVector({
nameSingular: 'wrongTsVectorName',
namePlural: 'wrongTsVectorNames',
labelSingular: 'Wrong TsVector Name',
labelPlural: 'Wrong TsVector Names',
description: 'Object with TS_VECTOR field named incorrectly',
searchVectorOverrides: {
name: 'wrongSearchVector',
},
}),
),
},
},
{
title:
'when label identifier is non-searchable type (searchVector has no expression)',
context: (() => {
const nonSearchableFieldId = uuidv4();
return {
manifest: buildManifest({
objects: [
buildDefaultObjectManifest({
nameSingular: 'noSearchVectorExpression',
namePlural: 'noSearchVectorExpressions',
labelSingular: 'No SearchVector Expression',
labelPlural: 'No SearchVector Expressions',
description:
'Object whose label identifier is non-searchable, so searchVector has no expression',
labelIdentifierFieldMetadataUniversalIdentifier:
nonSearchableFieldId,
additionalFields: [
{
universalIdentifier: nonSearchableFieldId,
type: FieldMetadataType.NUMBER,
name: 'quantity',
label: 'Quantity',
},
],
}),
],
fields: [],
}),
};
})(),
},
];
describe('Sync application should fail due to object system fields integrity', () => {
beforeAll(async () => {
await setupApplicationForSync({
applicationUniversalIdentifier: TEST_APP_ID,
name: 'Test System Fields App',
description: 'App for testing system field validation',
sourcePath: 'test-system-fields',
});
}, 60000);
afterAll(async () => {
await cleanupApplicationAndAppRegistration({
applicationUniversalIdentifier: TEST_APP_ID,
});
});
it.each(
eachTestingContextFilter(failingSyncApplicationSystemFieldsTestCases),
)(
'$title',
async ({ context }) => {
const { errors } = await syncApplication({
manifest: context.manifest,
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
},
60000,
);
it('should fail when trying to delete a system field after a successful sync', async () => {
const labelIdentifierFieldUniversalIdentifier = uuidv4();
const testObject = buildDefaultObjectManifest({
nameSingular: 'deleteSystemFieldObject',
namePlural: 'deleteSystemFieldObjects',
labelSingular: 'Delete System Field Object',
labelPlural: 'Delete System Field Objects',
description: 'Object for testing system field deletion',
labelIdentifierFieldMetadataUniversalIdentifier:
labelIdentifierFieldUniversalIdentifier,
additionalFields: [
{
universalIdentifier: labelIdentifierFieldUniversalIdentifier,
type: FieldMetadataType.TEXT,
name: 'labelIdentifierField',
label: 'Label Identifier Field',
description: 'Label identifier field',
icon: 'IconTextCaption',
},
],
});
const validManifest = buildManifest({
objects: [testObject],
fields: [],
});
await syncApplication({
manifest: validManifest,
expectToFail: false,
});
const manifestWithDeletedIdField = buildManifest({
objects: [
{
...testObject,
fields: testObject.fields.filter((field) => field.name !== 'id'),
},
],
fields: [],
});
const { errors } = await syncApplication({
manifest: manifestWithDeletedIdField,
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
}, 60000);
it('should fail when trying to update a system field after a successful sync', async () => {
const labelIdentifierFieldUniversalIdentifier = uuidv4();
const testObject = buildDefaultObjectManifest({
nameSingular: 'updateSystemFieldObject',
namePlural: 'updateSystemFieldObjects',
labelSingular: 'Update System Field Object',
labelPlural: 'Update System Field Objects',
description: 'Object for testing system field update',
labelIdentifierFieldMetadataUniversalIdentifier:
labelIdentifierFieldUniversalIdentifier,
additionalFields: [
{
universalIdentifier: labelIdentifierFieldUniversalIdentifier,
type: FieldMetadataType.TEXT,
name: 'labelIdentifierField',
label: 'Label Identifier Field',
description: 'Label identifier field',
icon: 'IconTextCaption',
},
],
});
const validManifest = buildManifest({
objects: [testObject],
fields: [],
});
await syncApplication({
manifest: validManifest,
expectToFail: false,
});
const manifestWithUpdatedIdField = buildManifest({
objects: [
{
...testObject,
fields: testObject.fields.map((field) =>
field.name === 'id'
? { ...field, label: 'Modified Id Label' }
: field,
),
},
],
fields: [],
});
const { errors } = await syncApplication({
manifest: manifestWithUpdatedIdField,
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
}, 60000);
});