From 59d3a1492226e8ca5e2ba459a315b235b83a9d3f Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:02:37 +0100 Subject: [PATCH] Common API - Add tests on position field validation at creation (#16630) Co-authored-by: guillim --- .../data-arg-processor/data-arg.processor.ts | 37 +++--- ...idden-position-field-or-throw.util.spec.ts | 87 ++++++++++++++ ...overridden-position-field-or-throw.util.ts | 23 ++++ .../common-base-query-runner.service.ts | 2 +- .../test-object-gql-fields.constant.ts | 1 + ...-input-validation.integration-spec.ts.snap | 17 +++ ...-input-validation.integration-spec.ts.snap | 9 ++ ...e-input-by-field-metadata-type.constant.ts | 12 ++ ...e-input-by-field-metadata-type.constant.ts | 34 ++++++ ...reate-input-validation.integration-spec.ts | 70 ++++++----- ...reate-input-validation.integration-spec.ts | 113 ++++++++++++++++++ .../types/field-metadata-type-to-test.ts | 1 - ...get-field-metadata-creation-inputs.util.ts | 2 +- 13 files changed, 353 insertions(+), 55 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/__tests__/validate-overridden-position-field-or-throw.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-overridden-position-field-or-throw.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/number-field-create-input-validation.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/position-field-create-input-validation.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/position-field-create-input-validation.integration-spec.ts diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/data-arg.processor.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/data-arg.processor.ts index 85bd811e21..0ab2dcf9ff 100644 --- a/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/data-arg.processor.ts +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/data-arg.processor.ts @@ -33,6 +33,7 @@ import { validateLinksFieldOrThrow } from 'src/engine/api/common/common-args-pro import { validateMultiSelectFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-multi-select-field-or-throw.util'; import { validateNumberFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-number-field-or-throw.util'; import { validateNumericFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-numeric-field-or-throw.util'; +import { validateOverriddenPositionFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-overridden-position-field-or-throw.util'; import { validatePhonesFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-phones-field-or-throw.util'; import { validateRatingAndSelectFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-rating-and-select-field-or-throw.util'; import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util'; @@ -44,7 +45,6 @@ import { CommonQueryRunnerExceptionCode, } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; import { transformEmailsValue } from 'src/engine/core-modules/record-transformer/utils/transform-emails-value.util'; import { transformLinksValue } from 'src/engine/core-modules/record-transformer/utils/transform-links-value.util'; @@ -58,10 +58,7 @@ import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-meta @Injectable() export class DataArgProcessor { - constructor( - private readonly recordPositionService: RecordPositionService, - private readonly featureFlagService: FeatureFlagService, - ) {} + constructor(private readonly recordPositionService: RecordPositionService) {} async process({ partialRecordInputs, @@ -90,9 +87,21 @@ export class DataArgProcessor { flatObjectMetadata, ); + const overriddenPositionRecords = + await this.recordPositionService.overridePositionOnRecords({ + partialRecordInputs: partialRecordInputs, + workspaceId: workspace.id, + objectMetadata: { + isCustom: flatObjectMetadata.isCustom, + nameSingular: flatObjectMetadata.nameSingular, + fieldIdByName, + }, + shouldBackfillPositionIfUndefined, + }); + const processedRecords: Partial[] = []; - for (const record of partialRecordInputs) { + for (const record of overriddenPositionRecords) { const processedRecord: Partial = {}; for (const [key, value] of Object.entries(record)) { @@ -139,19 +148,7 @@ export class DataArgProcessor { processedRecords.push(processedRecord); } - const overriddenPositionRecords = - await this.recordPositionService.overridePositionOnRecords({ - partialRecordInputs: processedRecords, - workspaceId: workspace.id, - objectMetadata: { - isCustom: flatObjectMetadata.isCustom, - nameSingular: flatObjectMetadata.nameSingular, - fieldIdByName, - }, - shouldBackfillPositionIfUndefined, - }); - - return overriddenPositionRecords; + return processedRecords; } private async processField( @@ -161,7 +158,7 @@ export class DataArgProcessor { ): Promise { switch (fieldMetadata.type) { case FieldMetadataType.POSITION: - return value; + return validateOverriddenPositionFieldOrThrow(value, key); case FieldMetadataType.NUMERIC: { const validatedValue = validateNumericFieldOrThrow(value, key); diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/__tests__/validate-overridden-position-field-or-throw.util.spec.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/__tests__/validate-overridden-position-field-or-throw.util.spec.ts new file mode 100644 index 0000000000..c49559ae6d --- /dev/null +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/__tests__/validate-overridden-position-field-or-throw.util.spec.ts @@ -0,0 +1,87 @@ +import { validateOverriddenPositionFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-overridden-position-field-or-throw.util'; + +describe('validateOverriddenPositionFieldOrThrow', () => { + describe('valid inputs', () => { + it('should return the position when value is a positive integer', () => { + const result = validateOverriddenPositionFieldOrThrow(123, 'testField'); + + expect(result).toBe(123); + }); + + it('should return the position when value is zero', () => { + const result = validateOverriddenPositionFieldOrThrow(0, 'testField'); + + expect(result).toBe(0); + }); + + it('should return the position when value is negative integer', () => { + const result = validateOverriddenPositionFieldOrThrow(-3, 'testField'); + + expect(result).toBe(-3); + }); + + it('should return the position when value is negative float', () => { + const result = validateOverriddenPositionFieldOrThrow(-0.5, 'testField'); + + expect(result).toBe(-0.5); + }); + }); + + describe('invalid inputs', () => { + it('should throw when value is null', () => { + expect(() => + validateOverriddenPositionFieldOrThrow(null, 'testField'), + ).toThrow('Invalid position value null for field "testField"'); + }); + + it('should throw when value is undefined', () => { + expect(() => + validateOverriddenPositionFieldOrThrow(undefined, 'testField'), + ).toThrow('Invalid position value undefined for field "testField"'); + }); + + it('should throw when value is a string with a number', () => { + expect(() => + validateOverriddenPositionFieldOrThrow('123', 'testField'), + ).toThrow('Invalid position value \'123\' for field "testField"'); + }); + + it('should throw when value is an empty string', () => { + expect(() => + validateOverriddenPositionFieldOrThrow('', 'testField'), + ).toThrow('Invalid position value \'\' for field "testField"'); + }); + + it('should throw when value is a boolean (true)', () => { + expect(() => + validateOverriddenPositionFieldOrThrow(true, 'testField'), + ).toThrow('Invalid position value true for field "testField"'); + }); + + it('should throw when value is a boolean (false)', () => { + expect(() => + validateOverriddenPositionFieldOrThrow(false, 'testField'), + ).toThrow('Invalid position value false for field "testField"'); + }); + + it('should throw when value is an array', () => { + expect(() => + validateOverriddenPositionFieldOrThrow([1, 2, 3], 'testField'), + ).toThrow('Invalid position value [ 1, 2, 3 ] for field "testField"'); + }); + + it('should throw when value is an object', () => { + expect(() => + validateOverriddenPositionFieldOrThrow({ key: 'value' }, 'testField'), + ).toThrow( + 'Invalid position value { key: \'value\' } for field "testField"', + ); + }); + + it('should throw when value is NaN', () => { + expect(() => + validateOverriddenPositionFieldOrThrow(NaN, 'testField'), + ).toThrow('Invalid position value NaN for field "testField"'); + }); + }); +}); diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-overridden-position-field-or-throw.util.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-overridden-position-field-or-throw.util.ts new file mode 100644 index 0000000000..3ac292ac2c --- /dev/null +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-overridden-position-field-or-throw.util.ts @@ -0,0 +1,23 @@ +import { inspect } from 'util'; + +import { + CommonQueryRunnerException, + CommonQueryRunnerExceptionCode, +} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception'; + +export const validateOverriddenPositionFieldOrThrow = ( + value: unknown, + fieldName: string, +): number | null => { + if ( + typeof value !== 'number' || + (typeof value === 'number' && + (isNaN(value) || value === Infinity || value === -Infinity)) + ) + throw new CommonQueryRunnerException( + `Invalid position value ${inspect(value)} for field "${fieldName}"`, + CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA, + ); + + return value; +}; diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts index 038d9d5b43..d3d18a0945 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts @@ -48,8 +48,8 @@ import { import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import type { RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; @Injectable() export abstract class CommonBaseQueryRunnerService< diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/constants/test-object-gql-fields.constant.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/constants/test-object-gql-fields.constant.ts index 7440fdec2c..f4fa775dbe 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/constants/test-object-gql-fields.constant.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/constants/test-object-gql-fields.constant.ts @@ -2,6 +2,7 @@ import { joinColumnNameForManyToOneMorphRelationField1 } from 'test/integration/ export const TEST_OBJECT_GQL_FIELDS = ` id + position manyToOneRelationFieldId manyToOneRelationField { id diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/number-field-create-input-validation.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/number-field-create-input-validation.integration-spec.ts.snap new file mode 100644 index 0000000000..6d14a51ac2 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/number-field-create-input-validation.integration-spec.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Create input validation - NUMBER Gql create input - failure NUMBER - should fail with : {"numberField":"string"} 1`] = `"Float cannot represent non numeric value: "string""`; + +exports[`Create input validation - NUMBER Gql create input - failure NUMBER - should fail with : {"numberField":[]} 1`] = `"Float cannot represent non numeric value: []"`; + +exports[`Create input validation - NUMBER Gql create input - failure NUMBER - should fail with : {"numberField":{}} 1`] = `"Float cannot represent non numeric value: {}"`; + +exports[`Create input validation - NUMBER Gql create input - failure NUMBER - should fail with : {"numberField":true} 1`] = `"Float cannot represent non numeric value: true"`; + +exports[`Create input validation - NUMBER Rest create input - failure NUMBER - should fail with : {"numberField":"string"} 1`] = `"["Invalid number value 'string' for field \\"numberField\\""]"`; + +exports[`Create input validation - NUMBER Rest create input - failure NUMBER - should fail with : {"numberField":[]} 1`] = `"["Invalid number value [] for field \\"numberField\\""]"`; + +exports[`Create input validation - NUMBER Rest create input - failure NUMBER - should fail with : {"numberField":{}} 1`] = `"["Invalid number value {} for field \\"numberField\\""]"`; + +exports[`Create input validation - NUMBER Rest create input - failure NUMBER - should fail with : {"numberField":true} 1`] = `"["Invalid number value true for field \\"numberField\\""]"`; diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/position-field-create-input-validation.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/position-field-create-input-validation.integration-spec.ts.snap new file mode 100644 index 0000000000..b6ae11a906 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/position-field-create-input-validation.integration-spec.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Create input validation - POSITION Gql create input - failure POSITION - should fail with : {"position":"not-a-position"} 1`] = `"Invalid position value: 'not-a-position'. Position must be 'first', 'last', or a number"`; + +exports[`Create input validation - POSITION Gql create input - failure POSITION - should fail with : {"position":null} 1`] = `"Invalid position value null for field "position""`; + +exports[`Create input validation - POSITION Rest create input - failure POSITION - should fail with : {"position":"not-a-position"} 1`] = `"["Invalid position value 'not-a-position' for field \\"position\\""]"`; + +exports[`Create input validation - POSITION Rest create input - failure POSITION - should fail with : {"position":null} 1`] = `"["Invalid position value null for field \\"position\\""]"`; diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant.ts index 8786db92a8..76d4f02162 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant.ts @@ -374,4 +374,16 @@ export const failingCreateInputByFieldMetadataType: { }, }, ], + [FieldMetadataType.POSITION]: [ + { + input: { + position: 'not-a-position', + }, + }, + { + input: { + position: NaN, + }, + }, + ], }; diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/successful-create-input-by-field-metadata-type.constant.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/successful-create-input-by-field-metadata-type.constant.ts index baa37d494e..9071fa27ac 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/successful-create-input-by-field-metadata-type.constant.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/successful-create-input-by-field-metadata-type.constant.ts @@ -475,4 +475,38 @@ export const successfulCreateInputByFieldMetadataType: { }, }, ], + [FieldMetadataType.POSITION]: [ + { + input: { + position: 1000, + }, + validateInput: (record: Record) => { + return record.position === 1000; + }, + }, + { + input: { + position: 'last', + }, + validateInput: (record: Record) => { + return record.position > 1000; + }, + }, + { + input: { + position: 'first', + }, + validateInput: (record: Record) => { + return record.position < 1000; + }, + }, + { + input: { + position: undefined, + }, + validateInput: (record: Record) => { + return typeof record.position === 'number'; + }, + }, + ], }; diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/number-field-create-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/number-field-create-input-validation.integration-spec.ts index ed51d0b8d5..e3f59343b9 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/number-field-create-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/number-field-create-input-validation.integration-spec.ts @@ -1,5 +1,8 @@ +import { failingCreateInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant'; import { successfulCreateInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/create-validation/constants/successful-create-input-by-field-metadata-type.constant'; +import { expectGqlCreateInputValidationError } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-gql-create-input-validation-error.util'; import { expectGqlCreateInputValidationSuccess } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-gql-create-input-validation-success.util'; +import { expectRestCreateInputValidationError } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-rest-create-input-validation-error.util'; import { expectRestCreateInputValidationSuccess } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-rest-create-input-validation-success.util'; import { destroyManyObjectsMetadata } from 'test/integration/graphql/suites/inputs-validation/utils/destroy-many-objects-metadata'; import { setupTestObjectsWithAllFieldTypes } from 'test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util'; @@ -10,6 +13,9 @@ const FIELD_METADATA_TYPE = FieldMetadataType.NUMBER; const successfulTestCases = successfulCreateInputByFieldMetadataType[FIELD_METADATA_TYPE]; +const failingTestCases = + failingCreateInputByFieldMetadataType[FIELD_METADATA_TYPE]; + describe(`Create input validation - ${FIELD_METADATA_TYPE}`, () => { let objectMetadataId: string; let objectMetadataSingularName: string; @@ -35,39 +41,39 @@ describe(`Create input validation - ${FIELD_METADATA_TYPE}`, () => { ]); }); - // describe('Gql create input - failure', () => { - // it.each( - // failingTestCases.map((testCase) => ({ - // ...testCase, - // stringifiedInput: JSON.stringify(testCase.input), - // })), - // )( - // `${FIELD_METADATA_TYPE} - should fail with : $stringifiedInput`, - // async ({ input }) => { - // await expectGqlCreateInputValidationError( - // objectMetadataSingularName, - // input, - // ); - // }, - // ); - // }); + describe('Gql create input - failure', () => { + it.each( + failingTestCases.map((testCase) => ({ + ...testCase, + stringifiedInput: JSON.stringify(testCase.input), + })), + )( + `${FIELD_METADATA_TYPE} - should fail with : $stringifiedInput`, + async ({ input }) => { + await expectGqlCreateInputValidationError( + objectMetadataSingularName, + input, + ); + }, + ); + }); - // describe('Rest create input - failure', () => { - // it.each( - // failingTestCases.map((testCase) => ({ - // ...testCase, - // stringifiedInput: JSON.stringify(testCase.input), - // })), - // )( - // `${FIELD_METADATA_TYPE} - should fail with : $stringifiedInput`, - // async ({ input }) => { - // await expectRestCreateInputValidationError( - // objectMetadataPluralName, - // input, - // ); - // }, - // ); - // }); + describe('Rest create input - failure', () => { + it.each( + failingTestCases.map((testCase) => ({ + ...testCase, + stringifiedInput: JSON.stringify(testCase.input), + })), + )( + `${FIELD_METADATA_TYPE} - should fail with : $stringifiedInput`, + async ({ input }) => { + await expectRestCreateInputValidationError( + objectMetadataPluralName, + input, + ); + }, + ); + }); describe('Gql create input - success', () => { it.each( diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/position-field-create-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/position-field-create-input-validation.integration-spec.ts new file mode 100644 index 0000000000..fb4932f3cf --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/position-field-create-input-validation.integration-spec.ts @@ -0,0 +1,113 @@ +import { failingCreateInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant'; +import { successfulCreateInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/create-validation/constants/successful-create-input-by-field-metadata-type.constant'; +import { expectGqlCreateInputValidationError } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-gql-create-input-validation-error.util'; +import { expectGqlCreateInputValidationSuccess } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-gql-create-input-validation-success.util'; +import { expectRestCreateInputValidationError } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-rest-create-input-validation-error.util'; +import { expectRestCreateInputValidationSuccess } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-rest-create-input-validation-success.util'; +import { destroyManyObjectsMetadata } from 'test/integration/graphql/suites/inputs-validation/utils/destroy-many-objects-metadata'; +import { setupTestObjectsWithAllFieldTypes } from 'test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util'; +import { FieldMetadataType } from 'twenty-shared/types'; + +const FIELD_METADATA_TYPE = FieldMetadataType.POSITION; + +const successfulTestCases = + successfulCreateInputByFieldMetadataType[FIELD_METADATA_TYPE]; +const failingTestCases = + failingCreateInputByFieldMetadataType[FIELD_METADATA_TYPE]; + +describe(`Create input validation - ${FIELD_METADATA_TYPE}`, () => { + let objectMetadataId: string; + let objectMetadataSingularName: string; + let objectMetadataPluralName: string; + let targetObjectMetadata1Id: string; + let targetObjectMetadata2Id: string; + + beforeAll(async () => { + const setupTest = await setupTestObjectsWithAllFieldTypes(); + + objectMetadataId = setupTest.objectMetadataId; + objectMetadataSingularName = setupTest.objectMetadataSingularName; + objectMetadataPluralName = setupTest.objectMetadataPluralName; + targetObjectMetadata1Id = setupTest.targetObjectMetadata1Id; + targetObjectMetadata2Id = setupTest.targetObjectMetadata2Id; + }); + + afterAll(async () => { + await destroyManyObjectsMetadata([ + objectMetadataId, + targetObjectMetadata1Id, + targetObjectMetadata2Id, + ]); + }); + + describe('Gql create input - failure', () => { + it.each( + failingTestCases.map((testCase) => ({ + ...testCase, + stringifiedInput: JSON.stringify(testCase.input), + })), + )( + `${FIELD_METADATA_TYPE} - should fail with : $stringifiedInput`, + async ({ input }) => { + await expectGqlCreateInputValidationError( + objectMetadataSingularName, + input, + ); + }, + ); + }); + + describe('Rest create input - failure', () => { + it.each( + failingTestCases.map((testCase) => ({ + ...testCase, + stringifiedInput: JSON.stringify(testCase.input), + })), + )( + `${FIELD_METADATA_TYPE} - should fail with : $stringifiedInput`, + async ({ input }) => { + await expectRestCreateInputValidationError( + objectMetadataPluralName, + input, + ); + }, + ); + }); + + describe('Gql create input - success', () => { + it.each( + successfulTestCases.map((testCase) => ({ + ...testCase, + stringifiedInput: JSON.stringify(testCase.input), + })), + )( + `${FIELD_METADATA_TYPE} - should succeed with : $stringifiedInput`, + async ({ input, validateInput }) => { + await expectGqlCreateInputValidationSuccess( + objectMetadataSingularName, + input, + validateInput, + ); + }, + ); + }); + + describe('Rest create input - success', () => { + it.each( + successfulTestCases.map((testCase) => ({ + ...testCase, + stringifiedInput: JSON.stringify(testCase.input), + })), + )( + `${FIELD_METADATA_TYPE} - should succeed with : $stringifiedInput`, + async ({ input, validateInput }) => { + await expectRestCreateInputValidationSuccess( + objectMetadataPluralName, + objectMetadataSingularName, + input, + validateInput, + ); + }, + ); + }); +}); diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/types/field-metadata-type-to-test.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/types/field-metadata-type-to-test.ts index 85707446df..4933083e11 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/types/field-metadata-type-to-test.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/types/field-metadata-type-to-test.ts @@ -11,7 +11,6 @@ type FieldMetadataTypesNotTestedForFilterInputValidation = type FieldMetadataTypesNotTestedForCreateInputValidation = | 'TS_VECTOR' | 'ACTOR' - | 'POSITION' | 'NUMERIC'; export type FieldMetadataTypesToTestForCreateInputValidation = Exclude< diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/utils/get-field-metadata-creation-inputs.util.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/utils/get-field-metadata-creation-inputs.util.ts index 23e1891a16..4ca725ed9e 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/utils/get-field-metadata-creation-inputs.util.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/utils/get-field-metadata-creation-inputs.util.ts @@ -29,7 +29,7 @@ export const getFieldMetadataCreationInputs = ( [K in Exclude< | FieldMetadataTypesToTestForCreateInputValidation | FieldMetadataTypesToTestForFilterInputValidation, - 'ACTOR' + 'ACTOR' | 'POSITION' >]: FieldMetadataCreationInput | FieldMetadataCreationInput[]; } = { [FieldMetadataType.RICH_TEXT]: {