From eb7556e3334a88390956e07c61155252289c754d Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Tue, 3 Jun 2025 18:31:58 +0530 Subject: [PATCH] Fix: multi-select default values validation (#12271) https://github.com/user-attachments/assets/3bea63cc-b098-4252-8787-fc6263f01e8d Closes #12277 --------- Co-authored-by: prastoin Co-authored-by: Charles Bochet --- .../field-metadata-validation.service.ts | 46 -- .../field-metadata/field-metadata.service.ts | 23 +- .../field-metadata-default-value.interface.ts | 15 +- .../field-metadata-enum-validation.service.ts | 162 ++-- .../utils/is-enum-field-metadata-type.util.ts | 20 +- .../validate-default-value-for-type.util.ts | 4 +- .../services/field-metadata-health.service.ts | 42 +- ...duplicated-whitespaces-from-string.spec.ts | 68 ++ ...itespaces-from-object-string-properties.ts | 13 +- ...move-duplicated-whitespaces-from-string.ts | 4 + ...d-metadata-select.integration-spec.ts.snap | 321 -------- ...d-metadata-select.integration-spec.ts.snap | 310 ------- ...-field-metadata-select.integration-spec.ts | 128 --- ...eld-metadata-enum.integration-spec.ts.snap | 773 ++++++++++++++++++ ...um-field-metadata.integration-spec.ts.snap | 751 +++++++++++++++++ ...ti-select-operation-agnostic-test-cases.ts | 98 +++ ...i-select-operation-agnostic-tests-cases.ts | 354 ++++++++ .../select-operation-agnostic-test-cases.ts | 46 ++ .../create-enum-field-metadata-test-cases.ts | 49 ++ ...ne-field-metadata-enum.integration-spec.ts | 116 +++ ...tadataEnumSuccessfulAndFailingTestCases.ts | 7 + ...te-create-field-metadata-enum-test-case.ts | 10 + .../update-enum-field-metadata-test-cases.ts | 94 +++ ...ne-enum-field-metadata.integration-spec.ts | 228 ++++++ ...e-one-field-metadata-select-tests-cases.ts | 374 --------- ...-field-metadata-select.integration-spec.ts | 202 ----- ...ate-one-field-metadata.integration-spec.ts | 4 +- packages/twenty-shared/src/testing/index.ts | 1 + .../types/SuccessfulAndFailingTestCases.ts | 6 + 29 files changed, 2797 insertions(+), 1472 deletions(-) create mode 100644 packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-string.spec.ts create mode 100644 packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-string.ts delete mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata-select.integration-spec.ts.snap delete mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata-select.integration-spec.ts.snap delete mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/create-one-field-metadata-select.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/create-one-field-metadata-enum.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/update-one-enum-field-metadata.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/multi-select-operation-agnostic-test-cases.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/select-and-multi-select-operation-agnostic-tests-cases.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/select-operation-agnostic-test-cases.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/create-enum-field-metadata-test-cases.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/create-one-field-metadata-enum.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/update-enum-field-metadata-test-cases.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/update-one-enum-field-metadata.integration-spec.ts delete mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/update-create-one-field-metadata-select-tests-cases.ts delete mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-select.integration-spec.ts create mode 100644 packages/twenty-shared/src/testing/types/SuccessfulAndFailingTestCases.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts index 784960057..5c33901ee 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts @@ -11,8 +11,6 @@ import { } from 'class-validator'; import { FieldMetadataType } from 'twenty-shared/types'; -import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; -import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface'; import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; import { @@ -91,48 +89,4 @@ export class FieldMetadataValidationService< ); } } - - async validateDefaultValueOrThrow({ - fieldType, - defaultValue, - options, - }: { - fieldType: FieldMetadataType; - defaultValue: FieldMetadataDefaultValue; - options: FieldMetadataOptions; - }) { - if ( - fieldType === FieldMetadataType.SELECT || - fieldType === FieldMetadataType.MULTI_SELECT - ) { - this.validateEnumDefaultValue(options, defaultValue); - } - } - - private validateEnumDefaultValue( - options: FieldMetadataOptions, - defaultValue: FieldMetadataDefaultValue, - ) { - if (typeof defaultValue === 'string') { - const formattedDefaultValue = defaultValue.replace( - /^['"](.*)['"]$/, - '$1', - ); - - const enumOptions = options.map((option) => option.value); - - if ( - enumOptions && - (enumOptions.includes(formattedDefaultValue) || - // @ts-expect-error legacy noImplicitAny - enumOptions.some((option) => option.to === formattedDefaultValue)) - ) { - return; - } - } - throw new FieldMetadataException( - `Default value for existing options is invalid: ${defaultValue}`, - FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - ); - } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index 0fd47591f..35591dfd3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -68,6 +68,7 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global. 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 { ViewService } from 'src/modules/view/services/view.service'; +import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties'; import { FieldMetadataValidationService } from './field-metadata-validation.service'; @@ -662,13 +663,15 @@ export class FieldMetadataService extends TypeOrmQueryService; +export type FieldMetadataDefaultValueForType< + T extends keyof FieldMetadataDefaultValueMapping, +> = ExtractValueType | null; + +export type FieldMetadataDefaultValueForAnyType = ExtractValueType< + UnionOfValues +> | null; + export type FieldMetadataDefaultValue< T extends FieldMetadataType = FieldMetadataType, > = IsExactly extends true - ? ExtractValueType> | null + ? FieldMetadataDefaultValueForAnyType : T extends keyof FieldMetadataDefaultValueMapping - ? ExtractValueType | null + ? FieldMetadataDefaultValueForType : never; type FieldMetadataDefaultValueExtractedTypes = { diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service.ts index e9fb07300..dc806ddf6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service.ts @@ -2,24 +2,28 @@ import { Injectable } from '@nestjs/common'; import { isNonEmptyString } from '@sniptt/guards'; import { FieldMetadataType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; +import { assertUnreachable, isDefined } from 'twenty-shared/utils'; import { z } from 'zod'; import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface'; import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; +import { + FieldMetadataComplexOption, + FieldMetadataDefaultOption, +} from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input'; -import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataException, FieldMetadataExceptionCode, } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; -import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; +import { EnumFieldMetadataUnionType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; import { beneathDatabaseIdentifierMinimumLength, exceedsDatabaseIdentifierMaximumLength, } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; +import { EnumFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory'; import { isSnakeCaseString } from 'src/utils/is-snake-case-string'; type Validator = { validator: (str: T) => boolean; message: string }; @@ -29,14 +33,14 @@ type FieldMetadataUpdateCreateInput = CreateFieldInput | UpdateFieldInput; type ValidateEnumFieldMetadataArgs = { existingFieldMetadata?: FieldMetadataEntity; fieldMetadataInput: FieldMetadataUpdateCreateInput; - fieldMetadataType: FieldMetadataType; + fieldMetadataType: EnumFieldMetadataUnionType; }; +const QUOTED_STRING_REGEX = /^['"](.*)['"]$/; + @Injectable() export class FieldMetadataEnumValidationService { - constructor( - private readonly fieldMetadataValidationService: FieldMetadataValidationService, - ) {} + constructor() {} private validatorRunner( elementToValidate: T, @@ -124,38 +128,22 @@ export class FieldMetadataEnumValidationService { } private validateDuplicates(options: FieldMetadataOptions) { - const seenOptionIds = new Set(); - const seenOptionValues = new Set(); - const seenOptionPositions = new Set< - FieldMetadataOptions[number]['position'] - >(); + const fieldsToCheckForDuplicates = [ + 'position', + 'id', + 'value', + ] as const satisfies (keyof FieldMetadataOptions[number])[]; + const duplicatedValidators = fieldsToCheckForDuplicates.map< + Validator + >((field) => ({ + message: `Duplicated option ${field}`, + validator: () => + new Set(options.map((option) => option[field])).size !== options.length, + })); - for (const option of options) { - if (seenOptionIds.has(option.id)) { - throw new FieldMetadataException( - `Duplicated option id "${option.id}"`, - FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - ); - } - - if (seenOptionValues.has(option.value)) { - throw new FieldMetadataException( - `Duplicated option value "${option.value}"`, - FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - ); - } - - if (seenOptionPositions.has(option.position)) { - throw new FieldMetadataException( - `Duplicated option position "${option.position}"`, - FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - ); - } - - seenOptionIds.add(option.id); - seenOptionValues.add(option.value); - seenOptionPositions.add(option.position); - } + duplicatedValidators.forEach((validator) => + this.validatorRunner(options, validator), + ); } private validateFieldMetadataInputOptions( @@ -179,15 +167,97 @@ export class FieldMetadataEnumValidationService { this.validateDuplicates(options); } + private validateSelectDefaultValue( + options: FieldMetadataOptions, + defaultValue: unknown, + ) { + if (typeof defaultValue !== 'string') { + throw new FieldMetadataException( + 'Default value for multi-select must be a stringified array', + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); + } + + const validators: Validator[] = [ + { + validator: (value: string) => !QUOTED_STRING_REGEX.test(value), + message: 'Default value should be as quoted string', + }, + { + validator: (value: string) => + !options.some( + (option) => + option.value === value.replace(QUOTED_STRING_REGEX, '$1'), + ), + message: `Default value "${defaultValue}" must be one of the option values`, + }, + ]; + + validators.forEach((validator) => + this.validatorRunner(defaultValue, validator), + ); + } + + private validateMultiSelectDefaultValue( + options: FieldMetadataOptions, + defaultValue: unknown, + ) { + if (!Array.isArray(defaultValue)) { + throw new FieldMetadataException( + 'Default value for multi-select must be an array', + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); + } + + const validators: Validator[] = [ + { + validator: (values) => values.length === 0, + message: 'If defined default value must contain at least one value', + }, + { + validator: (values) => new Set(values).size !== values.length, + message: 'Default values must be unique', + }, + ]; + + validators.forEach((validator) => + this.validatorRunner(defaultValue, validator), + ); + + defaultValue.forEach((value) => { + this.validateSelectDefaultValue(options, value); + }); + } + + private validateFieldMetadataDefaultValue( + fieldType: EnumFieldMetadataType, + options: FieldMetadataOptions, + defaultValue: unknown, + ) { + switch (fieldType) { + case FieldMetadataType.SELECT: + this.validateSelectDefaultValue(options, defaultValue); + break; + case FieldMetadataType.MULTI_SELECT: + this.validateMultiSelectDefaultValue(options, defaultValue); + break; + case FieldMetadataType.RATING: + // TODO: Determine if RATING should be handled here + break; + default: { + assertUnreachable( + fieldType, + 'Should never occur, unknown field metadata enum type', + ); + } + } + } + async validateEnumFieldMetadataInput({ fieldMetadataInput, fieldMetadataType, existingFieldMetadata, }: ValidateEnumFieldMetadataArgs) { - if (!isEnumFieldMetadataType(fieldMetadataType)) { - return; - } - const isUpdate = isDefined(existingFieldMetadata); const shouldSkipFieldMetadataInputOptionsValidation = isUpdate && fieldMetadataInput.options === undefined; @@ -207,11 +277,11 @@ export class FieldMetadataEnumValidationService { ); } - await this.fieldMetadataValidationService.validateDefaultValueOrThrow({ - fieldType: fieldMetadataType, + this.validateFieldMetadataDefaultValue( + fieldMetadataType, options, - defaultValue: fieldMetadataInput.defaultValue, - }); + fieldMetadataInput.defaultValue, + ); } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util.ts index 2319a7f06..509bf09c3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util.ts @@ -1,15 +1,15 @@ +import { isDefined } from 'class-validator'; import { FieldMetadataType } from 'twenty-shared/types'; +export const fieldMetadataEnumTypes = [ + FieldMetadataType.MULTI_SELECT, + FieldMetadataType.SELECT, + FieldMetadataType.RATING, +] as const; + export type EnumFieldMetadataUnionType = - | FieldMetadataType.RATING - | FieldMetadataType.SELECT - | FieldMetadataType.MULTI_SELECT; + (typeof fieldMetadataEnumTypes)[number]; export const isEnumFieldMetadataType = ( type: FieldMetadataType, -): type is EnumFieldMetadataUnionType => { - return ( - type === FieldMetadataType.RATING || - type === FieldMetadataType.SELECT || - type === FieldMetadataType.MULTI_SELECT - ); -}; +): type is EnumFieldMetadataUnionType => + isDefined(fieldMetadataEnumTypes.find((el) => type === el)); diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts index 27b3f5db2..75bf96ddc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts @@ -85,7 +85,7 @@ export const validateDefaultValueForType = ( } const validationResults = validators.map((validator) => { - const conputedDefaultValue = isCompositeFieldMetadataType(type) + const computedDefaultValue = isCompositeFieldMetadataType(type) ? defaultValue : { value: defaultValue }; @@ -93,7 +93,7 @@ export const validateDefaultValueForType = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any any, FieldMetadataClassValidation - >(validator, conputedDefaultValue as FieldMetadataClassValidation); + >(validator, computedDefaultValue as FieldMetadataClassValidation); const errors = validateSync(defaultValueInstance, { whitelist: true, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/field-metadata-health.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/field-metadata-health.service.ts index 3c37ceb8a..15d5fd54d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/field-metadata-health.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/field-metadata-health.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; -import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; import { WorkspaceHealthIssue, WorkspaceHealthIssueType, @@ -17,10 +17,7 @@ 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 { - EnumFieldMetadataUnionType, - isEnumFieldMetadataType, -} from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; +import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value'; import { validateDefaultValueForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util'; import { validateOptionsForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-options-for-type.util'; @@ -291,15 +288,34 @@ export class FieldMetadataHealthService { const enumValues = fieldMetadata.options?.map((option) => serializeDefaultValue(`'${option.value}'`), ); - const metadataDefaultValue = - fieldMetadata.defaultValue as FieldMetadataDefaultValue; + const metadataDefaultValue = fieldMetadata.defaultValue; - if (metadataDefaultValue && !enumValues.includes(metadataDefaultValue)) { - issues.push({ - type: WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_NOT_VALID, - fieldMetadata, - message: `Column default value is not in the enum values "${metadataDefaultValue}" NOT IN "${enumValues}"`, - }); + if (isDefined(metadataDefaultValue)) { + if (fieldMetadata.type === FieldMetadataType.MULTI_SELECT) { + if (!Array.isArray(metadataDefaultValue)) { + issues.push({ + type: WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_NOT_VALID, + fieldMetadata, + message: `Column default value for multi-select must be an array, got "${metadataDefaultValue}"`, + }); + } else { + metadataDefaultValue.forEach((value) => { + if (!enumValues.includes(value)) { + issues.push({ + type: WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_NOT_VALID, + fieldMetadata, + message: `Column default value "${value}" is not in the enum values "${enumValues}"`, + }); + } + }); + } + } else if (enumValues.includes(metadataDefaultValue as string)) { + issues.push({ + type: WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_NOT_VALID, + fieldMetadata, + message: `Column default value is not in the enum values "${metadataDefaultValue}" NOT IN "${enumValues}"`, + }); + } } } diff --git a/packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-string.spec.ts b/packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-string.spec.ts new file mode 100644 index 000000000..4030447ef --- /dev/null +++ b/packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-string.spec.ts @@ -0,0 +1,68 @@ +import { EachTestingContext } from 'twenty-shared/testing'; + +import { trimAndRemoveDuplicatedWhitespacesFromString } from 'src/utils/trim-and-remove-duplicated-whitespaces-from-string'; + +type TrimAndRemoveWhitespacesTestCase = EachTestingContext<{ + input: string; + expected: string; +}>; + +describe('trim-and-remove-duplicated-whitespaces-from-string', () => { + const testCases: TrimAndRemoveWhitespacesTestCase[] = [ + { + title: 'should trim and remove duplicated whitespaces from a string', + context: { + input: ' Hello World ', + expected: 'Hello World', + }, + }, + { + title: 'should handle string with multiple spaces between words', + context: { + input: 'This is a test', + expected: 'This is a test', + }, + }, + { + title: 'should handle string with only whitespaces', + context: { + input: ' ', + expected: '', + }, + }, + { + title: 'should handle empty string', + context: { + input: '', + expected: '', + }, + }, + { + title: 'should handle string with tabs and newlines', + context: { + input: 'Hello\t\t\tWorld\n\n Test', + expected: 'Hello World Test', + }, + }, + { + title: 'should handle string with leading and trailing spaces', + context: { + input: ' Leading and trailing spaces ', + expected: 'Leading and trailing spaces', + }, + }, + { + title: 'should preserve single spaces between words', + context: { + input: 'This is already properly spaced', + expected: 'This is already properly spaced', + }, + }, + ]; + + test.each(testCases)('$title', ({ context: { input, expected } }) => { + const result = trimAndRemoveDuplicatedWhitespacesFromString(input); + + expect(result).toEqual(expected); + }); +}); diff --git a/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts b/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts index 9abed176a..0e5d73653 100644 --- a/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts +++ b/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts @@ -1,4 +1,4 @@ -import { isDefined } from 'twenty-shared/utils'; +import { trimAndRemoveDuplicatedWhitespacesFromString } from 'src/utils/trim-and-remove-duplicated-whitespaces-from-string'; type OnlyStringPropertiesKey = Extract; @@ -8,9 +8,6 @@ type StringPropertyKeys = { : never; }[OnlyStringPropertiesKey]; -const sanitizeString = (str: string | null) => - isDefined(str) ? str.trim().replace(/\s+/g, ' ') : str; - export const trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties = ( obj: T, keys: StringPropertyKeys[], @@ -18,13 +15,17 @@ export const trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties = ( return keys.reduce((acc, key) => { const occurrence = acc[key]; - if (occurrence === undefined || typeof occurrence !== 'string') { + if ( + occurrence === undefined || + typeof occurrence !== 'string' || + occurrence === null + ) { return acc; } return { ...acc, - [key]: sanitizeString(acc[key] as string | null), + [key]: trimAndRemoveDuplicatedWhitespacesFromString(occurrence), }; }, obj); }; diff --git a/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-string.ts b/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-string.ts new file mode 100644 index 000000000..6f5387829 --- /dev/null +++ b/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-string.ts @@ -0,0 +1,4 @@ +const MULTIPLE_WHITESPACE_REGEX = /\s+/g; + +export const trimAndRemoveDuplicatedWhitespacesFromString = (str: string) => + str.trim().replace(MULTIPLE_WHITESPACE_REGEX, ' '); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata-select.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata-select.integration-spec.ts.snap deleted file mode 100644 index 2fd7dd334..000000000 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata-select.integration-spec.ts.snap +++ /dev/null @@ -1,321 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Field metadata select creation tests group Create should fail with an invalid default value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Default value for existing options is invalid: invalid", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with an unknown default value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Default value for existing options is invalid: 'OPTION_424242'", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with comma in option label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Label must not contain a comma", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with duplicated option ids 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Duplicated option id "fd1f11fd-3f05-4a33-bddf-800c3412ce98"", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with duplicated option positions 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Duplicated option position "1"", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with duplicated option values 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Duplicated option value "OPTION_1"", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with duplicated trimmed option values 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Duplicated option value "OPTION_1"", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with empty options 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Options are required for enum fields", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with empty string id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with empty string label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label "" is beneath 1 character", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with empty string value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value "" is beneath 1 character", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with invalid option id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with invalid option value format 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with not a string id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with not a string label 1`] = ` -[ - { - "extensions": { - "code": "INTERNAL_SERVER_ERROR", - "exceptionEventId": "mocked-exception-id", - }, - "message": "label.includes is not a function", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with not a string value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Value must be in UPPER_CASE and follow snake_case "22222"", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with null id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is required", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with null label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label is required", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with null options 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Options are required for enum fields", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with null value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value is required", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with only white spaces id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with only white spaces label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label "" is beneath 1 character", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with only white spaces value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value "" is beneath 1 character", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with too long id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with too long label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with too long value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with undefined option label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label is required", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with undefined option value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value is required", - }, -] -`; - -exports[`Field metadata select creation tests group Create should fail with undefined options 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Options are required for enum fields", - }, -] -`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata-select.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata-select.integration-spec.ts.snap deleted file mode 100644 index 758c0d2b1..000000000 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata-select.integration-spec.ts.snap +++ /dev/null @@ -1,310 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Field metadata select update tests group Update should fail with an invalid default value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Default value for existing options is invalid: invalid", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with an unknown default value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Default value for existing options is invalid: 'OPTION_424242'", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with comma in option label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Label must not contain a comma", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with duplicated option ids 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Duplicated option id "fd1f11fd-3f05-4a33-bddf-800c3412ce98"", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with duplicated option positions 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Duplicated option position "1"", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with duplicated option values 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Duplicated option value "OPTION_1"", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with duplicated trimmed option values 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Duplicated option value "OPTION_1"", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with empty options 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Options are required for enum fields", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with empty string id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with empty string label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label "" is beneath 1 character", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with empty string value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value "" is beneath 1 character", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with invalid option id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with invalid option value format 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with not a string id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with not a string label 1`] = ` -[ - { - "extensions": { - "code": "INTERNAL_SERVER_ERROR", - "exceptionEventId": "mocked-exception-id", - }, - "message": "label.includes is not a function", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with not a string value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Value must be in UPPER_CASE and follow snake_case "22222"", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with null id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is required", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with null label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label is required", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with null value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value is required", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with only white spaces id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with only white spaces label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label "" is beneath 1 character", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with only white spaces value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value "" is beneath 1 character", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with too long id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option id is invalid", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with too long label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with too long value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with undefined option label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option label is required", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with undefined option value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Option value is required", - }, -] -`; - -exports[`Field metadata select update tests group Update should fail with unknown default value and no options 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - }, - "message": "Default value for existing options is invalid: 'OPTION_42'", - }, -] -`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/create-one-field-metadata-select.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/create-one-field-metadata-select.integration-spec.ts deleted file mode 100644 index 2d9baca16..000000000 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/create-one-field-metadata-select.integration-spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { - UPDATE_CREATE_ONE_FIELD_METADATA_SELECT_TEST_CASES, - UpdateCreateFieldMetadataSelectTestCase, -} from 'test/integration/metadata/suites/field-metadata/update-create-one-field-metadata-select-tests-cases'; -import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; -import { - LISTING_NAME_PLURAL, - LISTING_NAME_SINGULAR, -} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant'; -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-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 { isDefined } from 'twenty-shared/utils'; - -import { - FieldMetadataComplexOption, - FieldMetadataDefaultOption, -} from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; - -const { failingTestCases, successfulTestCases } = - UPDATE_CREATE_ONE_FIELD_METADATA_SELECT_TEST_CASES; - -describe('Field metadata select creation tests group', () => { - let createdObjectMetadataId: string; - - beforeEach(async () => { - const { data } = await createOneObjectMetadata({ - input: { - labelSingular: LISTING_NAME_SINGULAR, - labelPlural: LISTING_NAME_PLURAL, - nameSingular: LISTING_NAME_SINGULAR, - namePlural: LISTING_NAME_PLURAL, - icon: 'IconBuildingSkyscraper', - isLabelSyncedWithName: false, - }, - }); - - createdObjectMetadataId = data.createOneObject.id; - }); - - afterEach(async () => { - await deleteOneObjectMetadata({ - input: { idToDelete: createdObjectMetadataId }, - }); - }); - - test.each(successfulTestCases)( - 'Create $title', - async ({ context: { input, expectedOptions } }) => { - const { data, errors } = await createOneFieldMetadata({ - input: { - objectMetadataId: createdObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'testField', - label: 'Test Field', - isLabelSyncedWithName: false, - ...input, - }, - gqlFields: ` - id - options - defaultValue - `, - }); - - expect(data).not.toBeNull(); - expect(data.createOneField).toBeDefined(); - const createdOptions: - | FieldMetadataDefaultOption[] - | FieldMetadataComplexOption[] = data.createOneField.options; - - const optionsToCompare = expectedOptions ?? input.options; - - expect(errors).toBeUndefined(); - expect(createdOptions.length).toBe(optionsToCompare.length); - createdOptions.forEach((option) => expect(option.id).toBeDefined()); - expect(createdOptions).toMatchObject(optionsToCompare); - - if (isDefined(input.defaultValue)) { - expect(data.createOneField.defaultValue).toEqual(input.defaultValue); - } - }, - ); - - const createSpecificFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] = - [ - { - title: 'should fail with null options', - context: { - input: { - options: null as unknown as FieldMetadataComplexOption[], - }, - }, - }, - { - title: 'should fail with undefined options', - context: { - input: { - options: undefined as unknown as FieldMetadataComplexOption[], - }, - }, - }, - ]; - - test.each([...failingTestCases, ...createSpecificFailingTestCases])( - 'Create $title', - async ({ context: { input } }) => { - const { data, errors } = await createOneFieldMetadata({ - input: { - objectMetadataId: createdObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'testField', - label: 'Test Field', - isLabelSyncedWithName: false, - ...input, - }, - gqlFields: ` - id - options - `, - }); - - expect(data).toBeNull(); - expect(errors).toBeDefined(); - expect(errors).toMatchSnapshot(); - }, - ); -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/create-one-field-metadata-enum.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/create-one-field-metadata-enum.integration-spec.ts.snap new file mode 100644 index 000000000..a91cd9d1e --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/create-one-field-metadata-enum.integration-spec.ts.snap @@ -0,0 +1,773 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with an invalid default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with an unknown default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with comma in option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Label must not contain a comma", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with duplicated option ids 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option id", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with duplicated option positions 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option position", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with duplicated option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option value", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with duplicated trimmed option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option value", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with empty multi-select default values array 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "If defined default value must contain at least one value", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with empty options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Options are required for enum fields", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with empty string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with empty string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with empty string label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "" is beneath 1 character", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with empty string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "" is beneath 1 character", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with invalid multi-select default value format 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with invalid option id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with invalid option value format 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with non stringified array default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with not a string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with not a string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with not a string label 1`] = ` +[ + { + "extensions": { + "code": "INTERNAL_SERVER_ERROR", + "exceptionEventId": "mocked-exception-id", + }, + "message": "label.includes is not a function", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with not a string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with null id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is required", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with null label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label is required", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with null options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Options are required for enum fields", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with null value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value is required", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with only white spaces defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with only white spaces id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with only white spaces label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "" is beneath 1 character", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with only white spaces value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "" is beneath 1 character", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with too long defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with too long id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with too long label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with too long value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with undefined option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label is required", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with undefined option value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value is required", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with undefined options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Options are required for enum fields", + }, +] +`; + +exports[`Create field metadata MULTI_SELECT tests suite Create should fail with unknown multi-select default values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value "'UNKNOWN_OPTION'" must be one of the option values", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with an invalid default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with an unknown default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value "'OPTION_424242'" must be one of the option values", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with comma in option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Label must not contain a comma", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with duplicated option ids 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option id", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with duplicated option positions 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option position", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with duplicated option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option value", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with duplicated trimmed option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option value", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with empty options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Options are required for enum fields", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with empty string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with empty string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with empty string label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "" is beneath 1 character", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with empty string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "" is beneath 1 character", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with invalid option id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with invalid option value format 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with not a string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be a stringified array", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with not a string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with not a string label 1`] = ` +[ + { + "extensions": { + "code": "INTERNAL_SERVER_ERROR", + "exceptionEventId": "mocked-exception-id", + }, + "message": "label.includes is not a function", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with not a string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with null id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is required", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with null label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label is required", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with null options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Options are required for enum fields", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with null value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value is required", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with only white spaces defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with only white spaces id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with only white spaces label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "" is beneath 1 character", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with only white spaces value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "" is beneath 1 character", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with too long defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with too long id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with too long label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with too long value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with undefined option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label is required", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with undefined option value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value is required", + }, +] +`; + +exports[`Create field metadata SELECT tests suite Create should fail with undefined options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Options are required for enum fields", + }, +] +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/update-one-enum-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/update-one-enum-field-metadata.integration-spec.ts.snap new file mode 100644 index 000000000..8b6c40329 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/update-one-enum-field-metadata.integration-spec.ts.snap @@ -0,0 +1,751 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with an invalid default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with an unknown default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with comma in option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Label must not contain a comma", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with duplicated option ids 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option id", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with duplicated option positions 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option position", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with duplicated option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option value", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with duplicated trimmed option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option value", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with empty multi-select default values array 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "If defined default value must contain at least one value", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with empty options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Options are required for enum fields", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with empty string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with empty string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with empty string label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "" is beneath 1 character", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with empty string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "" is beneath 1 character", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with invalid multi-select default value format 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with invalid option id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with invalid option value format 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with non stringified array default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with not a string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with not a string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with not a string label 1`] = ` +[ + { + "extensions": { + "code": "INTERNAL_SERVER_ERROR", + "exceptionEventId": "mocked-exception-id", + }, + "message": "label.includes is not a function", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with not a string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with null id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is required", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with null label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label is required", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with null value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value is required", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with only white spaces defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with only white spaces id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with only white spaces label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "" is beneath 1 character", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with only white spaces value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "" is beneath 1 character", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with too long defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be an array", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with too long id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with too long label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with too long value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with undefined option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label is required", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with undefined option value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value is required", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with unknown default value and no options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value "'OPTION_42'" must be one of the option values", + }, +] +`; + +exports[`Update field metadata MULTI_SELECT tests suite Update should fail with unknown multi-select default values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value "'UNKNOWN_OPTION'" must be one of the option values", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with an invalid default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with an unknown default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value "'OPTION_424242'" must be one of the option values", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with comma in option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Label must not contain a comma", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with duplicated option ids 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option id", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with duplicated option positions 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option position", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with duplicated option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option value", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with duplicated trimmed option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Duplicated option value", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with empty options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Options are required for enum fields", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with empty string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with empty string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with empty string label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "" is beneath 1 character", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with empty string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "" is beneath 1 character", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with invalid option id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with invalid option value format 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with not a string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value for multi-select must be a stringified array", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with not a string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with not a string label 1`] = ` +[ + { + "extensions": { + "code": "INTERNAL_SERVER_ERROR", + "exceptionEventId": "mocked-exception-id", + }, + "message": "label.includes is not a function", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with not a string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with null id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is required", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with null label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label is required", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with null value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value is required", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with only white spaces defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with only white spaces id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with only white spaces label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "" is beneath 1 character", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with only white spaces value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "" is beneath 1 character", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with too long defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value should be as quoted string", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with too long id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option id is invalid", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with too long label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with too long value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with undefined option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option label is required", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with undefined option value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Option value is required", + }, +] +`; + +exports[`Update field metadata SELECT tests suite Update should fail with unknown default value and no options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + }, + "message": "Default value "'OPTION_42'" must be one of the option values", + }, +] +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/multi-select-operation-agnostic-test-cases.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/multi-select-operation-agnostic-test-cases.ts new file mode 100644 index 000000000..d762f9a68 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/multi-select-operation-agnostic-test-cases.ts @@ -0,0 +1,98 @@ +import { SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/select-and-multi-select-operation-agnostic-tests-cases'; +import { FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases'; + +export const MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES: FieldMetadataEnumSuccessfulAndFailingTestCases = + { + successful: [ + ...SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES.successful, + { + title: 'should succeed with valid multi-select default values', + context: { + input: { + defaultValue: ["'OPTION_1'", "'OPTION_2'"], + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + { + label: 'Option 2', + value: 'OPTION_2', + color: 'blue', + position: 2, + }, + ], + }, + }, + }, + ], + failing: [ + ...SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES.failing, + { + title: 'should fail with non stringified array default value', + context: { + input: { + defaultValue: 'Option_1', + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with empty multi-select default values array', + context: { + input: { + defaultValue: [], + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with unknown multi-select default values', + context: { + input: { + defaultValue: ["'OPTION_1'", "'UNKNOWN_OPTION'"], + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with invalid multi-select default value format', + context: { + input: { + defaultValue: ['invalid', 'format'], + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + ], + }; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/select-and-multi-select-operation-agnostic-tests-cases.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/select-and-multi-select-operation-agnostic-tests-cases.ts new file mode 100644 index 000000000..8423274de --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/select-and-multi-select-operation-agnostic-tests-cases.ts @@ -0,0 +1,354 @@ +import { v4 } from 'uuid'; +import { FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases'; +import { UpdateCreateFieldMetadataSelectTestCase } from 'test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case'; +import { isDefined } from 'twenty-shared/utils'; + +import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; + +const basicFailingStringEdgeCaseInputs: { + label: string; + input: string | undefined | number | null; +}[] = [ + { input: ' ', label: 'only white spaces' }, + { input: '', label: 'empty string' }, + { input: null, label: 'null' }, + { input: 22222, label: 'not a string' }, + { input: 'a'.repeat(64), label: 'too long' }, +]; + +const stringFields: (keyof FieldMetadataComplexOption)[] = [ + 'id', + 'label', + 'value', +]; +const fuzzedOptionsStringFieldFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] = + stringFields.flatMap((field) => { + return basicFailingStringEdgeCaseInputs.map( + ({ input, label }) => ({ + title: `should fail with ${label} ${field}`, + context: { + input: { + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + [field]: input, + }, + ], + }, + }, + }), + ); + }); + +const fuzzedDefaultValueFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] = + basicFailingStringEdgeCaseInputs + .filter((el) => isDefined(el.input)) + .map(({ input, label }) => ({ + title: `should fail with ${label} defaultValue`, + context: { + input: { + defaultValue: input, + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + })); + +export const SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES: FieldMetadataEnumSuccessfulAndFailingTestCases = + { + failing: [ + ...fuzzedDefaultValueFailingTestCases, + ...fuzzedOptionsStringFieldFailingTestCases, + { + title: 'should fail with invalid option id', + context: { + input: { + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + id: 'not a uuid', + }, + ], + }, + }, + }, + { + title: 'should fail with empty options', + context: { + input: { + options: [], + }, + }, + }, + { + title: 'should fail with invalid option value format', + context: { + input: { + options: [ + { + label: 'Option 1', + value: 'Option 1 and some other things, /', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with comma in option label', + context: { + input: { + options: [ + { + label: 'Option ,1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with duplicated option values', + context: { + input: { + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 0, + }, + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with duplicated option ids', + context: { + input: { + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + id: 'fd1f11fd-3f05-4a33-bddf-800c3412ce98', + }, + { + label: 'Option 2', + value: 'OPTION_2', + color: 'green', + position: 2, + id: 'fd1f11fd-3f05-4a33-bddf-800c3412ce98', + }, + ], + }, + }, + }, + { + title: 'should fail with duplicated option positions', + context: { + input: { + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + { + label: 'Option 2', + value: 'OPTION_2', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with duplicated trimmed option values', + context: { + input: { + options: [ + { + label: 'Option 1', + value: ' OPTION_1 ', + color: 'green', + position: 1, + }, + { + label: 'Option 2', + value: ' OPTION_1 ', + color: 'green', + position: 2, + }, + ], + }, + }, + }, + { + title: 'should fail with undefined option label', + context: { + input: { + options: [ + { + label: undefined as unknown as string, + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with an invalid default value', + context: { + input: { + defaultValue: 'invalid', + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with an unknown default value', + context: { + input: { + defaultValue: "'OPTION_424242'", + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should fail with undefined option value', + context: { + input: { + options: [ + { + label: 'Option 1', + value: undefined as unknown as string, + color: 'green', + position: 1, + }, + ], + }, + }, + }, + ], + successful: [ + { + title: 'should succeed with provided option id', + context: { + input: { + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31', + }, + ], + }, + }, + }, + { + title: 'should succeed with various options id', + context: { + input: { + options: Array.from({ length: 42 }, (_value, index) => { + const optionWithoutId: FieldMetadataComplexOption = { + label: `Option ${index}`, + value: `OPTION_${index}`, + color: 'green', + position: index, + }; + + if (index % 2 === 0) { + return { + ...optionWithoutId, + id: v4(), + }; + } + + return optionWithoutId; + }), + }, + }, + }, + { + title: 'should succeed without option id', + context: { + input: { + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + }, + { + title: 'should trim option values', + context: { + input: { + options: [ + { + label: ' Option 1 ', + value: ' OPTION_1 ', + color: 'green', + position: 1, + }, + ], + }, + expectedOptions: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + ], + }, + }, + ], + }; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/select-operation-agnostic-test-cases.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/select-operation-agnostic-test-cases.ts new file mode 100644 index 000000000..e664f40c4 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/common/select-operation-agnostic-test-cases.ts @@ -0,0 +1,46 @@ +import { SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/select-and-multi-select-operation-agnostic-tests-cases'; +import { FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases'; + +export const SELECT_OPERATION_AGNOSTIC_TEST_CASES: FieldMetadataEnumSuccessfulAndFailingTestCases = + { + failing: [ + ...SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES.failing, + ], + successful: [ + ...SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES.successful, + { + title: 'should succeed with valid default value', + context: { + input: { + defaultValue: "'OPTION_1'", + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31', + }, + ], + }, + }, + }, + { + title: 'should succeed with null default value', + context: { + input: { + defaultValue: null, + options: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31', + }, + ], + }, + }, + }, + ], + }; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/create-enum-field-metadata-test-cases.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/create-enum-field-metadata-test-cases.ts new file mode 100644 index 000000000..0c4842f5b --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/create-enum-field-metadata-test-cases.ts @@ -0,0 +1,49 @@ +import { MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/multi-select-operation-agnostic-test-cases'; +import { SELECT_OPERATION_AGNOSTIC_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/select-operation-agnostic-test-cases'; +import { FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases'; +import { UpdateCreateFieldMetadataSelectTestCase } from 'test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case'; + +import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; +import { EnumFieldMetadataUnionType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; + +const fieldMetadataTypeAgnosticCreateFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] = + [ + { + title: 'should fail with null options', + context: { + input: { + options: null as unknown as FieldMetadataComplexOption[], + }, + }, + }, + { + title: 'should fail with undefined options', + context: { + input: { + options: undefined as unknown as FieldMetadataComplexOption[], + }, + }, + }, + ]; + +export const CREATE_ENUM_FIELD_METADATA_TEST_CASES: Partial< + Record< + EnumFieldMetadataUnionType, + FieldMetadataEnumSuccessfulAndFailingTestCases + > +> = { + MULTI_SELECT: { + failing: [ + ...MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES.failing, + ...fieldMetadataTypeAgnosticCreateFailingTestCases, + ], + successful: [...MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES.successful], + }, + SELECT: { + failing: [ + ...SELECT_OPERATION_AGNOSTIC_TEST_CASES.failing, + ...fieldMetadataTypeAgnosticCreateFailingTestCases, + ], + successful: [...SELECT_OPERATION_AGNOSTIC_TEST_CASES.successful], + }, +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/create-one-field-metadata-enum.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/create-one-field-metadata-enum.integration-spec.ts new file mode 100644 index 000000000..2d19e0aab --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/create-one-field-metadata-enum.integration-spec.ts @@ -0,0 +1,116 @@ +import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; +import { + LISTING_NAME_PLURAL, + LISTING_NAME_SINGULAR, +} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant'; +import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; +import { forceCreateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util'; +import { CREATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/create-enum-field-metadata-test-cases'; +import { isDefined } from 'twenty-shared/utils'; + +import { + FieldMetadataComplexOption, + FieldMetadataDefaultOption, +} from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; +import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; + +describe.each(fieldMetadataEnumTypes)( + 'Create field metadata %s tests suite', + (testedFieldMetadataType) => { + let createdObjectMetadataId: string; + const testCases = + CREATE_ENUM_FIELD_METADATA_TEST_CASES[testedFieldMetadataType]; + + if (!isDefined(testCases)) { + return; + } + const { failing: failingTestCases, successful: successfulTestCases } = + testCases; + + beforeEach(async () => { + const { data } = await forceCreateOneObjectMetadata({ + input: { + labelSingular: LISTING_NAME_SINGULAR, + labelPlural: LISTING_NAME_PLURAL, + nameSingular: LISTING_NAME_SINGULAR, + namePlural: LISTING_NAME_PLURAL, + icon: 'IconBuildingSkyscraper', + isLabelSyncedWithName: false, + }, + }); + + createdObjectMetadataId = data.createOneObject.id; + }); + + afterEach(async () => { + await deleteOneObjectMetadata({ + input: { idToDelete: createdObjectMetadataId }, + }); + }); + + test.each(successfulTestCases)( + 'Create $title', + async ({ context: { input, expectedOptions } }) => { + const { data, errors } = await createOneFieldMetadata({ + input: { + objectMetadataId: createdObjectMetadataId, + type: testedFieldMetadataType, + name: 'testField', + label: 'Test Field', + isLabelSyncedWithName: false, + ...input, + }, + gqlFields: ` + id + options + defaultValue + type + `, + }); + + expect(data).not.toBeNull(); + expect(data.createOneField).toBeDefined(); + expect(data.createOneField.type).toEqual(testedFieldMetadataType); + const createdOptions: + | FieldMetadataDefaultOption[] + | FieldMetadataComplexOption[] = data.createOneField.options; + + const optionsToCompare = expectedOptions ?? input.options; + + expect(errors).toBeUndefined(); + expect(createdOptions.length).toBe(optionsToCompare.length); + createdOptions.forEach((option) => expect(option.id).toBeDefined()); + expect(createdOptions).toMatchObject(optionsToCompare); + + if (isDefined(input.defaultValue)) { + expect(data.createOneField.defaultValue).toEqual(input.defaultValue); + } + }, + ); + + test.each(failingTestCases)( + 'Create $title', + async ({ context: { input } }) => { + const { data, errors } = await createOneFieldMetadata({ + input: { + objectMetadataId: createdObjectMetadataId, + type: testedFieldMetadataType, + name: 'testField', + label: 'Test Field', + isLabelSyncedWithName: false, + ...input, + }, + gqlFields: ` + id + options + defaultValue + `, + }); + + expect(data).toBeNull(); + expect(errors).toBeDefined(); + expect(errors).toMatchSnapshot(); + }, + ); + }, +); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases.ts new file mode 100644 index 000000000..61f6211a3 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases.ts @@ -0,0 +1,7 @@ +import { UpdateCreateFieldMetadataSelectTestCase } from 'test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case'; +import { SuccessfulAndFailingTestCases } from 'twenty-shared/testing'; + +export type FieldMetadataEnumSuccessfulAndFailingTestCases = + SuccessfulAndFailingTestCases< + UpdateCreateFieldMetadataSelectTestCase['context'] + >; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case.ts new file mode 100644 index 000000000..d9149e65c --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case.ts @@ -0,0 +1,10 @@ +import { CreateOneFieldFactoryInput } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util'; +import { EachTestingContext } from 'twenty-shared/testing'; + +import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; + +export type UpdateCreateFieldMetadataSelectTestCase = EachTestingContext<{ + input: Partial> & + Required>; + expectedOptions?: FieldMetadataComplexOption[]; +}>; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/update-enum-field-metadata-test-cases.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/update-enum-field-metadata-test-cases.ts new file mode 100644 index 000000000..e9d7b2ffe --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/update-enum-field-metadata-test-cases.ts @@ -0,0 +1,94 @@ +import { MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/multi-select-operation-agnostic-test-cases'; +import { SELECT_OPERATION_AGNOSTIC_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/select-operation-agnostic-test-cases'; +import { FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases'; + +import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; +import { EnumFieldMetadataUnionType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; + +export const UPDATE_ENUM_FIELD_METADATA_TEST_CASES: Partial< + Record< + EnumFieldMetadataUnionType, + FieldMetadataEnumSuccessfulAndFailingTestCases + > +> = { + MULTI_SELECT: { + failing: [ + ...MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES.failing, + { + title: 'should fail with unknown default value and no options', + context: { + input: { + defaultValue: ["'OPTION_42'"], + options: undefined as unknown as FieldMetadataComplexOption[], + }, + }, + }, + ], + successful: [ + ...MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES.successful, + { + title: 'should succeed with default value and no options', + context: { + input: { + defaultValue: ["'OPTION_2'"], + options: undefined as unknown as FieldMetadataComplexOption[], + }, + expectedOptions: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + { + label: 'Option 2', + value: 'OPTION_2', + color: 'green', + position: 2, + }, + ], + }, + }, + ], + }, + SELECT: { + failing: [ + ...SELECT_OPERATION_AGNOSTIC_TEST_CASES.failing, + { + title: 'should fail with unknown default value and no options', + context: { + input: { + defaultValue: "'OPTION_42'", + options: undefined as unknown as FieldMetadataComplexOption[], + }, + }, + }, + ], + successful: [ + ...SELECT_OPERATION_AGNOSTIC_TEST_CASES.successful, + { + title: 'should succeed with default value and no options', + context: { + input: { + defaultValue: "'OPTION_2'", + options: undefined as unknown as FieldMetadataComplexOption[], + }, + expectedOptions: [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + { + label: 'Option 2', + value: 'OPTION_2', + color: 'green', + position: 2, + }, + ], + }, + }, + ], + }, +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/update-one-enum-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/update-one-enum-field-metadata.integration-spec.ts new file mode 100644 index 000000000..d4fad2e46 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/update-one-enum-field-metadata.integration-spec.ts @@ -0,0 +1,228 @@ +import { UPDATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/update-enum-field-metadata-test-cases'; +import { CreateOneFieldFactoryInput } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util'; +import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; +import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; +import { + LISTING_NAME_PLURAL, + LISTING_NAME_SINGULAR, +} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant'; +import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-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 { isDefined } from 'twenty-shared/utils'; + +import { + FieldMetadataComplexOption, + FieldMetadataDefaultOption, +} from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; +import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; + +describe.each(fieldMetadataEnumTypes)( + 'Update field metadata %s tests suite', + (testedFieldMetadataType) => { + let createdObjectMetadataId: string; + const testCases = + UPDATE_ENUM_FIELD_METADATA_TEST_CASES[testedFieldMetadataType]; + + if (!isDefined(testCases)) { + return; + } + const { failing: failingTestCases, successful: successfulTestCases } = + testCases; + const initialOptions: CreateOneFieldFactoryInput['options'] = [ + { + label: 'Option 1', + value: 'OPTION_1', + color: 'green', + position: 1, + }, + { + label: 'Option 2', + value: 'OPTION_2', + color: 'green', + position: 2, + }, + ]; + + beforeEach(async () => { + const { data } = await createOneObjectMetadata({ + input: { + labelSingular: LISTING_NAME_SINGULAR, + labelPlural: LISTING_NAME_PLURAL, + nameSingular: LISTING_NAME_SINGULAR, + namePlural: LISTING_NAME_PLURAL, + icon: 'IconBuildingSkyscraper', + isLabelSyncedWithName: false, + }, + }); + + createdObjectMetadataId = data.createOneObject.id; + }); + + afterEach(async () => { + await deleteOneObjectMetadata({ + input: { idToDelete: createdObjectMetadataId }, + }); + }); + + it('Should update default value to null even if it was set before', async () => { + const { + data: { createOneField }, + } = await createOneFieldMetadata({ + input: { + objectMetadataId: createdObjectMetadataId, + type: testedFieldMetadataType, + name: 'testField', + label: 'Test Field', + isLabelSyncedWithName: false, + options: initialOptions, + }, + gqlFields: ` + id + type + `, + }); + + const createdFieldMetadata = createOneField.id; + const isMultiSelect = + testedFieldMetadataType === FieldMetadataType.MULTI_SELECT; + const rawDefaultValue = `'${initialOptions[0].value}'`; + const expectedDefaultValue = isMultiSelect + ? [rawDefaultValue] + : rawDefaultValue; + + const { data: firstUpdate } = await updateOneFieldMetadata({ + input: { + idToUpdate: createdFieldMetadata, + updatePayload: { + defaultValue: expectedDefaultValue, + }, + }, + gqlFields: ` + id + defaultValue + `, + }); + + expect(firstUpdate.updateOneField.defaultValue).toEqual( + expectedDefaultValue, + ); + + const updatedOptions = initialOptions.slice(1); + const { data: secondUpdate, errors } = await updateOneFieldMetadata({ + input: { + idToUpdate: createdFieldMetadata, + updatePayload: { + defaultValue: null, + options: updatedOptions, + }, + }, + gqlFields: ` + id + options + defaultValue + `, + }); + + expect(errors).toBeUndefined(); + expect(secondUpdate.updateOneField.defaultValue).toBeNull(); + expect(secondUpdate.updateOneField.options).toMatchObject(updatedOptions); + }); + + test.each(successfulTestCases)( + 'Update $title', + async ({ context: { input, expectedOptions } }) => { + const { + data: { createOneField }, + } = await createOneFieldMetadata({ + input: { + objectMetadataId: createdObjectMetadataId, + type: testedFieldMetadataType, + name: 'testField', + label: 'Test Field', + isLabelSyncedWithName: false, + options: initialOptions, + }, + gqlFields: ` + id + type + `, + }); + + const createdFieldMetadata = createOneField.id; + + const { ...updatePayload } = input; + + const { data, errors } = await updateOneFieldMetadata({ + input: { + idToUpdate: createdFieldMetadata, + updatePayload, + }, + gqlFields: ` + id + options + defaultValue + `, + }); + + expect(data.updateOneField).toBeDefined(); + const updatedOptions: + | FieldMetadataComplexOption[] + | FieldMetadataDefaultOption[] = data.updateOneField.options; + + expect(errors).toBeUndefined(); + updatedOptions.forEach((option) => expect(option.id).toBeDefined()); + + const optionsToCompare = expectedOptions ?? input.options; + + expect(updatedOptions.length).toBe(optionsToCompare.length); + expect(updatedOptions).toMatchObject(optionsToCompare); + + if (isDefined(input.defaultValue)) { + expect(data.updateOneField.defaultValue).toEqual(input.defaultValue); + } + }, + ); + + test.each(failingTestCases)( + 'Update $title', + async ({ context: { input } }) => { + const { + data: { createOneField }, + } = await createOneFieldMetadata({ + input: { + objectMetadataId: createdObjectMetadataId, + type: testedFieldMetadataType, + name: 'testField', + label: 'Test Field', + isLabelSyncedWithName: false, + options: initialOptions, + }, + gqlFields: ` + id + type + `, + }); + + const createdFieldMetadata = createOneField.id; + + const { ...updatePayload } = input; + + const { data, errors } = await updateOneFieldMetadata({ + input: { + idToUpdate: createdFieldMetadata, + updatePayload, + }, + gqlFields: ` + id + options + `, + }); + + expect(data).toBeNull(); + expect(errors).toBeDefined(); + expect(errors).toMatchSnapshot(); + }, + ); + }, +); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-create-one-field-metadata-select-tests-cases.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-create-one-field-metadata-select-tests-cases.ts deleted file mode 100644 index 35d23fc15..000000000 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-create-one-field-metadata-select-tests-cases.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { CreateOneFieldFactoryInput } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util'; -import { EachTestingContext } from 'twenty-shared/testing'; -import { v4 } from 'uuid'; - -import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; - -export type UpdateCreateFieldMetadataSelectTestCase = EachTestingContext<{ - input: Partial & - Required>; - expectedOptions?: FieldMetadataComplexOption[]; -}>; - -const successfulTestCases: UpdateCreateFieldMetadataSelectTestCase[] = [ - { - title: 'should succeed with provided option id', - context: { - input: { - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31', - }, - ], - }, - }, - }, - { - title: 'should succeed with valid default value', - context: { - input: { - defaultValue: "'OPTION_1'", - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31', - }, - ], - }, - }, - }, - { - title: 'should succeed with various options id', - context: { - input: { - options: Array.from({ length: 42 }, (_value, index) => { - const optionWithoutId: FieldMetadataComplexOption = { - label: `Option ${index}`, - value: `OPTION_${index}`, - color: 'green', - position: index, - }; - - if (index % 2 === 0) { - return { - ...optionWithoutId, - id: v4(), - }; - } - - return optionWithoutId; - }), - }, - }, - }, - { - title: 'should succeed without option id', - context: { - input: { - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - }, - ], - }, - }, - }, - { - title: 'should trim option values', - context: { - input: { - options: [ - { - label: ' Option 1 ', - value: ' OPTION_1 ', - color: 'green', - position: 1, - }, - ], - }, - expectedOptions: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - }, - ], - }, - }, - { - title: 'should succeed with null default value', - context: { - input: { - defaultValue: null, - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31', - }, - ], - }, - }, - }, -]; - -const basicFailingStringEdgeCaseInputs: { - label: string; - input: string | undefined | number | null; -}[] = [ - { input: ' ', label: 'only white spaces' }, - { input: '', label: 'empty string' }, - { input: null, label: 'null' }, - { input: 22222, label: 'not a string' }, - { input: 'a'.repeat(64), label: 'too long' }, -]; - -const stringFields: (keyof FieldMetadataComplexOption)[] = [ - 'id', - 'label', - 'value', -]; -const autoGeneratedStringFailingTestsCases: UpdateCreateFieldMetadataSelectTestCase[] = - stringFields.flatMap((field) => { - return basicFailingStringEdgeCaseInputs.map( - ({ input, label }) => ({ - title: `should fail with ${label} ${field}`, - context: { - input: { - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - [field]: input, - }, - ], - }, - }, - }), - ); - }); -const failingTestCases: UpdateCreateFieldMetadataSelectTestCase[] = [ - ...autoGeneratedStringFailingTestsCases, - { - title: 'should fail with invalid option id', - context: { - input: { - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - id: 'not a uuid', - }, - ], - }, - }, - }, - { - title: 'should fail with empty options', - context: { - input: { - options: [], - }, - }, - }, - { - title: 'should fail with invalid option value format', - context: { - input: { - options: [ - { - label: 'Option 1', - value: 'Option 1 and some other things, /', - color: 'green', - position: 1, - }, - ], - }, - }, - }, - { - title: 'should fail with comma in option label', - context: { - input: { - options: [ - { - label: 'Option ,1', - value: 'OPTION_1', - color: 'green', - position: 1, - }, - ], - }, - }, - }, - { - title: 'should fail with duplicated option values', - context: { - input: { - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 0, - }, - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - }, - ], - }, - }, - }, - { - title: 'should fail with duplicated option ids', - context: { - input: { - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - id: 'fd1f11fd-3f05-4a33-bddf-800c3412ce98', - }, - { - label: 'Option 2', - value: 'OPTION_2', - color: 'green', - position: 2, - id: 'fd1f11fd-3f05-4a33-bddf-800c3412ce98', - }, - ], - }, - }, - }, - { - title: 'should fail with duplicated option positions', - context: { - input: { - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - }, - { - label: 'Option 2', - value: 'OPTION_2', - color: 'green', - position: 1, - }, - ], - }, - }, - }, - { - title: 'should fail with duplicated trimmed option values', - context: { - input: { - options: [ - { - label: 'Option 1', - value: ' OPTION_1 ', - color: 'green', - position: 1, - }, - { - label: 'Option 2', - value: ' OPTION_1 ', - color: 'green', - position: 2, - }, - ], - }, - }, - }, - { - title: 'should fail with undefined option label', - context: { - input: { - options: [ - { - label: undefined as unknown as string, - value: 'OPTION_1', - color: 'green', - position: 1, - }, - ], - }, - }, - }, - { - title: 'should fail with an invalid default value', - context: { - input: { - defaultValue: 'invalid', - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - }, - ], - }, - }, - }, - { - title: 'should fail with an unknown default value', - context: { - input: { - defaultValue: "'OPTION_424242'", - options: [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - }, - ], - }, - }, - }, - { - title: 'should fail with undefined option value', - context: { - input: { - options: [ - { - label: 'Option 1', - value: undefined as unknown as string, - color: 'green', - position: 1, - }, - ], - }, - }, - }, -]; - -export const UPDATE_CREATE_ONE_FIELD_METADATA_SELECT_TEST_CASES = { - successfulTestCases, - failingTestCases, -}; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-select.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-select.integration-spec.ts deleted file mode 100644 index 004baca87..000000000 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-select.integration-spec.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { - UPDATE_CREATE_ONE_FIELD_METADATA_SELECT_TEST_CASES, - UpdateCreateFieldMetadataSelectTestCase, -} from 'test/integration/metadata/suites/field-metadata/update-create-one-field-metadata-select-tests-cases'; -import { CreateOneFieldFactoryInput } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util'; -import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; -import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; -import { - LISTING_NAME_PLURAL, - LISTING_NAME_SINGULAR, -} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant'; -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-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 { isDefined } from 'twenty-shared/utils'; - -import { - FieldMetadataComplexOption, - FieldMetadataDefaultOption, -} from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; - -const { failingTestCases, successfulTestCases } = - UPDATE_CREATE_ONE_FIELD_METADATA_SELECT_TEST_CASES; - -describe('Field metadata select update tests group', () => { - let createdObjectMetadataId: string; - let createdFieldMetadata: string; - const initialOptions: CreateOneFieldFactoryInput['options'] = [ - { - label: 'Option 1', - value: 'OPTION_1', - color: 'green', - position: 1, - }, - { - label: 'Option 2', - value: 'OPTION_2', - color: 'green', - position: 2, - }, - ]; - - beforeEach(async () => { - const { data } = await createOneObjectMetadata({ - input: { - labelSingular: LISTING_NAME_SINGULAR, - labelPlural: LISTING_NAME_PLURAL, - nameSingular: LISTING_NAME_SINGULAR, - namePlural: LISTING_NAME_PLURAL, - icon: 'IconBuildingSkyscraper', - isLabelSyncedWithName: false, - }, - }); - - createdObjectMetadataId = data.createOneObject.id; - - const { - data: { createOneField }, - } = await createOneFieldMetadata({ - input: { - objectMetadataId: createdObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'testField', - label: 'Test Field', - isLabelSyncedWithName: false, - options: initialOptions, - }, - gqlFields: ` - id - `, - }); - - createdFieldMetadata = createOneField.id; - }); - - afterEach(async () => { - await deleteOneObjectMetadata({ - input: { idToDelete: createdObjectMetadataId }, - }); - }); - - it('Should update default value to null even if it was set before', async () => { - const expectedDefaultValue = `'${initialOptions[0].value}'`; - const { data: firstUdpate } = await updateOneFieldMetadata({ - input: { - idToUpdate: createdFieldMetadata, - updatePayload: { - defaultValue: expectedDefaultValue, - }, - }, - gqlFields: ` - id - defaultValue - `, - }); - - expect(firstUdpate.updateOneField.defaultValue).toEqual( - expectedDefaultValue, - ); - - const updatedOptions = initialOptions.slice(1); - const { data: secondUpdate, errors } = await updateOneFieldMetadata({ - input: { - idToUpdate: createdFieldMetadata, - updatePayload: { - defaultValue: null, - options: updatedOptions, - }, - }, - gqlFields: ` - id - options - defaultValue - `, - }); - - expect(errors).toBeUndefined(); - expect(secondUpdate.updateOneField.defaultValue).toBeNull(); - expect(secondUpdate.updateOneField.options).toMatchObject(updatedOptions); - }); - - const updateSpecificSuccessfulTestCases: UpdateCreateFieldMetadataSelectTestCase[] = - [ - { - title: 'should succeed with default value and no options', - context: { - input: { - defaultValue: "'OPTION_2'", - options: undefined as unknown as FieldMetadataComplexOption[], - }, - expectedOptions: initialOptions, - }, - }, - ]; - - test.each([...successfulTestCases, ...updateSpecificSuccessfulTestCases])( - 'Update $title', - async ({ context: { input, expectedOptions } }) => { - const { data, errors } = await updateOneFieldMetadata({ - input: { - idToUpdate: createdFieldMetadata, - updatePayload: input, - }, - gqlFields: ` - id - options - defaultValue - `, - }); - - expect(data.updateOneField).toBeDefined(); - const updatedOptions: - | FieldMetadataComplexOption[] - | FieldMetadataDefaultOption[] = data.updateOneField.options; - - expect(errors).toBeUndefined(); - updatedOptions.forEach((option) => expect(option.id).toBeDefined()); - - const optionsToCompare = expectedOptions ?? input.options; - - expect(updatedOptions.length).toBe(optionsToCompare.length); - expect(updatedOptions).toMatchObject(optionsToCompare); - - if (isDefined(input.defaultValue)) { - expect(data.updateOneField.defaultValue).toEqual(input.defaultValue); - } - }, - ); - - const updateSpecificFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] = - [ - { - title: 'should fail with unknown default value and no options', - context: { - input: { - defaultValue: "'OPTION_42'", - options: undefined as unknown as FieldMetadataComplexOption[], - }, - }, - }, - ]; - - test.each([...updateSpecificFailingTestCases, ...failingTestCases])( - 'Update $title', - async ({ context: { input } }) => { - const { data, errors } = await updateOneFieldMetadata({ - input: { - idToUpdate: createdFieldMetadata, - updatePayload: input, - }, - gqlFields: ` - id - options - `, - }); - - expect(data).toBeNull(); - expect(errors).toBeDefined(); - expect(errors).toMatchSnapshot(); - }, - ); -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata.integration-spec.ts index 81a270fdb..6ff472a64 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata.integration-spec.ts @@ -164,7 +164,7 @@ describe('updateOne', () => { input: { idToUpdate: createdFieldMetadata.createOneField.id, updatePayload: { - defaultValue: 'OPTION_2', + defaultValue: "'OPTION_2'", }, }, gqlFields: ` @@ -182,7 +182,7 @@ describe('updateOne', () => { "extensions": { "code": "BAD_USER_INPUT", }, - "message": "Default value for existing options is invalid: OPTION_2", + "message": "Default value "'OPTION_2'" must be one of the option values", }, ] `); diff --git a/packages/twenty-shared/src/testing/index.ts b/packages/twenty-shared/src/testing/index.ts index 053ac0f70..5ca352654 100644 --- a/packages/twenty-shared/src/testing/index.ts +++ b/packages/twenty-shared/src/testing/index.ts @@ -8,3 +8,4 @@ */ export type { EachTestingContext } from './types/EachTestingContext.type'; +export type { SuccessfulAndFailingTestCases } from './types/SuccessfulAndFailingTestCases'; diff --git a/packages/twenty-shared/src/testing/types/SuccessfulAndFailingTestCases.ts b/packages/twenty-shared/src/testing/types/SuccessfulAndFailingTestCases.ts new file mode 100644 index 000000000..751dfb0a2 --- /dev/null +++ b/packages/twenty-shared/src/testing/types/SuccessfulAndFailingTestCases.ts @@ -0,0 +1,6 @@ +import { EachTestingContext } from '@/testing/types/EachTestingContext.type'; + +export type SuccessfulAndFailingTestCases = { + successful: EachTestingContext[]; + failing: EachTestingContext[]; +};