From 4fbdfb6abc0727fba160384c257b34c5a4e3da0d Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:05:09 +0200 Subject: [PATCH] Activate v2 default seed (#14660) ## Introduction After enabling flag by default got following errors: ```ts Test Suites: 48 failed, 1 skipped, 97 passed, 145 of 146 total Tests: 499 failed, 1 skipped, 644 passed, 1144 total Snapshots: 61 failed, 133 passed, 194 total Time: 363.226 s Ran all test suites. ``` ## From image ## To image There's a huge test bundle in the latest shard that we could split up ## Notes - Set as failing morph relation field rename as for the moment we do not handle relation field mutation - fixed the object update and creation validation adding label identifier field metadata id checks - and more Some integrations tests are still on the v1 ( they have before and after all disabling and re-enabling the flat ) but mainly we now have more coverage on the v2 than the v1. Mainly related records, uniqueness have to be migrated the v2 and so tests too --- .vscode/launch.json | 21 +- packages/twenty-server/scripts/setup-db.ts | 10 +- ...flat-view-properies-to-compare.constant.ts | 1 + .../view/resolvers/view.resolver.ts | 4 +- .../view/services/view-v2.service.ts | 13 +- .../engine/dataloaders/dataloader.service.ts | 1 - .../services/field-metadata.service-v2.ts | 4 +- .../compare-two-flat-field-metadata.util.ts | 19 +- ...validate-relation-creation-payload.util.ts | 2 +- .../flat-object-metadata-validator.service.ts | 62 +- ...-to-flat-field-metadatas-to-delete.util.ts | 2 +- ...morph-field-on-object-names-update.util.ts | 2 +- .../object-metadata-v2.service.ts | 16 +- .../core/utils/seed-feature-flags.util.ts | 2 +- .../utils/serialize-default-value-v2.util.ts | 3 +- ...ct-records-permissions.integration-spec.ts | 14 + ...ct-records-permissions.integration-spec.ts | 14 + ...ct-records-permissions.integration-spec.ts | 12 + ...ct-records-permissions.integration-spec.ts | 13 + .../page-layout-resolver.integration-spec.ts | 19 + ...ge-layout-tab-resolver.integration-spec.ts | 10 + ...-with-tabs-and-widgets.integration-spec.ts | 10 + ...layout-widget-resolver.integration-spec.ts | 10 + .../data-model.integration-spec.ts | 19 + .../roles.integration-spec.ts | 10 + .../suites/upsert/upsert.integration-spec.ts | 10 + .../delete-view.integration-spec.ts.snap | 12 + .../destroy-view.integration-spec.ts.snap | 12 + .../update-view.integration-spec.ts.snap | 12 + .../view/create-view.integration-spec.ts | 108 ++ .../view/delete-view.integration-spec.ts | 90 ++ .../view/destroy-view.integration-spec.ts | 95 ++ .../view/update-view.integration-spec.ts | 102 ++ .../utils/setup-view-field-test-v2.util.ts | 41 +- .../view-field-resolver.integration-spec.ts | 40 +- ...-filter-group-resolver.integration-spec.ts | 10 + .../view-filter-resolver.integration-spec.ts | 10 + .../view-group-resolver.integration-spec.ts | 10 + .../view/view-resolver.integration-spec.ts | 339 ------- .../view-sort-resolver.integration-spec.ts | 10 + .../utils/graphql-test-assertions.util.ts | 20 + ...ne-field-metadata.integration-spec.ts.snap | 45 + ...ne-field-metadata.integration-spec.ts.snap | 97 ++ ...ate-one-field-metadata.integration-spec.ts | 20 +- ...ete-one-field-metadata.integration-spec.ts | 38 +- ...ld-metadata-enum.integration-spec.ts.snap} | 461 --------- ...eld-metadata-enum.integration-spec.ts.snap | 462 +++++++++ ...field-metadata-enum-v2.integration-spec.ts | 38 +- ...ne-field-metadata-enum.integration-spec.ts | 74 -- ...ct-field-metadata-enum.integration-spec.ts | 85 ++ ...ct-field-metadata-enum.integration-spec.ts | 85 ++ ...enum-field-metadata-v2.integration-spec.ts | 44 +- ...ne-enum-field-metadata.integration-spec.ts | 42 +- ...field-metadata-enum-v2.integration-spec.ts | 38 +- ...ne-field-metadata-enum.integration-spec.ts | 30 +- ...enum-field-metadata-v2.integration-spec.ts | 40 +- ...ne-enum-field-metadata.integration-spec.ts | 31 +- ...etadata-morph-relation.integration-spec.ts | 49 + ...etadata-morph-relation.integration-spec.ts | 30 + ...data-morph-relation-v2.integration-spec.ts | 38 +- ...orph-relation-creation.integration-spec.ts | 18 + ...morph-relation-records.integration-spec.ts | 30 + ...data-morph-relation-v2.integration-spec.ts | 38 +- ...data-morph-relation-v2.integration-spec.ts | 39 +- ...e-field-metadata-phone.integration-spec.ts | 12 +- ...relation-creation.integration-spec.ts.snap | 928 ++++++++++++++++-- ...a-relation-update.integration-spec.ts.snap | 132 ++- ...ield-metadata-relation.integration-spec.ts | 150 +-- ...data-relation-creation.integration-spec.ts | 19 +- ...tadata-relation-update.integration-spec.ts | 15 +- ...tadata-relation-update.integration-spec.ts | 10 + ...-unique-field-metadata.integration-spec.ts | 29 + ...th-custom-unique-field.integration-spec.ts | 29 + ...-unique-field-metadata.integration-spec.ts | 29 + ...etadata-related-record.integration-spec.ts | 31 + ...ate-one-field-metadata.integration-spec.ts | 34 +- ...-on-object-creation-v2.integration-spec.ts | 19 - ...x-on-relation-field-v2.integration-spec.ts | 25 +- ...e-object-metadata.integration-spec.ts.snap | 125 ++- ...ate-object-metadata-v2.integration-spec.ts | 19 +- ...one-object-metadata-v2.integration-spec.ts | 21 - ...te-one-object-metadata.integration-spec.ts | 25 +- ...te-one-object-metadata.integration-spec.ts | 68 +- ...ta-with-morph-relation.integration-spec.ts | 52 +- ...with-morph-relation-v2.integration-spec.ts | 105 +- ...metadata-with-relation.integration-spec.ts | 103 +- .../rename-custom-object.integration-spec.ts | 47 +- ...te-one-object-metadata.integration-spec.ts | 29 +- .../utils/create-one-object-metadata.util.ts | 2 +- .../utils/delete-one-object-metadata.util.ts | 2 +- .../force-create-one-object-metadata.util.ts | 1 + .../types/perform-metadata-query.type.ts | 2 +- .../page-layout-tab.integration-spec.ts | 10 + .../page-layout-widget.integration-spec.ts | 10 + .../suites/page-layout.integration-spec.ts | 10 + .../suites/view-field.integration-spec.ts | 12 +- .../view-filter-group.integration-spec.ts | 14 +- .../suites/view-filter.integration-spec.ts | 10 + .../suites/view-group.integration-spec.ts | 15 +- .../rest/suites/view-sort.integration-spec.ts | 10 + .../rest/suites/view.integration-spec.ts | 10 + 101 files changed, 3601 insertions(+), 1649 deletions(-) create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/delete-view.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/destroy-view.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/update-view.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/create-view.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/delete-view.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/destroy-view.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/graphql/suites/view/update-view.integration-spec.ts delete mode 100644 packages/twenty-server/test/integration/graphql/suites/view/view-resolver.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata.integration-spec.ts.snap rename packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/{failing-create-one-field-metadata-enum.integration-spec.ts.snap => failing-create-one-multi-select-field-metadata-enum.integration-spec.ts.snap} (53%) create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-select-field-metadata-enum.integration-spec.ts.snap delete mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-field-metadata-enum.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-select-field-metadata-enum.integration-spec.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 378bd89a66..eb67626726 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,11 +8,7 @@ "runtimeVersion": "24.5", "request": "launch", "runtimeExecutable": "npx", - "runtimeArgs": [ - "nx", - "run", - "twenty-server:start" - ], + "runtimeArgs": ["nx", "run", "twenty-server:start"], "outputCapture": "std", "internalConsoleOptions": "openOnSessionStart", "console": "internalConsole", @@ -23,11 +19,7 @@ "type": "node", "request": "launch", "runtimeExecutable": "npx", - "runtimeArgs": [ - "nx", - "run", - "twenty-server:worker" - ], + "runtimeArgs": ["nx", "run", "twenty-server:worker"], "outputCapture": "std", "internalConsoleOptions": "openOnSessionStart", "console": "internalConsole", @@ -74,14 +66,15 @@ "--", "--config", "./jest-integration.config.ts", - "${relativeFile}" + "${relativeFile}", + "--testTimeout=0" ], "cwd": "${workspaceFolder}/packages/twenty-server", "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "env": { "NODE_ENV": "test" - }, + } }, { "type": "node", @@ -102,7 +95,7 @@ "internalConsoleOptions": "neverOpen", "env": { "NODE_ENV": "test" - }, + } } ] -} \ No newline at end of file +} diff --git a/packages/twenty-server/scripts/setup-db.ts b/packages/twenty-server/scripts/setup-db.ts index 82fe1cce85..0da6654802 100644 --- a/packages/twenty-server/scripts/setup-db.ts +++ b/packages/twenty-server/scripts/setup-db.ts @@ -27,9 +27,13 @@ rawDataSource ); await performQuery( - `CREATE OR REPLACE FUNCTION unaccent_immutable(text) RETURNS text AS $$ - SELECT public.unaccent($1) - $$ LANGUAGE sql IMMUTABLE;`, + `CREATE OR REPLACE FUNCTION public.unaccent_immutable(input text) + RETURNS text + LANGUAGE sql + IMMUTABLE +AS $$ +SELECT public.unaccent('public.unaccent'::regdictionary, input) +$$;`, 'create immutable unaccent wrapper function', ); diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-properies-to-compare.constant.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-properies-to-compare.constant.ts index 6c719d0b2c..7b8f41a33c 100644 --- a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-properies-to-compare.constant.ts +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-properies-to-compare.constant.ts @@ -3,5 +3,6 @@ import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat export const FLAT_VIEW_PROPERTIES_TO_COMPARE = [ 'key', + 'deletedAt', ...FLAT_VIEW_EDITABLE_PROPERTIES, ] as const satisfies (keyof FlatView)[]; diff --git a/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts b/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts index 1c7f992680..fb40c1b420 100644 --- a/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts @@ -14,6 +14,7 @@ import { isDefined } from 'twenty-shared/utils'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { type I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type'; import { CreateViewInput } from 'src/engine/core-modules/view/dtos/inputs/create-view.input'; import { UpdateViewInput } from 'src/engine/core-modules/view/dtos/inputs/update-view.input'; @@ -36,7 +37,6 @@ import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util'; -import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; @Resolver(() => ViewDTO) @UseFilters(ViewGraphqlApiExceptionFilter) @@ -161,7 +161,7 @@ export class ViewResolver { if (isWorkspaceMigrationV2Enabled) { return await this.viewV2Service.updateOne({ - updateViewInput: input, + updateViewInput: { ...input, id }, workspaceId: workspace.id, }); } diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view-v2.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view-v2.service.ts index 2e2001a422..a545464341 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view-v2.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view-v2.service.ts @@ -281,17 +281,6 @@ export class ViewV2Service { ); } - const { flatViewMaps: recomputedExistingFlatViewMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatEntities: ['flatViewMaps'], - }, - ); - - return findFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityId: destroyViewInput.id, - flatEntityMaps: recomputedExistingFlatViewMaps, - }); + return flatViewFromDestroyInput; } } diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts index 194469efea..7ff87df1e0 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts @@ -337,7 +337,6 @@ export class DataloaderService { >(async (dataLoaderParams: IndexFieldMetadataLoaderPayload[]) => { const workspaceId = dataLoaderParams[0].workspaceId; - // This computes the old cache :thinking: const { objectMetadataMaps } = await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps( { workspaceId }, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts index 37e7968a01..e87513d8e3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts @@ -381,9 +381,9 @@ export class FieldMetadataServiceV2 { return this.fieldMetadataRepository.find({ where: { - name: In( + id: In( allTranspiledTranspilationInputs.map( - ({ result: { flatFieldMetadatas } }) => flatFieldMetadatas[0].name, + ({ result: { flatFieldMetadatas } }) => flatFieldMetadatas[0].id, ), ), workspaceId, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util.ts index 0697e1fbae..1e02d06c3b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util.ts @@ -1,16 +1,13 @@ import diff from 'microdiff'; import { type FromTo } from 'twenty-shared/types'; -import { isDefined, parseJson } from 'twenty-shared/utils'; +import { parseJson } from 'twenty-shared/utils'; import { FLAT_FIELD_METADATA_JSONB_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-jsonb-properties.constant'; import { FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-properties-to-compare.constant'; -import { FLAT_FIELD_METADATA_RELATION_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-relation-properties-to-compare.constant'; import { type FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type'; -import { type FlatFieldMetadataRelationPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-relation-properties-to-compare.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { isFlatFieldMetadataJsonbProperty } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-jsonb-property.util'; import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util'; -import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util'; import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; @@ -38,20 +35,6 @@ export const compareTwoFlatFieldMetadata = ({ return true; } - // Remove below assertion when we authorize relation edition, see https://github.com/twentyhq/twenty/commit/39f6f3c4bb101272a9014e142a842d0801a3c33b - const isMorphOrRelationFieldType = - isDefined(fieldMetadata.type) && - isMorphOrRelationFieldMetadataType(fieldMetadata.type); - - if ( - isMorphOrRelationFieldType && - !FLAT_FIELD_METADATA_RELATION_PROPERTIES_TO_COMPARE.includes( - property as FlatFieldMetadataRelationPropertiesToCompare, - ) - ) { - return true; - } - if ( isStandardMetadata(fieldMetadata) && property !== 'standardOverrides' diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util.ts index 83dbd5a007..17361f9356 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util.ts @@ -42,7 +42,7 @@ export const validateRelationCreationPayload = async ({ status: 'fail', error: { code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - message: `Relation creation payload is invalid ${JSON.stringify(relationCreationPayload)}`, + message: `Relation creation payload is invalid`, userFriendlyMessage: t`Invalid relation creation payload`, value: relationCreationPayload, }, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service.ts index cf715d35a4..d84bd1ee07 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service.ts @@ -1,7 +1,10 @@ import { Injectable } from '@nestjs/common'; import { t } from '@lingui/core/macro'; -import { isDefined } from 'twenty-shared/utils'; +import { + isDefined, + isLabelIdentifierFieldMetadataTypes, +} from 'twenty-shared/utils'; import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service'; import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; @@ -73,6 +76,12 @@ export class FlatObjectMetadataValidatorService { ...validateFlatObjectMetadataIdentifiers(existingFlatObjectMetadata), ); + validationResult.errors.push( + ...this.validateFlatObjectMetadataLabelIdentifierFieldMetadataId({ + flatObjectMetadata: updatedFlatObjectMetadata, + }), + ); + return validationResult; } @@ -188,6 +197,12 @@ export class FlatObjectMetadataValidatorService { }); } + objectValidationResult.errors.push( + ...this.validateFlatObjectMetadataLabelIdentifierFieldMetadataId({ + flatObjectMetadata: flatObjectMetadataToValidate, + }), + ); + objectValidationResult.errors.push( ...this.validateFlatObjectMetadataNameAndLabels({ existingFlatObjectMetadataMaps, @@ -295,4 +310,49 @@ export class FlatObjectMetadataValidatorService { return errors; } + + private validateFlatObjectMetadataLabelIdentifierFieldMetadataId({ + flatObjectMetadata, + }: { + flatObjectMetadata: FlatObjectMetadata; + }) { + const errors: FlatObjectMetadataValidationError[] = []; + + if (!isDefined(flatObjectMetadata.labelIdentifierFieldMetadataId)) { + errors.push({ + code: ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD, + message: t`Label identifier field metadata is required`, + userFriendlyMessage: t`Label identifier field metadata is required`, + }); + } else { + const relatedFlatFieldMetadata = + flatObjectMetadata.flatFieldMetadatas.find( + (flatFieldMetadata) => + flatFieldMetadata.id === + flatObjectMetadata.labelIdentifierFieldMetadataId, + ); + + if (!relatedFlatFieldMetadata) { + errors.push({ + code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, + message: t`Label identifier field metadata not found in field metadata list`, + userFriendlyMessage: t`Label identifier field metadata not found in field metadata list`, + value: flatObjectMetadata.labelIdentifierFieldMetadataId, + }); + } else { + if ( + !isLabelIdentifierFieldMetadataTypes(relatedFlatFieldMetadata.type) + ) { + errors.push({ + code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, + message: t`Label identifier field metadata must be a TEXT or FULL_NAME field type`, + userFriendlyMessage: t`Label identifier field metadata must be a TEXT or FULL_NAME field type`, + value: relatedFlatFieldMetadata.type, + }); + } + } + } + + return errors; + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util.ts index bbd6d61924..3abb30b13b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util.ts @@ -68,7 +68,7 @@ export const fromDeleteObjectInputToFlatFieldMetadatasToDelete = ({ }, ); - // We should maintain a idsByObjectMetadataId maps in the index + // TODO We should maintain a idsByObjectMetadataId in the flatIndexMaps const flatIndexMetadataToDelete = Object.values( existingFlatIndexMaps.byId, ).filter( diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts index ff57035754..56ee9da28b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts @@ -37,7 +37,7 @@ type RenameRelatedMorphFieldOnObjectNamesUpdateArgs = FromTo< > & { existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; }; -// We should recompute each index here too ? YES TODO prastoin +// TODO We should recompute each index here too export const renameRelatedMorphFieldOnObjectNamesUpdate = ({ fromFlatObjectMetadata, existingFlatObjectMetadataMaps, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts index 36e10d1f4a..b419c9a2ec 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts @@ -76,18 +76,6 @@ export class ObjectMetadataServiceV2 { flatIndexMaps: existingFlatIndexMaps, }); - const impactedObjectMetadataIds = [ - ...new Set([ - optimisticallyUpdatedFlatObjectMetadata.id, - ...otherObjectFlatFieldMetadatas.map( - (flatFieldMetadata) => flatFieldMetadata.objectMetadataId, - ), - ]), - ]; - const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - objectMetadataIds: impactedObjectMetadataIds, - }); const toFlatObjectMetadataMaps = otherObjectFlatFieldMetadatas.reduce( (flatObjectMetadataMaps, flatFieldMetadata) => replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ @@ -96,7 +84,7 @@ export class ObjectMetadataServiceV2 { }), replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ flatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata, - flatObjectMetadataMaps: fromFlatObjectMetadataMaps, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, }), ); @@ -114,7 +102,7 @@ export class ObjectMetadataServiceV2 { { fromToAllFlatEntityMaps: { flatObjectMetadataMaps: { - from: fromFlatObjectMetadataMaps, + from: existingFlatObjectMetadataMaps, to: toFlatObjectMetadataMaps, }, flatIndexMaps: { diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts index c89c299a60..40b5dcee23 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts @@ -53,7 +53,7 @@ export const seedFeatureFlags = async ( { key: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, workspaceId: workspaceId, - value: false, + value: true, }, { key: FeatureFlagKey.IS_PAGE_LAYOUT_ENABLED, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/serialize-default-value-v2.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/serialize-default-value-v2.util.ts index 0a795107c6..d11bdbfdac 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/serialize-default-value-v2.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/serialize-default-value-v2.util.ts @@ -83,7 +83,8 @@ export const serializeDefaultValueV2 = ({ return `ARRAY[${arrayValues}]${castSuffix}[]`; } - return sanitizeAndAddCastPrefix(`'${JSON.stringify(defaultValue)}'`); // Won't work :thinking: at all will remove every brackets and so on + // Default value for objects won't work with sanitization here + return sanitizeAndAddCastPrefix(`'${JSON.stringify(defaultValue)}'`); } default: { throw new FieldMetadataException( diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-many-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-many-object-records-permissions.integration-spec.ts index 4b8c61c383..2863173154 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-many-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-many-object-records-permissions.integration-spec.ts @@ -5,11 +5,21 @@ import { createManyOperationFactory } from 'test/integration/graphql/utils/creat import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { deleteRecordsByIds } from 'test/integration/utils/delete-records-by-ids'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('createManyObjectRecordsPermissions', () => { + let createdPersonIds: string[] = []; + + afterEach(async () => { + if (createdPersonIds.length > 0) { + await deleteRecordsByIds('person', createdPersonIds); + createdPersonIds = []; + } + }); + it('should throw a permission error when user does not have permission (guest role)', async () => { const graphqlOperation = createManyOperationFactory({ objectMetadataSingularName: 'person', @@ -55,6 +65,8 @@ describe('createManyObjectRecordsPermissions', () => { const response = await makeGraphqlAPIRequest(graphqlOperation); + createdPersonIds.push(personId1, personId2); + expect(response.body.data).toBeDefined(); expect(response.body.data.createPeople).toBeDefined(); expect(response.body.data.createPeople).toHaveLength(2); @@ -88,6 +100,8 @@ describe('createManyObjectRecordsPermissions', () => { const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation); + createdPersonIds.push(personId1, personId2); + expect(response.body.data).toBeDefined(); expect(response.body.data.createPeople).toBeDefined(); expect(response.body.data.createPeople).toHaveLength(2); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-one-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-one-object-records-permissions.integration-spec.ts index f7a6d0cc62..99443c8066 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-one-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-one-object-records-permissions.integration-spec.ts @@ -5,11 +5,21 @@ import { createOneOperationFactory } from 'test/integration/graphql/utils/create import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { deleteRecordsByIds } from 'test/integration/utils/delete-records-by-ids'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('createOneObjectRecordsPermissions', () => { + let createdPersonIds: string[] = []; + + afterEach(async () => { + if (createdPersonIds.length > 0) { + await deleteRecordsByIds('person', createdPersonIds); + createdPersonIds = []; + } + }); + it('should throw a permission error when user does not have permission (guest role)', async () => { const graphqlOperation = createOneOperationFactory({ objectMetadataSingularName: 'person', @@ -41,6 +51,8 @@ describe('createOneObjectRecordsPermissions', () => { const response = await makeGraphqlAPIRequest(graphqlOperation); + createdPersonIds.push(personId); + expect(response.body.data).toBeDefined(); expect(response.body.data.createPerson).toBeDefined(); expect(response.body.data.createPerson.id).toBe(personId); @@ -58,6 +70,8 @@ describe('createOneObjectRecordsPermissions', () => { const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation); + createdPersonIds.push(personId); + expect(response.body.data).toBeDefined(); expect(response.body.data.createPerson).toBeDefined(); expect(response.body.data.createPerson.id).toBe(personId); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-many-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-many-object-records-permissions.integration-spec.ts index c30a4d68be..97cc5e6b49 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-many-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-many-object-records-permissions.integration-spec.ts @@ -6,11 +6,21 @@ import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delet import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { deleteRecordsByIds } from 'test/integration/utils/delete-records-by-ids'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('deleteManyObjectRecordsPermissions', () => { + let createdPersonIds: string[] = []; + + afterEach(async () => { + if (createdPersonIds.length > 0) { + await deleteRecordsByIds('person', createdPersonIds); + createdPersonIds = []; + } + }); + it('should throw a permission error when user does not have permission (guest role)', async () => { const graphqlOperation = deleteManyOperationFactory({ objectMetadataSingularName: 'person', @@ -52,6 +62,7 @@ describe('deleteManyObjectRecordsPermissions', () => { }); await makeGraphqlAPIRequest(createGraphqlOperation); + createdPersonIds.push(personId1, personId2); const deleteGraphqlOperation = deleteManyOperationFactory({ objectMetadataSingularName: 'person', @@ -96,6 +107,7 @@ describe('deleteManyObjectRecordsPermissions', () => { }); await makeGraphqlAPIRequest(createGraphqlOperation); + createdPersonIds.push(personId1, personId2); const deleteGraphqlOperation = deleteManyOperationFactory({ objectMetadataSingularName: 'person', diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/update-many-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/update-many-object-records-permissions.integration-spec.ts index 751ae41046..45293adee2 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/update-many-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/update-many-object-records-permissions.integration-spec.ts @@ -6,11 +6,21 @@ import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/ import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; import { updateManyOperationFactory } from 'test/integration/graphql/utils/update-many-operation-factory.util'; +import { deleteRecordsByIds } from 'test/integration/utils/delete-records-by-ids'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('updateManyObjectRecordsPermissions', () => { + let createdPersonIds: string[] = []; + + afterEach(async () => { + if (createdPersonIds.length > 0) { + await deleteRecordsByIds('person', createdPersonIds); + createdPersonIds = []; + } + }); + it('should throw a permission error when user does not have permission (guest role)', async () => { const personId1 = randomUUID(); const personId2 = randomUUID(); @@ -29,6 +39,7 @@ describe('updateManyObjectRecordsPermissions', () => { }); await makeGraphqlAPIRequest(createGraphqlOperation); + createdPersonIds.push(personId1, personId2); const updateGraphqlOperation = updateManyOperationFactory({ objectMetadataSingularName: 'person', @@ -74,6 +85,7 @@ describe('updateManyObjectRecordsPermissions', () => { }); await makeGraphqlAPIRequest(createGraphqlOperation); + createdPersonIds.push(personId1, personId2); const updateGraphqlOperation = updateManyOperationFactory({ objectMetadataSingularName: 'person', @@ -118,6 +130,7 @@ describe('updateManyObjectRecordsPermissions', () => { }); await makeGraphqlAPIRequest(createGraphqlOperation); + createdPersonIds.push(personId1, personId2); const updateGraphqlOperation = updateManyOperationFactory({ objectMetadataSingularName: 'person', diff --git a/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-resolver.integration-spec.ts index 6e42181afd..59d3ab2172 100644 --- a/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-resolver.integration-spec.ts @@ -15,6 +15,7 @@ import { restorePageLayoutOperationFactory } from 'test/integration/graphql/util import { updatePageLayoutOperationFactory } from 'test/integration/graphql/utils/update-page-layout-operation-factory.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { assertPageLayoutStructure, cleanupPageLayoutRecords, @@ -49,6 +50,15 @@ describe('Page Layout Resolver', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); @@ -122,6 +132,15 @@ describe('Page Layout Resolver', () => { objectMetadataId: testObjectMetadataId, }); + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadata2Id, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: objectMetadata2Id }, }); diff --git a/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-tab-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-tab-resolver.integration-spec.ts index 0c029d1b59..4d5f91fd61 100644 --- a/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-tab-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-tab-resolver.integration-spec.ts @@ -22,6 +22,7 @@ import { restorePageLayoutTabOperationFactory } from 'test/integration/graphql/u import { updatePageLayoutTabOperationFactory } from 'test/integration/graphql/utils/update-page-layout-tab-operation-factory.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { assertPageLayoutTabStructure } from 'test/integration/utils/page-layout-tab-test.util'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -63,6 +64,15 @@ describe('Page Layout Tab Resolver', () => { afterAll(async () => { await cleanupPageLayoutRecordsWithGraphQL(); + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-update-with-tabs-and-widgets.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-update-with-tabs-and-widgets.integration-spec.ts index 746cde5d01..022ca36014 100644 --- a/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-update-with-tabs-and-widgets.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-update-with-tabs-and-widgets.integration-spec.ts @@ -14,6 +14,7 @@ import { cleanupPageLayoutRecords, } from 'test/integration/utils/page-layout-test.util'; import { cleanupPageLayoutWidgetRecords } from 'test/integration/utils/page-layout-widget-test.util'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { type PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity'; @@ -53,6 +54,15 @@ describe('Page Layout Update With Tabs And Widgets Integration', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-widget-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-widget-resolver.integration-spec.ts index 900d8f58af..1b3d7635a9 100644 --- a/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-widget-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-widget-resolver.integration-spec.ts @@ -23,6 +23,7 @@ import { restorePageLayoutWidgetOperationFactory } from 'test/integration/graphq import { updatePageLayoutWidgetOperationFactory } from 'test/integration/graphql/utils/update-page-layout-widget-operation-factory.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { assertPageLayoutWidgetStructure } from 'test/integration/utils/page-layout-widget-test.util'; import { isDefined } from 'twenty-shared/utils'; @@ -76,6 +77,15 @@ describe('Page Layout Widget Resolver', () => { afterAll(async () => { await cleanupPageLayoutRecordsWithGraphQL(); + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/data-model.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/data-model.integration-spec.ts index b1101819ed..dd347c273b 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/data-model.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/data-model.integration-spec.ts @@ -7,6 +7,7 @@ import { createOneObjectMetadata } from 'test/integration/metadata/suites/object import { deleteOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-query-factory.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-query-factory.util'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeMetadataAPIRequestWithMemberRole } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-member-role.util'; import { FieldMetadataType } from 'twenty-shared/types'; @@ -43,6 +44,15 @@ describe('datamodel permissions', () => { testFieldId = createdFieldData.createOneField.id; }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: listingObjectId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: listingObjectId }, }); @@ -184,6 +194,15 @@ describe('datamodel permissions', () => { listingObjectId = data.createOneObject.id; }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: listingObjectId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: listingObjectId }, }); diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts index f746aab40c..b325daa703 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts @@ -2,6 +2,7 @@ import request from 'supertest'; import { deleteOneRoleOperationFactory } from 'test/integration/graphql/utils/delete-one-role-operation-factory.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { fieldTextMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -466,6 +467,15 @@ describe('roles permissions', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: listingObjectId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: listingObjectId }, diff --git a/packages/twenty-server/test/integration/graphql/suites/upsert/upsert.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/upsert/upsert.integration-spec.ts index 6919f9c917..3f0623e844 100644 --- a/packages/twenty-server/test/integration/graphql/suites/upsert/upsert.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/upsert/upsert.integration-spec.ts @@ -3,6 +3,7 @@ import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graph import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { FieldMetadataType } from 'twenty-shared/types'; const createRecordsQuery = gql` @@ -88,6 +89,15 @@ describe('upsert (createMany with upsert:true)', () => { }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/delete-view.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/delete-view.integration-spec.ts.snap new file mode 100644 index 0000000000..6e09cd66a3 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/delete-view.integration-spec.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Delete core view should throw an error when deleting non-existent view 1`] = ` +{ + "extensions": { + "code": "NOT_FOUND", + "userFriendlyMessage": "An error occurred.", + }, + "message": "View to delete not found", + "name": "NotFoundError", +} +`; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/destroy-view.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/destroy-view.integration-spec.ts.snap new file mode 100644 index 0000000000..89608048b6 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/destroy-view.integration-spec.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Destroy core view should throw an error when destroying non-existent view 1`] = ` +{ + "extensions": { + "code": "NOT_FOUND", + "userFriendlyMessage": "An error occurred.", + }, + "message": "View to destroy not found", + "name": "NotFoundError", +} +`; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/update-view.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/update-view.integration-spec.ts.snap new file mode 100644 index 0000000000..64677f8882 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/__snapshots__/update-view.integration-spec.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Update core view should throw error when updating non-existent view 1`] = ` +{ + "extensions": { + "code": "NOT_FOUND", + "userFriendlyMessage": "An error occurred.", + }, + "message": "View to update not found", + "name": "NotFoundError", +} +`; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/create-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/create-view.integration-spec.ts new file mode 100644 index 0000000000..4adb0dbb15 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/create-view.integration-spec.ts @@ -0,0 +1,108 @@ +import { assertGraphQLSuccessfulResponse } from 'test/integration/graphql/utils/graphql-test-assertions.util'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { + assertViewStructure, + cleanupViewRecords, +} from 'test/integration/utils/view-test.util'; +import { createViewOperationFactory } from 'test/integration/graphql/utils/create-view-operation-factory.util'; + +import { ViewOpenRecordIn } from 'src/engine/core-modules/view/enums/view-open-record-in'; +import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; + +describe('Create core view', () => { + let testObjectMetadataId: string; + + beforeAll(async () => { + const { + data: { + createOneObject: { id: objectMetadataId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'myViewTestObject', + namePlural: 'myViewTestObjects', + labelSingular: 'My View Test Object', + labelPlural: 'My View Test Objects', + icon: 'Icon123', + }, + }); + + testObjectMetadataId = objectMetadataId; + }); + + afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: testObjectMetadataId }, + }); + await cleanupViewRecords(); + }); + + beforeEach(async () => { + await cleanupViewRecords(); + }); + + it('should create a new view with all properties', async () => { + const input = { + name: 'Kanban View', + objectMetadataId: testObjectMetadataId, + icon: 'IconDeal', + type: ViewType.KANBAN, + key: null, + position: 1, + isCompact: true, + openRecordIn: ViewOpenRecordIn.SIDE_PANEL, + }; + + const operation = createViewOperationFactory({ data: input }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLSuccessfulResponse(response); + assertViewStructure(response.body.data.createCoreView, { + name: input.name, + objectMetadataId: input.objectMetadataId, + type: input.type, + key: null, + icon: input.icon, + position: input.position, + isCompact: input.isCompact, + openRecordIn: input.openRecordIn, + }); + }); + + it('should create a view with minimum required fields', async () => { + const input = { + name: 'Minimal View', + objectMetadataId: testObjectMetadataId, + icon: 'IconList', + }; + + const operation = createViewOperationFactory({ data: input }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLSuccessfulResponse(response); + assertViewStructure(response.body.data.createCoreView, { + name: input.name, + objectMetadataId: input.objectMetadataId, + icon: input.icon, + type: ViewType.TABLE, + key: null, + position: 0, + isCompact: false, + openRecordIn: ViewOpenRecordIn.SIDE_PANEL, + }); + }); +}); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/delete-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/delete-view.integration-spec.ts new file mode 100644 index 0000000000..c5e24628f4 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/delete-view.integration-spec.ts @@ -0,0 +1,90 @@ +import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; +import { deleteViewOperationFactory } from 'test/integration/graphql/utils/delete-view-operation-factory.util'; +import { findViewOperationFactory } from 'test/integration/graphql/utils/find-view-operation-factory.util'; +import { + assertGraphQLErrorResponseWithSnapshot, + assertGraphQLSuccessfulResponse, +} from 'test/integration/graphql/utils/graphql-test-assertions.util'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { cleanupViewRecords } from 'test/integration/utils/view-test.util'; +import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util'; + +describe('Delete core view', () => { + let testObjectMetadataId: string; + + beforeAll(async () => { + const { + data: { + createOneObject: { id: objectMetadataId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'myViewTestObject', + namePlural: 'myViewTestObjects', + labelSingular: 'My View Test Object', + labelPlural: 'My View Test Objects', + icon: 'Icon123', + }, + }); + + testObjectMetadataId = objectMetadataId; + }); + + afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: testObjectMetadataId }, + }); + await cleanupViewRecords(); + }); + + beforeEach(async () => { + await cleanupViewRecords(); + }); + + it('should delete an existing view', async () => { + const { + data: { createCoreView: view }, + } = await createOneCoreView({ + input: { + name: 'View to Delete', + objectMetadataId: testObjectMetadataId, + icon: '123Icon', + }, + expectToFail: false, + }); + + const deleteOperation = deleteViewOperationFactory({ viewId: view.id }); + const deleteResponse = await makeGraphqlAPIRequest(deleteOperation); + + assertGraphQLSuccessfulResponse(deleteResponse); + expect(deleteResponse.body.data.deleteCoreView).toBe(true); + + const getOperation = findViewOperationFactory({ viewId: view.id }); + const getResponse = await makeGraphqlAPIRequest(getOperation); + + expect(getResponse.body.data.getCoreView).toBeNull(); + }); + + it('should throw an error when deleting non-existent view', async () => { + const operation = deleteViewOperationFactory({ + viewId: TEST_NOT_EXISTING_VIEW_ID, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponseWithSnapshot(response); + }); +}); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/destroy-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/destroy-view.integration-spec.ts new file mode 100644 index 0000000000..49e04426e1 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/destroy-view.integration-spec.ts @@ -0,0 +1,95 @@ +import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; +import { deleteViewOperationFactory } from 'test/integration/graphql/utils/delete-view-operation-factory.util'; +import { destroyViewOperationFactory } from 'test/integration/graphql/utils/destroy-view-operation-factory.util'; +import { findViewOperationFactory } from 'test/integration/graphql/utils/find-view-operation-factory.util'; +import { + assertGraphQLErrorResponseWithSnapshot, + assertGraphQLSuccessfulResponse, +} from 'test/integration/graphql/utils/graphql-test-assertions.util'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { cleanupViewRecords } from 'test/integration/utils/view-test.util'; +import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util'; + +describe('Destroy core view', () => { + let testObjectMetadataId: string; + + beforeAll(async () => { + const { + data: { + createOneObject: { id: objectMetadataId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'myViewTestObject', + namePlural: 'myViewTestObjects', + labelSingular: 'My View Test Object', + labelPlural: 'My View Test Objects', + icon: 'Icon123', + }, + }); + + testObjectMetadataId = objectMetadataId; + }); + + afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: testObjectMetadataId }, + }); + await cleanupViewRecords(); + }); + + beforeEach(async () => { + await cleanupViewRecords(); + }); + + it('should destroy an existing view', async () => { + const { + data: { createCoreView: view }, + } = await createOneCoreView({ + input: { + icon: '123Icon', + name: 'View to Destroy', + objectMetadataId: testObjectMetadataId, + }, + expectToFail: false, + }); + const deleteOperation = deleteViewOperationFactory({ viewId: view.id }); + const deleteResponse = await makeGraphqlAPIRequest(deleteOperation); + + assertGraphQLSuccessfulResponse(deleteResponse); + + const destroyOperation = destroyViewOperationFactory({ viewId: view.id }); + const destroyResponse = await makeGraphqlAPIRequest(destroyOperation); + + assertGraphQLSuccessfulResponse(destroyResponse); + expect(destroyResponse.body.data.destroyCoreView).toBe(true); + + const getOperation = findViewOperationFactory({ viewId: view.id }); + const getResponse = await makeGraphqlAPIRequest(getOperation); + + expect(getResponse.body.data.getCoreView).toBeNull(); + }); + + it('should throw an error when destroying non-existent view', async () => { + const operation = destroyViewOperationFactory({ + viewId: TEST_NOT_EXISTING_VIEW_ID, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponseWithSnapshot(response); + }); +}); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/update-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/update-view.integration-spec.ts new file mode 100644 index 0000000000..33fbbeb956 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/view/update-view.integration-spec.ts @@ -0,0 +1,102 @@ +import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; +import { + assertGraphQLErrorResponseWithSnapshot, + assertGraphQLSuccessfulResponse, +} from 'test/integration/graphql/utils/graphql-test-assertions.util'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { cleanupViewRecords } from 'test/integration/utils/view-test.util'; +import { updateViewOperationFactory } from 'test/integration/graphql/utils/update-view-operation-factory.util'; +import { updateViewData } from 'test/integration/graphql/utils/view-data-factory.util'; +import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util'; + +import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; + +describe('Update core view', () => { + let testObjectMetadataId: string; + + beforeAll(async () => { + const { + data: { + createOneObject: { id: objectMetadataId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'myViewTestObject', + namePlural: 'myViewTestObjects', + labelSingular: 'My View Test Object', + labelPlural: 'My View Test Objects', + icon: 'Icon123', + }, + }); + + testObjectMetadataId = objectMetadataId; + }); + + afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: testObjectMetadataId }, + }); + await cleanupViewRecords(); + }); + + beforeEach(async () => { + await cleanupViewRecords(); + }); + + it('should update an existing view', async () => { + const { + data: { createCoreView: view }, + } = await createOneCoreView({ + input: { + icon: '123Icon', + name: 'Original View', + type: ViewType.TABLE, + isCompact: false, + objectMetadataId: testObjectMetadataId, + }, + expectToFail: false, + }); + + const updateInput = updateViewData({ + name: 'Updated View', + type: ViewType.KANBAN, + isCompact: true, + }); + + const operation = updateViewOperationFactory({ + viewId: view.id, + data: updateInput, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLSuccessfulResponse(response); + expect(response.body.data.updateCoreView).toMatchObject({ + id: view.id, + ...updateInput, + }); + }); + + it('should throw error when updating non-existent view', async () => { + const operation = updateViewOperationFactory({ + viewId: TEST_NOT_EXISTING_VIEW_ID, + data: { name: 'Non-existent View' }, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponseWithSnapshot(response); + }); +}); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/utils/setup-view-field-test-v2.util.ts b/packages/twenty-server/test/integration/graphql/suites/view/utils/setup-view-field-test-v2.util.ts index bd1005cccb..0fa0bf368b 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/utils/setup-view-field-test-v2.util.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/utils/setup-view-field-test-v2.util.ts @@ -2,11 +2,8 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-m 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; -import { FieldMetadataType } from 'twenty-shared/types'; import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util'; - -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FieldMetadataType } from 'twenty-shared/types'; export type ViewFieldTestSetup = { testViewId: string; @@ -15,12 +12,6 @@ export type ViewFieldTestSetup = { }; export const setupViewFieldTestV2 = async (): Promise => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - const { data: { createOneObject: { id: objectMetadataId }, @@ -80,24 +71,16 @@ export const setupViewFieldTestV2 = async (): Promise => { export const cleanupViewFieldTestV2 = async ( objectMetadataId: string, ): Promise => { - try { - await updateOneObjectMetadata({ - input: { - idToUpdate: objectMetadataId, - updatePayload: { - isActive: false, - }, + await updateOneObjectMetadata({ + input: { + idToUpdate: objectMetadataId, + updatePayload: { + isActive: false, }, - }); - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: objectMetadataId }, - }); - } finally { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - } + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: objectMetadataId }, + }); }; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-field-resolver.integration-spec.ts index e6e9c9e4a0..57fb148fc6 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field-resolver.integration-spec.ts @@ -13,28 +13,39 @@ import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-g import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; +import { createOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/create-one-core-view-field.util'; +import { deleteOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-core-view-field.util'; +import { destroyOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-core-view-field.util'; +import { updateOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/update-one-core-view-field.util'; import { assertViewFieldStructure, cleanupViewRecords, } from 'test/integration/utils/view-test.util'; import { FieldMetadataType } from 'twenty-shared/types'; -import { createOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/create-one-core-view-field.util'; -import { deleteOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-core-view-field.util'; -import { destroyOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-core-view-field.util'; -import { updateOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/update-one-core-view-field.util'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { type CreateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/create-view-field.input'; import { generateViewFieldExceptionMessage, ViewFieldExceptionMessageKey, } from 'src/engine/core-modules/view/exceptions/view-field.exception'; -import { type CreateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/create-view-field.input'; -import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; describe('View Field Resolver', () => { let testViewId: string; let testObjectMetadataId: string; let testFieldMetadataId: string; + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + beforeAll(async () => { const { data: { @@ -80,6 +91,15 @@ describe('View Field Resolver', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: testObjectMetadataId }, @@ -87,6 +107,14 @@ describe('View Field Resolver', () => { await cleanupViewRecords(); }); + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + beforeEach(async () => { await cleanupViewRecords(); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts index 2b27f5c479..53bf06bdb6 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts @@ -17,6 +17,7 @@ import { import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { assertViewFilterGroupStructure, cleanupViewRecords, @@ -54,6 +55,15 @@ describe('View Filter Group Resolver', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: testObjectMetadataId }, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts index c8775d78ae..9e47c3f4b5 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts @@ -14,6 +14,7 @@ import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-g import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { assertViewFilterStructure, cleanupViewRecords, @@ -69,6 +70,15 @@ describe('View Filter Resolver', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: testObjectMetadataId }, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts index 5f3e836f48..bb6f3e7865 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts @@ -17,6 +17,7 @@ import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-g import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { assertViewGroupStructure, cleanupViewRecords, @@ -71,6 +72,15 @@ describe('View Group Resolver', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: testObjectMetadataId }, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-resolver.integration-spec.ts deleted file mode 100644 index 8168c47a04..0000000000 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-resolver.integration-spec.ts +++ /dev/null @@ -1,339 +0,0 @@ -import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; -import { createViewOperationFactory } from 'test/integration/graphql/utils/create-view-operation-factory.util'; -import { deleteViewOperationFactory } from 'test/integration/graphql/utils/delete-view-operation-factory.util'; -import { destroyViewOperationFactory } from 'test/integration/graphql/utils/destroy-view-operation-factory.util'; -import { findViewOperationFactory } from 'test/integration/graphql/utils/find-view-operation-factory.util'; -import { findViewsOperationFactory } from 'test/integration/graphql/utils/find-views-operation-factory.util'; -import { - assertGraphQLErrorResponse, - assertGraphQLSuccessfulResponse, -} from 'test/integration/graphql/utils/graphql-test-assertions.util'; -import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; -import { updateViewOperationFactory } from 'test/integration/graphql/utils/update-view-operation-factory.util'; -import { - createViewData, - updateViewData, -} from 'test/integration/graphql/utils/view-data-factory.util'; -import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.util'; -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 { - assertViewStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; - -import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { ViewOpenRecordIn } from 'src/engine/core-modules/view/enums/view-open-record-in'; -import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; -import { - ViewExceptionMessageKey, - generateViewExceptionMessage, -} from 'src/engine/core-modules/view/exceptions/view.exception'; - -describe('View Resolver', () => { - let testObjectMetadataId: string; - - beforeAll(async () => { - const { - data: { - createOneObject: { id: objectMetadataId }, - }, - } = await createOneObjectMetadata({ - expectToFail: false, - input: { - nameSingular: 'myViewTestObject', - namePlural: 'myViewTestObjects', - labelSingular: 'My View Test Object', - labelPlural: 'My View Test Objects', - icon: 'Icon123', - }, - }); - - testObjectMetadataId = objectMetadataId; - }); - - afterAll(async () => { - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: testObjectMetadataId }, - }); - await cleanupViewRecords(); - }); - - beforeEach(async () => { - await cleanupViewRecords(); - }); - - describe('getCoreViews', () => { - it('should return empty array when no views exist', async () => { - const operation = findViewsOperationFactory(); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLSuccessfulResponse(response); - expect(response.body.data.getCoreViews).toEqual([]); - }); - - it('should return all views for workspace when no objectMetadataId provided', async () => { - const viewName = 'Test View'; - - await createTestViewWithGraphQL({ - name: viewName, - objectMetadataId: testObjectMetadataId, - }); - - const viewData = createViewData({ - name: viewName, - objectMetadataId: testObjectMetadataId, - }); - - const operation = findViewsOperationFactory(); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLSuccessfulResponse(response); - expect(response.body.data.getCoreViews).toHaveLength(1); - expect(response.body.data.getCoreViews[0]).toMatchObject(viewData); - }); - - it('should filter views by objectMetadataId when provided', async () => { - const object1ViewName = 'View for Object 1'; - const object2ViewName = 'View for Object 2'; - - const { - data: { - createOneObject: { id: objectMetadata2Id }, - }, - } = await createOneObjectMetadata({ - input: { - nameSingular: 'myTestObject2', - namePlural: 'myTestObjects2', - labelSingular: 'My Test Object 2', - labelPlural: 'My Test Objects 2', - icon: 'Icon123', - }, - }); - - await Promise.all([ - createTestViewWithGraphQL({ - name: object1ViewName, - objectMetadataId: testObjectMetadataId, - }), - createTestViewWithGraphQL({ - name: object2ViewName, - objectMetadataId: objectMetadata2Id, - }), - ]); - - const operation = findViewsOperationFactory({ - objectMetadataId: testObjectMetadataId, - }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLSuccessfulResponse(response); - expect(response.body.data.getCoreViews).toHaveLength(1); - expect(response.body.data.getCoreViews[0]).toMatchObject({ - name: object1ViewName, - }); - - await deleteOneObjectMetadata({ - input: { idToDelete: objectMetadata2Id }, - }); - }); - }); - - describe('getCoreView', () => { - it('should return null when view does not exist', async () => { - const operation = findViewOperationFactory({ - viewId: TEST_NOT_EXISTING_VIEW_ID, - }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLSuccessfulResponse(response); - expect(response.body.data.getCoreView).toBeNull(); - }); - - it('should return view when it exists', async () => { - const viewName = 'Test View for Get'; - - const view = await createTestViewWithGraphQL({ - name: viewName, - objectMetadataId: testObjectMetadataId, - }); - - const operation = findViewOperationFactory({ viewId: view.id }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLSuccessfulResponse(response); - assertViewStructure(response.body.data.getCoreView, { - id: view.id, - name: viewName, - objectMetadataId: testObjectMetadataId, - }); - }); - }); - - describe('createCoreView', () => { - it('should create a new view with all properties', async () => { - const input = { - name: 'Kanban View', - objectMetadataId: testObjectMetadataId, - icon: 'IconDeal', - type: ViewType.KANBAN, - key: null, - position: 1, - isCompact: true, - openRecordIn: ViewOpenRecordIn.SIDE_PANEL, - }; - - const operation = createViewOperationFactory({ data: input }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLSuccessfulResponse(response); - assertViewStructure(response.body.data.createCoreView, { - name: input.name, - objectMetadataId: input.objectMetadataId, - type: input.type, - key: null, - icon: input.icon, - position: input.position, - isCompact: input.isCompact, - openRecordIn: input.openRecordIn, - }); - }); - - it('should create a view with minimum required fields', async () => { - const input = { - name: 'Minimal View', - objectMetadataId: testObjectMetadataId, - icon: 'IconList', - }; - - const operation = createViewOperationFactory({ data: input }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLSuccessfulResponse(response); - assertViewStructure(response.body.data.createCoreView, { - name: input.name, - objectMetadataId: input.objectMetadataId, - icon: input.icon, - type: ViewType.TABLE, - key: null, - position: 0, - isCompact: false, - openRecordIn: ViewOpenRecordIn.SIDE_PANEL, - }); - }); - }); - - describe('updateCoreView', () => { - it('should update an existing view', async () => { - const view = await createTestViewWithGraphQL({ - name: 'Original View', - type: ViewType.TABLE, - isCompact: false, - objectMetadataId: testObjectMetadataId, - }); - - const updateInput = updateViewData({ - name: 'Updated View', - type: ViewType.KANBAN, - isCompact: true, - }); - - const operation = updateViewOperationFactory({ - viewId: view.id, - data: updateInput, - }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLSuccessfulResponse(response); - expect(response.body.data.updateCoreView).toMatchObject({ - id: view.id, - ...updateInput, - }); - }); - - it('should throw error when updating non-existent view', async () => { - const operation = updateViewOperationFactory({ - viewId: TEST_NOT_EXISTING_VIEW_ID, - data: { name: 'Non-existent View' }, - }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLErrorResponse( - response, - ErrorCode.NOT_FOUND, - generateViewExceptionMessage( - ViewExceptionMessageKey.VIEW_NOT_FOUND, - TEST_NOT_EXISTING_VIEW_ID, - ), - ); - }); - }); - - describe('deleteCoreView', () => { - it('should delete an existing view', async () => { - const view = await createTestViewWithGraphQL({ - name: 'View to Delete', - objectMetadataId: testObjectMetadataId, - }); - - const deleteOperation = deleteViewOperationFactory({ viewId: view.id }); - const deleteResponse = await makeGraphqlAPIRequest(deleteOperation); - - assertGraphQLSuccessfulResponse(deleteResponse); - expect(deleteResponse.body.data.deleteCoreView).toBe(true); - - const getOperation = findViewOperationFactory({ viewId: view.id }); - const getResponse = await makeGraphqlAPIRequest(getOperation); - - expect(getResponse.body.data.getCoreView).toBeNull(); - }); - - it('should throw an error when deleting non-existent view', async () => { - const operation = deleteViewOperationFactory({ - viewId: TEST_NOT_EXISTING_VIEW_ID, - }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLErrorResponse( - response, - ErrorCode.NOT_FOUND, - generateViewExceptionMessage( - ViewExceptionMessageKey.VIEW_NOT_FOUND, - TEST_NOT_EXISTING_VIEW_ID, - ), - ); - }); - }); - - describe('destroyCoreView', () => { - it('should destroy an existing view', async () => { - const view = await createTestViewWithGraphQL({ - name: 'View to Destroy', - objectMetadataId: testObjectMetadataId, - }); - - const destroyOperation = destroyViewOperationFactory({ viewId: view.id }); - const destroyResponse = await makeGraphqlAPIRequest(destroyOperation); - - assertGraphQLSuccessfulResponse(destroyResponse); - expect(destroyResponse.body.data.destroyCoreView).toBe(true); - }); - - it('should throw an error when destroying non-existent view', async () => { - const operation = destroyViewOperationFactory({ - viewId: TEST_NOT_EXISTING_VIEW_ID, - }); - const response = await makeGraphqlAPIRequest(operation); - - assertGraphQLErrorResponse( - response, - ErrorCode.NOT_FOUND, - generateViewExceptionMessage( - ViewExceptionMessageKey.VIEW_NOT_FOUND, - TEST_NOT_EXISTING_VIEW_ID, - ), - ); - }); - }); -}); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts index a7b65520ea..695f9002da 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts @@ -17,6 +17,7 @@ import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-g import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { assertViewSortStructure, cleanupViewRecords, @@ -72,6 +73,15 @@ describe('View Sort Resolver', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: testObjectMetadataId }, diff --git a/packages/twenty-server/test/integration/graphql/utils/graphql-test-assertions.util.ts b/packages/twenty-server/test/integration/graphql/utils/graphql-test-assertions.util.ts index a1fb797bf9..3405c71146 100644 --- a/packages/twenty-server/test/integration/graphql/utils/graphql-test-assertions.util.ts +++ b/packages/twenty-server/test/integration/graphql/utils/graphql-test-assertions.util.ts @@ -1,3 +1,6 @@ +import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; + import { type BaseGraphQLError, type ErrorCode, @@ -43,3 +46,20 @@ export const assertGraphQLErrorResponse = >( expect(response.body.errors[0].message).toBe(expectedErrorMessage); } }; + +export const assertGraphQLErrorResponseWithSnapshot = < + T extends Record, +>( + response: GraphQLResponse, +) => { + expect(response.status).toBe(200); + jestExpectToBeDefined(response.body.errors); + expect(response.body.errors).toHaveLength(1); + + const firstError = response.body.errors[0]; + + jestExpectToBeDefined(firstError); + expect(firstError).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(firstError), + ); +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata.integration-spec.ts.snap new file mode 100644 index 0000000000..46a1331838 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata.integration-spec.ts.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`createOne FieldMetadataService name/label sync should return an error when name and label are not synced but isLabelSyncedWithName is true 1`] = ` +{ + "extensions": { + "code": "BAD_USER_INPUT", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "NAME_NOT_SYNCED_WITH_LABEL", + "message": "Name is not synced with label", + "userFriendlyMessage": "Field name is not synced with field label", + "value": "Different Label", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "testField", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata.integration-spec.ts.snap new file mode 100644 index 0000000000..0ddca359d2 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata.integration-spec.ts.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`updateOne failing update should not update a field name if it is not synced correctly with label and labelSync is true 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_MUTATION_NOT_ALLOWED", + "message": "Name is not synced with label.", + "userFriendlyMessage": "Updated field name is not synced with label", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "newName", + "objectMetadataId": Any, + }, + "type": "update_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating field", + "name": "GraphQLError", + }, +] +`; + +exports[`updateOne failing update should throw if the field name is not available because of other field with the same name 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "NOT_AVAILABLE", + "message": "Name "otherTestName" is not available as it is already used by another field", + "userFriendlyMessage": "Name "otherTestName" is not available as it is already used by another field", + "value": "otherTestName", + }, + { + "code": "FIELD_MUTATION_NOT_ALLOWED", + "message": "Name is not synced with label.", + "userFriendlyMessage": "Updated field name is not synced with label", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "otherTestName", + "objectMetadataId": Any, + }, + "type": "update_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating field", + "name": "GraphQLError", + }, +] +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/create-one-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/create-one-field-metadata.integration-spec.ts index 2910923463..d846ff3a19 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/create-one-field-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/create-one-field-metadata.integration-spec.ts @@ -1,6 +1,8 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { FieldMetadataType } from 'twenty-shared/types'; describe('createOne FieldMetadataService name/label sync', () => { @@ -24,6 +26,15 @@ describe('createOne FieldMetadataService name/label sync', () => { createdObjectMetadataId = objectMetadataId; }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataId }, }); @@ -89,7 +100,7 @@ describe('createOne FieldMetadataService name/label sync', () => { }; // Act - const { errors } = await createOneFieldMetadata({ + const response = await createOneFieldMetadata({ input: createFieldInput, gqlFields: ` id @@ -101,8 +112,11 @@ describe('createOne FieldMetadataService name/label sync', () => { }); // Assert - expect(errors[0].message).toBe( - 'Name is not synced with label. Expected name: "differentLabel", got testField', + expect(response.errors.length).toBe(1); + const [firstError] = response.errors; + + expect(firstError).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(firstError), ); }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/delete-one-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/delete-one-field-metadata.integration-spec.ts index efff621f37..6153bb8d14 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/delete-one-field-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/delete-one-field-metadata.integration-spec.ts @@ -1,12 +1,9 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { createTestViewWithRestApi, findViewByIdWithRestApi, @@ -14,11 +11,28 @@ import { import { generateRecordName } from 'test/integration/utils/generate-record-name'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; describe('deleteOne', () => { + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); describe('Kanban aggregate operation', () => { let listingObjectId = ''; let testFieldId = ''; @@ -28,8 +42,8 @@ describe('deleteOne', () => { const { data } = await createOneObjectMetadata({ expectToFail: false, input: { - nameSingular: LISTING_NAME_SINGULAR, - namePlural: LISTING_NAME_PLURAL, + nameSingular: 'listingv2', + namePlural: 'listingsv2', labelSingular: 'Listing', labelPlural: 'Listings', icon: 'IconBuildingSkyscraper', @@ -61,11 +75,21 @@ describe('deleteOne', () => { viewId = createdView.id; }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: listingObjectId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: listingObjectId }, }); }); + it('should reset kanban aggregate operation when deleting a field used as kanbanAggregateOperationFieldMetadataId', async () => { const viewThatShouldBeUpdated = await findViewByIdWithRestApi(viewId); @@ -79,6 +103,7 @@ describe('deleteOne', () => { expect(viewThatShouldBeUpdated.kanbanAggregateOperation).toBe('MAX'); await updateOneFieldMetadata({ + expectToFail: false, input: { idToUpdate: testFieldId, updatePayload: { isActive: false }, @@ -90,6 +115,7 @@ describe('deleteOne', () => { }); const { data } = await deleteOneFieldMetadata({ + expectToFail: false, input: { idToDelete: testFieldId }, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-field-metadata-enum.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts.snap similarity index 53% rename from packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-field-metadata-enum.integration-spec.ts.snap rename to packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts.snap index 1b79330c3f..12a6b005ac 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-field-metadata-enum.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts.snap @@ -516,464 +516,3 @@ exports[`Failing create field metadata MULTI_SELECT tests suite Create should fa }, ] `; - -exports[`Failing create field metadata SELECT tests suite Create should fail with an invalid default value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Default value should be as quoted string", - }, - "message": "Default value should be as quoted string", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with an unknown default value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Default value "'OPTION_424242'" must be one of the option values", - }, - "message": "Default value "'OPTION_424242'" must be one of the option values", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with comma in option label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Label must not contain a comma", - }, - "message": "Label must not contain a comma", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with duplicated option ids 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Duplicated option id", - }, - "message": "Duplicated option id", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with duplicated option positions 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Duplicated option position", - }, - "message": "Duplicated option position", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with duplicated option values 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Duplicated option value", - }, - "message": "Duplicated option value", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with duplicated trimmed option values 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Duplicated option value", - }, - "message": "Duplicated option value", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with empty options 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", - }, - "message": "Options are required for enum fields", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with empty string defaultValue 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Default value should be as quoted string", - }, - "message": "Default value should be as quoted string", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with empty string id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option id is invalid", - }, - "message": "Option id is invalid", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with empty string label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option label "" is beneath 1 character", - }, - "message": "Option label "" is beneath 1 character", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with empty string value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option value "" is beneath 1 character", - }, - "message": "Option value "" is beneath 1 character", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with invalid option id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option id is invalid", - }, - "message": "Option id is invalid", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with invalid option value format 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", - }, - "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with not a string defaultValue 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", - }, - "message": "Default value for multi-select must be a stringified array", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with not a string id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option id is invalid", - }, - "message": "Option id is invalid", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with not a string label 1`] = ` -[ - { - "extensions": { - "code": "INTERNAL_SERVER_ERROR", - "exceptionEventId": "mocked-exception-id", - "userFriendlyMessage": "An error occurred.", - }, - "message": "label.includes is not a function", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with not a string value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "22222"", - }, - "message": "Value must be in UPPER_CASE and follow snake_case "22222"", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with null id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option id is required", - }, - "message": "Option id is required", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with null label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option label is required", - }, - "message": "Option label is required", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with null options 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", - }, - "message": "Options are required for enum fields", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with null value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option value is required", - }, - "message": "Option value is required", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with only white spaces defaultValue 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Default value should be as quoted string", - }, - "message": "Default value should be as quoted string", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with only white spaces id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option id is invalid", - }, - "message": "Option id is invalid", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with only white spaces label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option label "" is beneath 1 character", - }, - "message": "Option label "" is beneath 1 character", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with only white spaces value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option value "" is beneath 1 character", - }, - "message": "Option value "" is beneath 1 character", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with too long defaultValue 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Default value should be as quoted string", - }, - "message": "Default value should be as quoted string", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with too long id 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option id is invalid", - }, - "message": "Option id is invalid", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with too long label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option label exceeds 63 characters", - }, - "message": "Option label exceeds 63 characters", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with too long value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option value exceeds 63 characters", - }, - "message": "Option value exceeds 63 characters", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with undefined option label 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option label is required", - }, - "message": "Option label is required", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with undefined option value 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Option value is required", - }, - "message": "Option value is required", - "name": "UserInputError", - }, -] -`; - -exports[`Failing create field metadata SELECT tests suite Create should fail with undefined options 1`] = ` -[ - { - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", - }, - "message": "Options are required for enum fields", - "name": "UserInputError", - }, -] -`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-select-field-metadata-enum.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-select-field-metadata-enum.integration-spec.ts.snap new file mode 100644 index 0000000000..09ead2527a --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-select-field-metadata-enum.integration-spec.ts.snap @@ -0,0 +1,462 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Failing create field metadata RATING tests suite Create should fail with an invalid default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Default value should be as quoted string", + }, + "message": "Default value should be as quoted string", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with an unknown default value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Default value "'OPTION_424242'" must be one of the option values", + }, + "message": "Default value "'OPTION_424242'" must be one of the option values", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with comma in option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Label must not contain a comma", + }, + "message": "Label must not contain a comma", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with duplicated option ids 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Duplicated option id", + }, + "message": "Duplicated option id", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with duplicated option positions 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Duplicated option position", + }, + "message": "Duplicated option position", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with duplicated option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Duplicated option value", + }, + "message": "Duplicated option value", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with duplicated trimmed option values 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Duplicated option value", + }, + "message": "Duplicated option value", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with empty options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "An error occurred.", + }, + "message": "Options are required for enum fields", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with empty string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Default value should be as quoted string", + }, + "message": "Default value should be as quoted string", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with empty string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option id is invalid", + }, + "message": "Option id is invalid", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with empty string label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option label "" is beneath 1 character", + }, + "message": "Option label "" is beneath 1 character", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with empty string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option value "" is beneath 1 character", + }, + "message": "Option value "" is beneath 1 character", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with invalid option id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option id is invalid", + }, + "message": "Option id is invalid", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with invalid option value format 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "Option 1 and some other things, /"", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with not a string defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "An error occurred.", + }, + "message": "Default value for multi-select must be a stringified array", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with not a string id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option id is invalid", + }, + "message": "Option id is invalid", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with not a string label 1`] = ` +[ + { + "extensions": { + "code": "INTERNAL_SERVER_ERROR", + "exceptionEventId": "mocked-exception-id", + "userFriendlyMessage": "An error occurred.", + }, + "message": "label.includes is not a function", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with not a string value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Value must be in UPPER_CASE and follow snake_case "22222"", + }, + "message": "Value must be in UPPER_CASE and follow snake_case "22222"", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with null id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option id is required", + }, + "message": "Option id is required", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with null label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option label is required", + }, + "message": "Option label is required", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with null options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "An error occurred.", + }, + "message": "Options are required for enum fields", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with null value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option value is required", + }, + "message": "Option value is required", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with only white spaces defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Default value should be as quoted string", + }, + "message": "Default value should be as quoted string", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with only white spaces id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option id is invalid", + }, + "message": "Option id is invalid", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with only white spaces label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option label "" is beneath 1 character", + }, + "message": "Option label "" is beneath 1 character", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with only white spaces value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option value "" is beneath 1 character", + }, + "message": "Option value "" is beneath 1 character", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with too long defaultValue 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Default value should be as quoted string", + }, + "message": "Default value should be as quoted string", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with too long id 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option id is invalid", + }, + "message": "Option id is invalid", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with too long label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option label exceeds 63 characters", + }, + "message": "Option label exceeds 63 characters", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with too long value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option value exceeds 63 characters", + }, + "message": "Option value exceeds 63 characters", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with undefined option label 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option label is required", + }, + "message": "Option label is required", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with undefined option value 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "Option value is required", + }, + "message": "Option value is required", + "name": "UserInputError", + }, +] +`; + +exports[`Failing create field metadata RATING tests suite Create should fail with undefined options 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "subCode": "INVALID_FIELD_INPUT", + "userFriendlyMessage": "An error occurred.", + }, + "message": "Options are required for enum fields", + "name": "UserInputError", + }, +] +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-field-metadata-enum-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-field-metadata-enum-v2.integration-spec.ts index 09dc0193be..cdd10e7c1a 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-field-metadata-enum-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-field-metadata-enum-v2.integration-spec.ts @@ -4,12 +4,10 @@ import { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-me 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { eachTestingContextFilter } from 'twenty-shared/testing'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; describe.each(fieldMetadataEnumTypes)( @@ -25,12 +23,6 @@ describe.each(fieldMetadataEnumTypes)( const { failing: failingTestCases } = testCases; beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - const { labelPlural, description, @@ -55,25 +47,17 @@ describe.each(fieldMetadataEnumTypes)( }); afterAll(async () => { - try { - await updateOneObjectMetadata({ - expectToFail: false, - input: { - idToUpdate: createdObjectMetadataId, - updatePayload: { isActive: false }, - }, - }); - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: createdObjectMetadataId }, - }); - } finally { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - } + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectMetadataId }, + }); }); test.each(eachTestingContextFilter(failingTestCases))( diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-field-metadata-enum.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-field-metadata-enum.integration-spec.ts deleted file mode 100644 index 97c0d803e3..0000000000 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-field-metadata-enum.integration-spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { CREATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/create-enum-field-metadata-test-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 { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; -import { isDefined } from 'twenty-shared/utils'; -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; - -import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; - -describe.each(fieldMetadataEnumTypes)( - 'Failing 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 } = testCases; - - beforeAll(async () => { - const { data } = await createOneObjectMetadata({ - expectToFail: false, - 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; - }); - - afterAll(async () => { - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: createdObjectMetadataId }, - }); - }); - - test.each(failingTestCases)( - 'Create $title', - async ({ context: { input } }) => { - const { data, errors } = await createOneFieldMetadata({ - expectToFail: true, - 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/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts new file mode 100644 index 0000000000..f27f810c81 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-multi-select-field-metadata-enum.integration-spec.ts @@ -0,0 +1,85 @@ +import { CREATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/create-enum-field-metadata-test-cases'; +import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; +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 { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; + +describe('Failing create field metadata MULTI_SELECT tests suite', () => { + let createdObjectMetadataId: string; + const testCases = + CREATE_ENUM_FIELD_METADATA_TEST_CASES[FieldMetadataType.MULTI_SELECT]; + + if (!isDefined(testCases)) { + return; + } + const { failing: failingTestCases } = testCases; + + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + beforeAll(async () => { + const { data } = await createOneObjectMetadata({ + expectToFail: false, + input: { + labelSingular: 'Enum testing v1', + labelPlural: 'Enums testings v1', + nameSingular: 'enumTestingV1', + namePlural: 'enumsTestingsV1', + icon: 'IconBuildingSkyscraper', + isLabelSyncedWithName: false, + }, + }); + + createdObjectMetadataId = data.createOneObject.id; + }); + + afterAll(async () => { + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectMetadataId }, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + + test.each(failingTestCases)( + 'Create $title', + async ({ context: { input } }) => { + const { data, errors } = await createOneFieldMetadata({ + expectToFail: true, + input: { + objectMetadataId: createdObjectMetadataId, + type: FieldMetadataType.MULTI_SELECT, + 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/failing-create-one-select-field-metadata-enum.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-select-field-metadata-enum.integration-spec.ts new file mode 100644 index 0000000000..655904b245 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-create-one-select-field-metadata-enum.integration-spec.ts @@ -0,0 +1,85 @@ +import { CREATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/create-enum-field-metadata-test-cases'; +import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; +import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; +import { isDefined } from 'twenty-shared/utils'; +import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { FieldMetadataType } from 'twenty-shared/types'; + +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; + +describe('Failing create field metadata RATING tests suite', () => { + let createdObjectMetadataId: string; + const testCases = + CREATE_ENUM_FIELD_METADATA_TEST_CASES[FieldMetadataType.SELECT]; + + if (!isDefined(testCases)) { + return; + } + const { failing: failingTestCases } = testCases; + + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + beforeAll(async () => { + const { data } = await createOneObjectMetadata({ + expectToFail: false, + input: { + labelSingular: 'Enum testing v1', + labelPlural: 'Enums testings v1', + nameSingular: 'enumTestingV1', + namePlural: 'enumsTestingsV1', + icon: 'IconBuildingSkyscraper', + isLabelSyncedWithName: false, + }, + }); + + createdObjectMetadataId = data.createOneObject.id; + }); + + afterAll(async () => { + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectMetadataId }, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + + test.each(failingTestCases)( + 'Create $title', + async ({ context: { input } }) => { + const { data, errors } = await createOneFieldMetadata({ + expectToFail: true, + input: { + objectMetadataId: createdObjectMetadataId, + type: FieldMetadataType.SELECT, + 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/failing-update-one-enum-field-metadata-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-update-one-enum-field-metadata-v2.integration-spec.ts index bf713c1af2..aeedcc98f3 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-update-one-enum-field-metadata-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-update-one-enum-field-metadata-v2.integration-spec.ts @@ -6,12 +6,10 @@ import { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-me 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { eachTestingContextFilter } from 'twenty-shared/testing'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; describe.each(fieldMetadataEnumTypes)( @@ -42,12 +40,6 @@ describe.each(fieldMetadataEnumTypes)( ]; beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - const { labelPlural, description, @@ -90,30 +82,22 @@ describe.each(fieldMetadataEnumTypes)( }); afterAll(async () => { - try { - await updateOneObjectMetadata({ - expectToFail: false, - input: { - idToUpdate: createdObjectMetadataId, - updatePayload: { - isActive: false, - }, + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { + isActive: false, }, - }); + }, + }); - await deleteOneObjectMetadata({ - input: { - idToDelete: createdObjectMetadataId, - }, - expectToFail: false, - }); - } finally { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - } + await deleteOneObjectMetadata({ + input: { + idToDelete: createdObjectMetadataId, + }, + expectToFail: false, + }); }); test.each(eachTestingContextFilter(failingTestCases))( diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-update-one-enum-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-update-one-enum-field-metadata.integration-spec.ts index 315141a3bf..f0498c349a 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-update-one-enum-field-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/failing-update-one-enum-field-metadata.integration-spec.ts @@ -2,15 +2,12 @@ import { UPDATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata import { type 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; describe.each(fieldMetadataEnumTypes)( @@ -25,6 +22,15 @@ describe.each(fieldMetadataEnumTypes)( return; } const { failing: failingTestCases } = testCases; + + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + const initialOptions: CreateOneFieldFactoryInput['options'] = [ { label: 'Option 1', @@ -44,10 +50,10 @@ describe.each(fieldMetadataEnumTypes)( const { data } = await createOneObjectMetadata({ expectToFail: false, input: { - labelSingular: LISTING_NAME_SINGULAR, - labelPlural: LISTING_NAME_PLURAL, - nameSingular: LISTING_NAME_SINGULAR, - namePlural: LISTING_NAME_PLURAL, + labelSingular: 'failingUpdateEnumField', + labelPlural: 'failingUpdateEnumFields', + nameSingular: 'failingUpdateEnumField', + namePlural: 'failingUpdateEnumFields', icon: 'IconBuildingSkyscraper', isLabelSyncedWithName: false, }, @@ -76,16 +82,6 @@ describe.each(fieldMetadataEnumTypes)( }); afterAll(async () => { - await updateOneObjectMetadata({ - expectToFail: false, - input: { - idToUpdate: createdObjectMetadataId, - updatePayload: { - isActive: false, - }, - }, - }); - await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataId, @@ -94,6 +90,14 @@ describe.each(fieldMetadataEnumTypes)( }); }); + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + test.each(failingTestCases)( 'Update $title', async ({ context: { input } }) => { diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-create-one-field-metadata-enum-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-create-one-field-metadata-enum-v2.integration-spec.ts index 0dc13c6bdf..555d272a07 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-create-one-field-metadata-enum-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-create-one-field-metadata-enum-v2.integration-spec.ts @@ -6,11 +6,9 @@ import { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-me 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { eachTestingContextFilter } from 'twenty-shared/testing'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; describe.each(fieldMetadataEnumTypes)( @@ -27,12 +25,6 @@ describe.each(fieldMetadataEnumTypes)( const { successful: successfulTestCases } = testCases; beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - const { labelPlural, description, @@ -57,25 +49,17 @@ describe.each(fieldMetadataEnumTypes)( }); afterAll(async () => { - try { - await updateOneObjectMetadata({ - expectToFail: false, - input: { - idToUpdate: createdObjectMetadataId, - updatePayload: { isActive: false }, - }, - }); - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: createdObjectMetadataId }, - }); - } finally { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - } + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectMetadataId }, + }); }); beforeEach(() => { diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-create-one-field-metadata-enum.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-create-one-field-metadata-enum.integration-spec.ts index a28f3ae115..665a5a268b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-create-one-field-metadata-enum.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-create-one-field-metadata-enum.integration-spec.ts @@ -1,14 +1,12 @@ import { CREATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/create-enum-field-metadata-test-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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; describe.each(fieldMetadataEnumTypes)( @@ -23,14 +21,30 @@ describe.each(fieldMetadataEnumTypes)( } const { successful: successfulTestCases } = testCases; + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + beforeEach(async () => { const { data } = await createOneObjectMetadata({ expectToFail: false, input: { - labelSingular: LISTING_NAME_SINGULAR, - labelPlural: LISTING_NAME_PLURAL, - nameSingular: LISTING_NAME_SINGULAR, - namePlural: LISTING_NAME_PLURAL, + labelSingular: 'successCreateEnumField', + labelPlural: 'successCreateEnumFields', + nameSingular: 'successCreateEnumField', + namePlural: 'successCreateEnumFields', icon: 'IconBuildingSkyscraper', isLabelSyncedWithName: false, }, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-update-one-enum-field-metadata-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-update-one-enum-field-metadata-v2.integration-spec.ts index 961a0a2bd3..7132a23eef 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-update-one-enum-field-metadata-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-update-one-enum-field-metadata-v2.integration-spec.ts @@ -7,17 +7,15 @@ import { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-me 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { eachTestingContextFilter } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; import { type FieldMetadataComplexOption, type 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)( 'Successful update field metadata %s tests suite v2', @@ -47,12 +45,6 @@ describe.each(fieldMetadataEnumTypes)( ]; beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - const { labelPlural, description, @@ -76,25 +68,17 @@ describe.each(fieldMetadataEnumTypes)( }); afterAll(async () => { - try { - await updateOneObjectMetadata({ - expectToFail: false, - input: { - idToUpdate: createdObjectMetadataId, - updatePayload: { isActive: false }, - }, - }); - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: createdObjectMetadataId }, - }); - } finally { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - } + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectMetadataId }, + }); }); beforeEach(async () => { diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-update-one-enum-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-update-one-enum-field-metadata.integration-spec.ts index 2c0691d8a9..50d7b0a994 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-update-one-enum-field-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/successful-update-one-enum-field-metadata.integration-spec.ts @@ -2,16 +2,14 @@ import { UPDATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata import { type 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type FieldMetadataComplexOption, type FieldMetadataDefaultOption, @@ -30,6 +28,23 @@ describe.each(fieldMetadataEnumTypes)( return; } const { successful: successfulTestCases } = testCases; + + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + const initialOptions: CreateOneFieldFactoryInput['options'] = [ { label: 'Option 1', @@ -49,10 +64,10 @@ describe.each(fieldMetadataEnumTypes)( const { data } = await createOneObjectMetadata({ expectToFail: false, input: { - labelSingular: LISTING_NAME_SINGULAR, - labelPlural: LISTING_NAME_PLURAL, - nameSingular: LISTING_NAME_SINGULAR, - namePlural: LISTING_NAME_PLURAL, + labelSingular: 'successUpdateEnumField', + labelPlural: 'successUpdateEnumFields', + nameSingular: 'successUpdateEnumField', + namePlural: 'successUpdateEnumFields', icon: 'IconBuildingSkyscraper', isLabelSyncedWithName: false, }, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/create-one-field-metadata-morph-relation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/create-one-field-metadata-morph-relation.integration-spec.ts index 19b1494b15..0a3b919c6b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/create-one-field-metadata-morph-relation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/create-one-field-metadata-morph-relation.integration-spec.ts @@ -1,6 +1,8 @@ import { createMorphRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { eachTestingContextFilter, type EachTestingContext, @@ -9,11 +11,29 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; + describe('createOne FieldMetadataService morph relation fields', () => { let createdObjectMetadataPersonId = ''; let createdObjectMetadataOpportunityId = ''; let createdObjectMetadataCompanyId = ''; + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + beforeEach(async () => { const { data: { @@ -67,14 +87,43 @@ describe('createOne FieldMetadataService morph relation fields', () => { createdObjectMetadataOpportunityId = objectMetadataOpportunityId; }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataPersonId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: createdObjectMetadataPersonId }, }); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataOpportunityId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: createdObjectMetadataOpportunityId }, }); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataCompanyId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: createdObjectMetadataCompanyId }, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/delete-one-field-metadata-morph-relation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/delete-one-field-metadata-morph-relation.integration-spec.ts index 29885f8771..417fc3817c 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/delete-one-field-metadata-morph-relation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/delete-one-field-metadata-morph-relation.integration-spec.ts @@ -3,6 +3,7 @@ import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-m import { createMorphRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { type EachTestingContext } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; @@ -63,12 +64,41 @@ describe('deleteOne FieldMetadataService morph relation fields', () => { createdObjectMetadataOpportunityId = objectMetadataOpportunityId; }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataPersonId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataPersonId }, }); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataOpportunityId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataOpportunityId }, }); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataCompanyId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataCompanyId }, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-create-one-field-metadata-morph-relation-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-create-one-field-metadata-morph-relation-v2.integration-spec.ts index 41f1d8fed8..6a8062253a 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-create-one-field-metadata-morph-relation-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-create-one-field-metadata-morph-relation-v2.integration-spec.ts @@ -3,7 +3,6 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-m 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { eachTestingContextFilter, @@ -13,9 +12,7 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; -import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util'; type FailingTestCases = EachTestingContext< (args: { @@ -351,13 +348,6 @@ describe('failing createOne FieldMetadataService morph relation fields v2', () = let createdObjectMetadataCompanyId: string; beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - workspaceId: SEED_APPLE_WORKSPACE_ID, - }); - const { data: { createOneObject: { id: objectMetadataPersonId }, @@ -417,25 +407,17 @@ describe('failing createOne FieldMetadataService morph relation fields v2', () = createdObjectMetadataCompanyId, ]; - try { - for (const objectMetadataId of createdObjectMetadataIds) { - await updateOneObjectMetadata({ - expectToFail: false, - input: { - idToUpdate: objectMetadataId, - updatePayload: { isActive: false }, - }, - }); - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: objectMetadataId }, - }); - } - } finally { - await updateFeatureFlag({ + for (const objectMetadataId of createdObjectMetadataIds) { + await updateOneObjectMetadata({ expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: objectMetadataId }, }); } }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts index 56fe9d547a..0aa4bb7f7a 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts @@ -5,9 +5,11 @@ import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object import { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; import { type EachTestingContext } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; type GlobalTestContext = { @@ -42,6 +44,14 @@ type CreateOneObjectMetadataItemTestingContext = EachTestingContext< | ((context: GlobalTestContext) => TestedRelationCreationPayload) >[]; describe('Field metadata morph relation creation should fail', () => { + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + const failingLabelsCreationTestsUseCase: CreateOneObjectMetadataItemTestingContext = [ { @@ -147,6 +157,14 @@ describe('Field metadata morph relation creation should fail', () => { } }); + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + it.each(failingLabelsCreationTestsUseCase)( 'relation ONE_TO_MANY $title', async ({ context }) => { diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/find-many-morph-relation-records.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/find-many-morph-relation-records.integration-spec.ts index 7bf3128432..75c594bff3 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/find-many-morph-relation-records.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/find-many-morph-relation-records.integration-spec.ts @@ -4,6 +4,7 @@ import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-m import { createMorphRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { type EachTestingContext } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; @@ -65,12 +66,41 @@ xdescribe('findMany morph relation records', () => { createdObjectMetadataOpportunityId = objectMetadataOpportunityId; }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataPersonId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataPersonId }, }); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataOpportunityId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataOpportunityId }, }); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataCompanyId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataCompanyId }, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-create-one-field-metadata-morph-relation-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-create-one-field-metadata-morph-relation-v2.integration-spec.ts index 646fd65d21..19ef95d4e7 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-create-one-field-metadata-morph-relation-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-create-one-field-metadata-morph-relation-v2.integration-spec.ts @@ -4,7 +4,6 @@ import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-m 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { eachTestingContextFilter, type EachTestingContext, @@ -14,11 +13,9 @@ import { isDefined } from 'twenty-shared/utils'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; import { type RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto'; -import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util'; type EachTestingContextArray = EachTestingContext< (args: { @@ -96,13 +93,6 @@ describe('successful createOne FieldMetadataService morph relation fields v2', ( let createdFieldMetadataId: string | undefined = undefined; beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - workspaceId: SEED_APPLE_WORKSPACE_ID, - }); - const { data: { createOneObject: { id: objectMetadataPersonId }, @@ -162,25 +152,17 @@ describe('successful createOne FieldMetadataService morph relation fields v2', ( createdObjectMetadataCompanyId, ]; - try { - for (const objectMetadataId of createdObjectMetadataIds) { - await updateOneObjectMetadata({ - expectToFail: false, - input: { - idToUpdate: objectMetadataId, - updatePayload: { isActive: false }, - }, - }); - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: objectMetadataId }, - }); - } - } finally { - await updateFeatureFlag({ + for (const objectMetadataId of createdObjectMetadataIds) { + await updateOneObjectMetadata({ expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: objectMetadataId }, }); } }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-update-one-field-metadata-morph-relation-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-update-one-field-metadata-morph-relation-v2.integration-spec.ts index da23d42f44..ee1e99a78c 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-update-one-field-metadata-morph-relation-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-update-one-field-metadata-morph-relation-v2.integration-spec.ts @@ -4,14 +4,10 @@ import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-m 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util'; - describe('updateOne FieldMetadataService morph relation fields v2', () => { let createdObjectMetadataPersonId: string; let createdObjectMetadataOpportunityId: string; @@ -19,13 +15,6 @@ describe('updateOne FieldMetadataService morph relation fields v2', () => { let createdFieldMetadataId: string; beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - workspaceId: SEED_APPLE_WORKSPACE_ID, - }); - const { data: { createOneObject: { id: objectMetadataPersonId }, @@ -85,25 +74,17 @@ describe('updateOne FieldMetadataService morph relation fields v2', () => { createdObjectMetadataCompanyId, ]; - try { - for (const objectMetadataId of createdObjectMetadataIds) { - await updateOneObjectMetadata({ - expectToFail: false, - input: { - idToUpdate: objectMetadataId, - updatePayload: { isActive: false }, - }, - }); - await deleteOneObjectMetadata({ - expectToFail: false, - input: { idToDelete: objectMetadataId }, - }); - } - } finally { - await updateFeatureFlag({ + for (const objectMetadataId of createdObjectMetadataIds) { + await updateOneObjectMetadata({ expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: objectMetadataId }, }); } }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/phone/create-one-field-metadata-phone.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/phone/create-one-field-metadata-phone.integration-spec.ts index 14a1633375..14bfb80cdd 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/phone/create-one-field-metadata-phone.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/phone/create-one-field-metadata-phone.integration-spec.ts @@ -2,10 +2,11 @@ import { faker } from '@faker-js/faker'; import { type CountryCode } from 'libphonenumber-js'; import { createOneOperation } from 'test/integration/graphql/utils/create-one-operation.util'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; +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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { type EachTestingContext } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { type AdditionalPhoneMetadata, @@ -355,6 +356,15 @@ describe('Phone field metadata tests suite', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap index f8796369e9..781041ef55 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap @@ -5,11 +5,43 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "NOT_AVAILABLE", + "message": "Name "fieldNameBisId" is not available as it is already used by another field", + "userFriendlyMessage": "Name "fieldNameBisId" is not available as it is already used by another field", + "value": "fieldNameBisId", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "fieldNameBisId", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name "fieldNameBisId" is not available as it is already used by another field", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -19,11 +51,43 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "NOT_AVAILABLE", + "message": "Name "fieldName" is not available as it is already used by another field", + "userFriendlyMessage": "Name "fieldName" is not available as it is already used by another field", + "value": "fieldName", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "fieldName", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name "fieldName" is not available as it is already used by another field", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -33,11 +97,49 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Invalid label", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is too short", + "userFriendlyMessage": "Name is too short", + "value": "", + }, + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "value": "", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Invalid label: " "", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -47,11 +149,43 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Exceeds max length", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is too long", + "userFriendlyMessage": "Name is too long", + "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name is too long: it exceeds the 63 characters limit.", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -61,11 +195,49 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Input too short", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is too short", + "userFriendlyMessage": "Name is too short", + "value": "", + }, + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "value": "", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Input is too short: """, - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -74,11 +246,45 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela [ { "extensions": { - "code": "INTERNAL_SERVER_ERROR", - "exceptionEventId": "mocked-exception-id", - "userFriendlyMessage": "An error occurred.", + "code": "BAD_USER_INPUT", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_METADATA_RELATION_MALFORMED", + "message": "Object metadata relation target not found for relation creation payload", + "userFriendlyMessage": "Object targeted by field to create not found", + "value": { + "targetFieldIcon": "IconBuildingSkyscraper", + "targetFieldLabel": "defaultTargetFieldLabel", + "targetObjectMetadataId": Any, + "type": "MANY_TO_ONE", + }, + }, + ], + "flatEntityMinimalInformation": {}, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Object metadata relation target not found for relation creation payload", + "message": "Multiple validation errors occurred while creating field", + "name": "GraphQLError", }, ] `; @@ -88,11 +294,44 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_METADATA_RELATION_MALFORMED", + "message": "Relation creation payload is invalid", + "userFriendlyMessage": "Invalid relation creation payload", + "value": { + "targetFieldIcon": "IconBuildingSkyscraper", + "targetFieldLabel": "defaultTargetFieldLabel", + "targetObjectMetadataId": Any, + "type": "wrong", + }, + }, + ], + "flatEntityMinimalInformation": {}, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Relation creation payload is invalid: type must be one of the following values: ONE_TO_MANY, MANY_TO_ONE", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating field", + "name": "GraphQLError", }, ] `; @@ -102,11 +341,43 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_METADATA_RELATION_MALFORMED", + "message": "Relation creation payload is invalid", + "userFriendlyMessage": "Invalid relation creation payload", + "value": { + "targetFieldIcon": "IconBuildingSkyscraper", + "targetFieldLabel": "defaultTargetFieldLabel", + "targetObjectMetadataId": Any, + }, + }, + ], + "flatEntityMinimalInformation": {}, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Relation creation payload is invalid: type must be one of the following values: ONE_TO_MANY, MANY_TO_ONE", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating field", + "name": "GraphQLError", }, ] `; @@ -116,11 +387,59 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Name is not available, it may be duplicating another field's name.", + "errors": { + "index": [ + { + "errors": [ + { + "code": "INDEX_FIELD_NOT_FOUND", + "message": "Could not find index field related field metadata", + "userFriendlyMessage": "Field referenced in index does not exist", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "IDX_747db609dd2bb7abe30d5d1926c", + }, + "status": "fail", + "type": "create_index", + }, + ], + "objectMetadata": [ + { + "errors": [ + { + "code": "NOT_AVAILABLE", + "message": "Name "fieldNameBisId" is not available as it is already used by another field", + "userFriendlyMessage": "Name "fieldNameBisId" is not available as it is already used by another field", + "value": "fieldNameBisId", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "fieldNameBisId", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name "fieldNameBisId" is not available, check that it is not duplicating another field's name.", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -130,11 +449,44 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_METADATA_RELATION_MALFORMED", + "message": "Relation creation payload is invalid", + "userFriendlyMessage": "Invalid relation creation payload", + "value": { + "targetFieldIcon": "IconBuildingSkyscraper", + "targetFieldLabel": "Relation Name", + "targetObjectMetadataId": Any, + "type": "MANY_TO_ONE", + }, + }, + ], + "flatEntityMinimalInformation": {}, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Relation creation payload is invalid: targetObjectMetadataId must be a UUID", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating field", + "name": "GraphQLError", }, ] `; @@ -144,11 +496,59 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field.", + "errors": { + "index": [ + { + "errors": [ + { + "code": "INDEX_FIELD_NOT_FOUND", + "message": "Could not find index field related field metadata", + "userFriendlyMessage": "Field referenced in index does not exist", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "IDX_28fc32819c3dc0ec7c8594219fc", + }, + "status": "fail", + "type": "create_index", + }, + ], + "objectMetadata": [ + { + "errors": [ + { + "code": "NOT_AVAILABLE", + "message": "Name "fieldNameBisId" is not available as it is already used by another field", + "userFriendlyMessage": "Name "fieldNameBisId" is not available as it is already used by another field", + "value": "fieldNameBisId", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "fieldNameBisId", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name "fieldNameBisId" is not available as it is already used by another field", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -158,11 +558,59 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Name not available", - "userFriendlyMessage": "This name is not available as it is already used by another field.", + "errors": { + "index": [ + { + "errors": [ + { + "code": "INDEX_FIELD_NOT_FOUND", + "message": "Could not find index field related field metadata", + "userFriendlyMessage": "Field referenced in index does not exist", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "IDX_e19260dc9ba8bb2ac7c39c6fcfd", + }, + "status": "fail", + "type": "create_index", + }, + ], + "objectMetadata": [ + { + "errors": [ + { + "code": "NOT_AVAILABLE", + "message": "Name "fieldName" is not available as it is already used by another field", + "userFriendlyMessage": "Name "fieldName" is not available as it is already used by another field", + "value": "fieldName", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "fieldName", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name "fieldName" is not available as it is already used by another field", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -172,11 +620,65 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Invalid label", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [ + { + "errors": [ + { + "code": "INDEX_FIELD_NOT_FOUND", + "message": "Could not find index field related field metadata", + "userFriendlyMessage": "Field referenced in index does not exist", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "IDX_78587d9d07f2ad40242e070725f", + }, + "status": "fail", + "type": "create_index", + }, + ], + "objectMetadata": [ + { + "errors": [ + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is too short", + "userFriendlyMessage": "Name is too short", + "value": "", + }, + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "value": "", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Invalid label: " "", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -186,11 +688,59 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Exceeds max length", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [ + { + "errors": [ + { + "code": "INDEX_FIELD_NOT_FOUND", + "message": "Could not find index field related field metadata", + "userFriendlyMessage": "Field referenced in index does not exist", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "IDX_94535ac037cc2cd5eb316e87a54", + }, + "status": "fail", + "type": "create_index", + }, + ], + "objectMetadata": [ + { + "errors": [ + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is too long", + "userFriendlyMessage": "Name is too long", + "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name is too long: it exceeds the 63 characters limit.", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -200,11 +750,65 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "Input too short", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [ + { + "errors": [ + { + "code": "INDEX_FIELD_NOT_FOUND", + "message": "Could not find index field related field metadata", + "userFriendlyMessage": "Field referenced in index does not exist", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "IDX_78587d9d07f2ad40242e070725f", + }, + "status": "fail", + "type": "create_index", + }, + ], + "objectMetadata": [ + { + "errors": [ + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is too short", + "userFriendlyMessage": "Name is too short", + "value": "", + }, + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "value": "", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Input is too short: """, - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -213,11 +817,45 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela [ { "extensions": { - "code": "INTERNAL_SERVER_ERROR", - "exceptionEventId": "mocked-exception-id", - "userFriendlyMessage": "An error occurred.", + "code": "BAD_USER_INPUT", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_METADATA_RELATION_MALFORMED", + "message": "Object metadata relation target not found for relation creation payload", + "userFriendlyMessage": "Object targeted by field to create not found", + "value": { + "targetFieldIcon": "IconBuildingSkyscraper", + "targetFieldLabel": "defaultTargetFieldLabel", + "targetObjectMetadataId": Any, + "type": "ONE_TO_MANY", + }, + }, + ], + "flatEntityMinimalInformation": {}, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Object metadata relation target not found for relation creation payload", + "message": "Multiple validation errors occurred while creating field", + "name": "GraphQLError", }, ] `; @@ -227,11 +865,44 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_METADATA_RELATION_MALFORMED", + "message": "Relation creation payload is invalid", + "userFriendlyMessage": "Invalid relation creation payload", + "value": { + "targetFieldIcon": "IconBuildingSkyscraper", + "targetFieldLabel": "defaultTargetFieldLabel", + "targetObjectMetadataId": Any, + "type": "wrong", + }, + }, + ], + "flatEntityMinimalInformation": {}, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Relation creation payload is invalid: type must be one of the following values: ONE_TO_MANY, MANY_TO_ONE", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating field", + "name": "GraphQLError", }, ] `; @@ -241,11 +912,43 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_METADATA_RELATION_MALFORMED", + "message": "Relation creation payload is invalid", + "userFriendlyMessage": "Invalid relation creation payload", + "value": { + "targetFieldIcon": "IconBuildingSkyscraper", + "targetFieldLabel": "defaultTargetFieldLabel", + "targetObjectMetadataId": Any, + }, + }, + ], + "flatEntityMinimalInformation": {}, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Relation creation payload is invalid: type must be one of the following values: ONE_TO_MANY, MANY_TO_ONE", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating field", + "name": "GraphQLError", }, ] `; @@ -255,11 +958,43 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "Name is not available, it may be duplicating another field's name.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "NOT_AVAILABLE", + "message": "Name "fieldNameBisId" is not available as it is already used by another field", + "userFriendlyMessage": "Name "fieldNameBisId" is not available as it is already used by another field", + "value": "fieldNameBisId", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "fieldNameBisId", + "objectMetadataId": Any, + }, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name "fieldNameBisId" is not available, check that it is not duplicating another field's name.", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating fields", + "name": "GraphQLError", }, ] `; @@ -269,11 +1004,44 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_METADATA_RELATION_MALFORMED", + "message": "Relation creation payload is invalid", + "userFriendlyMessage": "Invalid relation creation payload", + "value": { + "targetFieldIcon": "IconBuildingSkyscraper", + "targetFieldLabel": "Relation Name", + "targetObjectMetadataId": Any, + "type": "ONE_TO_MANY", + }, + }, + ], + "flatEntityMinimalInformation": {}, + "type": "create_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Relation creation payload is invalid: targetObjectMetadataId must be a UUID", - "name": "UserInputError", + "message": "Multiple validation errors occurred while creating field", + "name": "GraphQLError", }, ] `; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-update.integration-spec.ts.snap index 5251cb5758..abb68e0e15 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-update.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-update.integration-spec.ts.snap @@ -5,11 +5,57 @@ exports[`Field metadata relation update should fail relation when name is change { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_MUTATION_NOT_ALLOWED", + "message": "Forbidden updated properties for relation field metadata: name", + "userFriendlyMessage": "Forbidden updated properties for relation field metadata", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "newName", + "objectMetadataId": Any, + }, + "type": "update_field", + }, + { + "errors": [ + { + "code": "FIELD_MUTATION_NOT_ALLOWED", + "message": "Forbidden updated properties for relation field metadata: name", + "userFriendlyMessage": "Forbidden updated properties for relation field metadata", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "newName", + "objectMetadataId": Any, + }, + "type": "update_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "Name cannot be changed for relation fields", - "name": "UserInputError", + "message": "Multiple validation errors occurred while updating field", + "name": "GraphQLError", }, ] `; @@ -19,11 +65,81 @@ exports[`Field metadata relation update should fail relation when name is not in { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_FIELD_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "FIELD_MUTATION_NOT_ALLOWED", + "message": "Forbidden updated properties for relation field metadata: name", + "userFriendlyMessage": "Forbidden updated properties for relation field metadata", + }, + { + "code": "INVALID_FIELD_INPUT", + "message": "Name should be in camelCase", + "userFriendlyMessage": "Name should be in camelCase", + "value": "New Name", + }, + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "value": "New Name", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "New Name", + "objectMetadataId": Any, + }, + "type": "update_field", + }, + { + "errors": [ + { + "code": "FIELD_MUTATION_NOT_ALLOWED", + "message": "Forbidden updated properties for relation field metadata: name", + "userFriendlyMessage": "Forbidden updated properties for relation field metadata", + }, + { + "code": "INVALID_FIELD_INPUT", + "message": "Name should be in camelCase", + "userFriendlyMessage": "Name should be in camelCase", + "value": "New Name", + }, + { + "code": "INVALID_FIELD_INPUT", + "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", + "value": "New Name", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "New Name", + "objectMetadataId": Any, + }, + "type": "update_field", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "New Name should be in camelCase", - "name": "UserInputError", + "message": "Multiple validation errors occurred while updating field", + "name": "GraphQLError", }, ] `; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/create-one-field-metadata-relation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/create-one-field-metadata-relation.integration-spec.ts index 8ef06661f3..6e68b8b3a0 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/create-one-field-metadata-relation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/create-one-field-metadata-relation.integration-spec.ts @@ -1,17 +1,16 @@ -import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util'; +import { findManyFieldsMetadata } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { createRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-relation-between-objects.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; -import { type EachTestingContext } from 'twenty-shared/testing'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { findManyFieldsMetadata } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; describe('createOne FieldMetadataService relation fields', () => { - let createdObjectMetadataPersonId = ''; - let createdObjectMetadataOpportunityId = ''; + let createdObjectMetadataPersonId: string; + let createdObjectMetadataOpportunityId: string; beforeEach(async () => { const { @@ -19,6 +18,7 @@ describe('createOne FieldMetadataService relation fields', () => { createOneObject: { id: objectMetadataPersonId }, }, } = await createOneObjectMetadata({ + expectToFail: false, input: { nameSingular: 'personForRelation', namePlural: 'peopleForRelation', @@ -35,6 +35,7 @@ describe('createOne FieldMetadataService relation fields', () => { createOneObject: { id: objectMetadataOpportunityId }, }, } = await createOneObjectMetadata({ + expectToFail: false, input: { nameSingular: 'opportunityForRelation', namePlural: 'opportunitiesForRelation', @@ -47,72 +48,50 @@ describe('createOne FieldMetadataService relation fields', () => { createdObjectMetadataOpportunityId = objectMetadataOpportunityId; }); afterEach(async () => { - await deleteOneObjectMetadata({ - input: { idToDelete: createdObjectMetadataPersonId }, + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataPersonId, + updatePayload: { + isActive: false, + }, + }, }); await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectMetadataPersonId }, + }); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataOpportunityId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, input: { idToDelete: createdObjectMetadataOpportunityId }, }); }); - type EachTestingContextArray = EachTestingContext< - (args: { objectMetadataId: string; targetObjectMetadataId: string }) => { - relationType: RelationType; - objectMetadataId: string; - targetObjectMetadataId: string; - type: FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION; - } - >[]; - - const eachTestingContextArray: EachTestingContextArray = [ - { - title: 'should create a RELATION field type MANY_TO_ONE', - context: ({ objectMetadataId, targetObjectMetadataId }) => ({ - relationType: RelationType.MANY_TO_ONE, - objectMetadataId, - targetObjectMetadataId, - type: FieldMetadataType.RELATION, - }), - }, - { - title: 'should create a RELATION field type ONE_TO_MANY', - context: ({ objectMetadataId, targetObjectMetadataId }) => ({ - relationType: RelationType.ONE_TO_MANY, - objectMetadataId, - targetObjectMetadataId, - type: FieldMetadataType.RELATION, - }), - }, - ]; - - it.each(eachTestingContextArray)('$title', async ({ context }) => { - const contextPayload = context({ + it('MANY TO ONE field relation creation', async () => { + const createdField = await createRelationBetweenObjects({ objectMetadataId: createdObjectMetadataOpportunityId, targetObjectMetadataId: createdObjectMetadataPersonId, - }); - - const createdField = await createRelationBetweenObjects< - typeof contextPayload.type - >({ - objectMetadataId: contextPayload.objectMetadataId, - targetObjectMetadataId: contextPayload.targetObjectMetadataId, - type: contextPayload.type, - relationType: contextPayload.relationType, + relationType: RelationType.MANY_TO_ONE, + type: FieldMetadataType.RELATION, + name: 'person', }); expect(createdField.id).toBeDefined(); expect(createdField.name).toBe('person'); - expect(createdField.relation?.type).toBe(contextPayload.relationType); + expect(createdField.relation?.type).toBe(RelationType.MANY_TO_ONE); expect(createdField.relation?.targetFieldMetadata.id).toBeDefined(); - // TODO: expect(createdField.morphRelations).toBeUndefined(); - const isManyToOne = - contextPayload.relationType === RelationType.MANY_TO_ONE; - if (isManyToOne) { - expect(createdField.settings?.joinColumnName).toBe('personId'); - } else { - expect(createdField.settings?.joinColumnName).toBeUndefined(); - } + expect(createdField.settings?.joinColumnName).toBe('personId'); if (!isDefined(createdField.relation?.targetFieldMetadata?.id)) { throw new Error('targetFieldMetadata.id is not defined'); @@ -126,7 +105,46 @@ describe('createOne FieldMetadataService relation fields', () => { 'personForRelation', ); expect(opportunityFieldOnPerson.relation.type).toBe( - isManyToOne ? RelationType.ONE_TO_MANY : RelationType.MANY_TO_ONE, + RelationType.ONE_TO_MANY, + ); + expect( + opportunityFieldOnPerson.relation.targetFieldMetadata.id, + ).toBeDefined(); + expect( + opportunityFieldOnPerson.relation.targetObjectMetadata.id, + ).toBeDefined(); + expect(opportunityFieldOnPerson.settings?.joinColumnName).toBeUndefined(); + }); + + it('ONE TO MANY field relation creation', async () => { + const createdField = await createRelationBetweenObjects({ + objectMetadataId: createdObjectMetadataOpportunityId, + targetObjectMetadataId: createdObjectMetadataPersonId, + relationType: RelationType.ONE_TO_MANY, + type: FieldMetadataType.RELATION, + name: 'person', + }); + + expect(createdField.id).toBeDefined(); + expect(createdField.name).toBe('person'); + expect(createdField.relation?.type).toBe(RelationType.ONE_TO_MANY); + expect(createdField.relation?.targetFieldMetadata.id).toBeDefined(); + + expect(createdField.settings?.joinColumnName).toBeUndefined(); + + if (!isDefined(createdField.relation?.targetFieldMetadata?.id)) { + throw new Error('targetFieldMetadata.id is not defined'); + } + + const opportunityFieldOnPerson = await findFieldMetadata({ + fieldMetadataId: createdField.relation.targetFieldMetadata.id, + }); + + expect(opportunityFieldOnPerson.object.nameSingular).toBe( + 'personForRelation', + ); + expect(opportunityFieldOnPerson.relation.type).toBe( + RelationType.MANY_TO_ONE, ); expect( opportunityFieldOnPerson.relation.targetFieldMetadata.id, @@ -135,17 +153,9 @@ describe('createOne FieldMetadataService relation fields', () => { opportunityFieldOnPerson.relation.targetObjectMetadata.id, ).toBeDefined(); - if (!isManyToOne) { - expect(opportunityFieldOnPerson.settings?.joinColumnName).toBe( - 'opportunityId', - ); - } else { - expect(opportunityFieldOnPerson.settings?.joinColumnName).toBeUndefined(); - } - - await deleteOneFieldMetadata({ - input: { idToDelete: createdField.id }, - }).catch(); + expect(opportunityFieldOnPerson.settings?.joinColumnName).toBe( + 'opportunityId', + ); }); }); @@ -181,6 +191,8 @@ const findFieldMetadata = async ({ settings `, }); + + expect(fields.length).toBe(1); const field = fields[0]?.node; return field; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/failing-field-metadata-relation-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/failing-field-metadata-relation-creation.integration-spec.ts index 28e486924e..8337f283d3 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/failing-field-metadata-relation-creation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/failing-field-metadata-relation-creation.integration-spec.ts @@ -3,8 +3,10 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-m 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 { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { type EachTestingContext } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; @@ -232,6 +234,15 @@ describe('Field metadata relation creation should fail', () => { for (const objectMetadataId of Object.values( globalTestContext.objectMetadataIds, )) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: objectMetadataId, @@ -273,7 +284,9 @@ describe('Field metadata relation creation should fail', () => { }); expect(errors).toBeDefined(); - expect(errors).toMatchSnapshot(); + expect(errors).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(errors), + ); }, ); @@ -310,7 +323,9 @@ describe('Field metadata relation creation should fail', () => { }); expect(errors).toBeDefined(); - expect(errors).toMatchSnapshot(); + expect(errors).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(errors), + ); }, ); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/failing-field-metadata-relation-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/failing-field-metadata-relation-update.integration-spec.ts index 9e7d611015..476e5bd9fd 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/failing-field-metadata-relation-update.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/failing-field-metadata-relation-update.integration-spec.ts @@ -3,8 +3,10 @@ import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-m 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 { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { type EachTestingContext } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; @@ -82,6 +84,15 @@ describe('Field metadata relation update should fail', () => { globalTestContext.employeeObjectId, globalTestContext.enterpriseObjectId, ]) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: objectMetadataId, @@ -104,7 +115,9 @@ describe('Field metadata relation update should fail', () => { }); expect(errors).toBeDefined(); - expect(errors).toMatchSnapshot(); + expect(errors).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(errors), + ); }, ); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/successful-field-metadata-relation-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/successful-field-metadata-relation-update.integration-spec.ts index cf128b0fc0..f30db63448 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/successful-field-metadata-relation-update.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/successful-field-metadata-relation-update.integration-spec.ts @@ -3,6 +3,7 @@ import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-m 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 { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; @@ -65,6 +66,15 @@ describe('Field metadata relation update should succeed', () => { globalTestContext.employeeObjectId, globalTestContext.enterpriseObjectId, ]) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: objectMetadataId, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/create-one-unique-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/create-one-unique-field-metadata.integration-spec.ts index 36bc944b56..5ed578dc9b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/create-one-unique-field-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/create-one-unique-field-metadata.integration-spec.ts @@ -1,11 +1,23 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; + describe('create one unique field metadata', () => { let createdObjectMetadataId = ''; + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + beforeEach(async () => { const { data: { @@ -25,11 +37,28 @@ describe('create one unique field metadata', () => { }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataId }, }); }); + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + it('should create one unique field metadata', async () => { const createFieldInput = { name: 'uniqueTestField', diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/record-creation-with-custom-unique-field.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/record-creation-with-custom-unique-field.integration-spec.ts index 24d2a3e5d1..3a701a51ce 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/record-creation-with-custom-unique-field.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/record-creation-with-custom-unique-field.integration-spec.ts @@ -4,8 +4,12 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-m import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; + const createRecordQuery = gql` mutation CreateFirstRecord($data: TestRecordObjectCreateInput!) { createTestRecordObject(data: $data) { @@ -19,6 +23,22 @@ describe('create records with custom unique fields', () => { let createdObjectMetadataId = ''; let uniqueFieldId = ''; + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + beforeEach(async () => { const { data: { @@ -57,6 +77,15 @@ describe('create records with custom unique fields', () => { }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/update-one-unique-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/update-one-unique-field-metadata.integration-spec.ts index 182d54963e..3b508cef41 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/update-one-unique-field-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/update-one-unique-field-metadata.integration-spec.ts @@ -3,11 +3,31 @@ import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-m import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; + describe('update one unique field metadata', () => { let createdObjectId = ''; + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + beforeEach(async () => { const { data: { @@ -27,6 +47,15 @@ describe('update one unique field metadata', () => { }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: createdObjectId }, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-related-record.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-related-record.integration-spec.ts index 9a95492481..6963bb98a9 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-related-record.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-related-record.integration-spec.ts @@ -4,6 +4,7 @@ import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-m 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 { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { createTestViewFilterWithRestApi, createTestViewWithRestApi, @@ -15,7 +16,9 @@ import { FieldMetadataType, } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { ViewFilterOperand } from 'src/engine/core-modules/view/enums/view-filter-operand'; import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; import { type ViewFilterValue } from 'src/engine/core-modules/view/types/view-filter-value.type'; @@ -69,6 +72,22 @@ const testFieldMetadataType: EnumFieldMetadataType[] = [ describe('update-one-field-metadata-related-record', () => { let idToDelete: string; + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + const createObjectSelectFieldAndView = async ({ options, type: fieldMetadataType, @@ -78,6 +97,7 @@ describe('update-one-field-metadata-related-record', () => { const { data: { createOneObject }, } = await createOneObjectMetadata({ + expectToFail: false, input: getMockCreateObjectInput({ labelSingular: singular, labelPlural: plural, @@ -92,6 +112,7 @@ describe('update-one-field-metadata-related-record', () => { const { data: { createOneField }, } = await createOneFieldMetadata({ + expectToFail: false, input: { objectMetadataId: createOneObject.id, type: fieldMetadataType, @@ -118,7 +139,17 @@ describe('update-one-field-metadata-related-record', () => { afterEach(async () => { if (isDefined(idToDelete)) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: idToDelete, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ + expectToFail: false, input: { idToDelete: idToDelete }, }); } 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 07d9a027a8..72847e2c45 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 @@ -6,6 +6,8 @@ import { } 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { FieldMetadataType } from 'twenty-shared/types'; describe('updateOne', () => { @@ -42,6 +44,15 @@ describe('updateOne', () => { testFieldId = createdFieldMetadata.createOneField.id; }); afterEach(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: listingObjectId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: listingObjectId }, @@ -128,6 +139,15 @@ describe('updateOne', () => { testFieldId = createdFieldMetadata.createOneField.id; }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: listingObjectId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: listingObjectId }, @@ -153,14 +173,14 @@ describe('updateOne', () => { expectToFail: true, }); - // Assert - expect(errors[0].message).toBe( - 'Name is not synced with label. Expected name: "testName", got newName', + expect(errors).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(errors), ); }); it('should throw if the field name is not available because of other field with the same name', async () => { await createOneFieldMetadata({ + expectToFail: false, input: { objectMetadataId: listingObjectId, type: FieldMetadataType.TEXT, @@ -170,15 +190,15 @@ describe('updateOne', () => { }); const { errors } = await updateOneFieldMetadata({ + expectToFail: true, input: { idToUpdate: testFieldId, - updatePayload: { name: 'testName' }, + updatePayload: { name: 'otherTestName' }, }, }); - // Assert - expect(errors[0].message).toBe( - 'Name "testName" is not available, check that it is not duplicating another field\'s name.', + expect(errors).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(errors), ); }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/index/successful-index-creation-on-object-creation-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/index/successful-index-creation-on-object-creation-v2.integration-spec.ts index ba8770a962..6dd72cbef0 100644 --- a/packages/twenty-server/test/integration/metadata/suites/index/successful-index-creation-on-object-creation-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/index/successful-index-creation-on-object-creation-v2.integration-spec.ts @@ -3,12 +3,9 @@ import { createOneObjectMetadata } from 'test/integration/metadata/suites/object import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { findManyObjectMetadataWithIndexes } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-with-indexes.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test'; import { FieldMetadataType } from 'twenty-shared/types'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; - const findObjectWithIndex = async ({ objectMetadataId, }: { @@ -29,22 +26,6 @@ const findObjectWithIndex = async ({ describe('Index metadata creation through object metadata creation v2', () => { let createdObjectId: string; - beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - }); - - afterAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - }); - beforeEach(async () => { const { labelPlural, diff --git a/packages/twenty-server/test/integration/metadata/suites/index/successful-index-on-relation-field-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/index/successful-index-on-relation-field-v2.integration-spec.ts index 4966ed6fba..8df59956ae 100644 --- a/packages/twenty-server/test/integration/metadata/suites/index/successful-index-on-relation-field-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/index/successful-index-on-relation-field-v2.integration-spec.ts @@ -1,39 +1,20 @@ -import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; -import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; -import { FieldMetadataType, RelationType } from 'twenty-shared/types'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util'; import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; import { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-metadata/constants/custom-object-dishes.constants'; import { CUSTOM_OBJECT_FOOD } from 'test/integration/metadata/suites/object-metadata/constants/custom-object-food.constants'; 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 { findManyObjectMetadataWithIndexes } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-with-indexes.util'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test'; import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; - -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FieldMetadataType, RelationType } from 'twenty-shared/types'; describe('Index metadata creation on relation field creation v2', () => { let createdObjectId: string; let secondCreatedObjectId: string; - beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - }); - - afterAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - }); - beforeEach(async () => { const { labelPlural, diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap index bef86fa84a..d1d55af520 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap @@ -5,11 +5,43 @@ exports[`Object metadata update should fail when labelIdentifier is not a TEXT o { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_OBJECT_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "INVALID_OBJECT_INPUT", + "message": "Label identifier field metadata must be a TEXT or FULL_NAME field type", + "userFriendlyMessage": "Label identifier field metadata must be a TEXT or FULL_NAME field type", + "value": "NUMBER", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "namePlural": "whatevers", + "nameSingular": "whatever", + }, + "type": "update_object", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "labelIdentifierFieldMetadataId validation failed: it must be a TEXT or FULL_NAME field metadata type id", - "name": "UserInputError", + "message": "Multiple validation errors occurred while updating object", + "name": "GraphQLError", }, ] `; @@ -19,11 +51,43 @@ exports[`Object metadata update should fail when labelIdentifier is not a known { "extensions": { "code": "BAD_USER_INPUT", - "subCode": "INVALID_OBJECT_INPUT", - "userFriendlyMessage": "An error occurred.", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "INVALID_OBJECT_INPUT", + "message": "Label identifier field metadata not found in field metadata list", + "userFriendlyMessage": "Label identifier field metadata not found in field metadata list", + "value": "42422020-f49c-4159-8751-76a24f47b360", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "namePlural": "whatevers", + "nameSingular": "whatever", + }, + "type": "update_object", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, - "message": "labelIdentifierFieldMetadataId validation failed: related field metadata not found", - "name": "UserInputError", + "message": "Multiple validation errors occurred while updating object", + "name": "GraphQLError", }, ] `; @@ -43,3 +107,48 @@ exports[`Object metadata update should fail when labelIdentifier is not a uuid 1 }, ] `; + +exports[`Object metadata update should fail when labelIdentifier is null 1`] = ` +[ + { + "extensions": { + "code": "BAD_USER_INPUT", + "errors": { + "index": [], + "objectMetadata": [ + { + "errors": [ + { + "code": "MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD", + "message": "Label identifier field metadata is required", + "userFriendlyMessage": "Label identifier field metadata is required", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "namePlural": "whatevers", + "nameSingular": "whatever", + }, + "type": "update_object", + }, + ], + "serverlessFunction": [], + "view": [], + "viewField": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating object", + "name": "GraphQLError", + }, +] +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/create-delete-and-create-object-metadata-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/create-delete-and-create-object-metadata-v2.integration-spec.ts index 86b7474edc..a7059640db 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/create-delete-and-create-object-metadata-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/create-delete-and-create-object-metadata-v2.integration-spec.ts @@ -1,35 +1,18 @@ import { type 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 { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util'; +import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; import { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-metadata/constants/custom-object-dishes.constants'; import { type CreateOneObjectFactoryInput } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-query-factory.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; - -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; describe('Handle same object sequential operations test suite', () => { let createdObjectMetadataId: string | undefined = undefined; - beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - }); - afterAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - }); afterEach(async () => { if (!isDefined(createdObjectMetadataId)) { return; diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-create-one-object-metadata-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-create-one-object-metadata-v2.integration-spec.ts index 75822de30a..c721d7d98d 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-create-one-object-metadata-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-create-one-object-metadata-v2.integration-spec.ts @@ -4,10 +4,6 @@ import { createOneObjectMetadata } from 'test/integration/metadata/suites/object import { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { eachTestingContextFilter } from 'twenty-shared/testing'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; - -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util'; const allTestsUseCases = [ ...OBJECT_METADATA_NAMES_FAILING_TEST_CASES, @@ -15,23 +11,6 @@ const allTestsUseCases = [ ]; describe('Object metadata creation should fail v2', () => { - beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - workspaceId: SEED_APPLE_WORKSPACE_ID, - }); - }); - - afterAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - workspaceId: SEED_APPLE_WORKSPACE_ID, - }); - }); it.each(eachTestingContextFilter(allTestsUseCases))( '$title', async ({ context }) => { diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-create-one-object-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-create-one-object-metadata.integration-spec.ts index 251071dd59..590b3569d2 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-create-one-object-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-create-one-object-metadata.integration-spec.ts @@ -1,9 +1,12 @@ -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; -import { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; import { OBJECT_METADATA_LABEL_FAILING_TEST_CASES } from 'test/integration/metadata/suites/object-metadata/common/object-metadata-label-failing-tests-cases'; import { OBJECT_METADATA_NAMES_FAILING_TEST_CASES } from 'test/integration/metadata/suites/object-metadata/common/object-metadata-names-failing-tests-cases'; +import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util'; const allTestsUseCases = [ ...OBJECT_METADATA_NAMES_FAILING_TEST_CASES, @@ -11,6 +14,24 @@ const allTestsUseCases = [ ]; describe('Object metadata creation should fail', () => { + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + workspaceId: SEED_APPLE_WORKSPACE_ID, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + workspaceId: SEED_APPLE_WORKSPACE_ID, + }); + }); + it.each(allTestsUseCases)('$title', async ({ context }) => { const { errors } = await createOneObjectMetadata({ input: getMockCreateObjectInput(context), diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-update-one-object-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-update-one-object-metadata.integration-spec.ts index 897842d5d5..e05987f2e1 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-update-one-object-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-update-one-object-metadata.integration-spec.ts @@ -1,10 +1,13 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; -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 { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { type EachTestingContext } from 'twenty-shared/testing'; +import { + eachTestingContextFilter, + type EachTestingContext, +} from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; +import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { type UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; @@ -32,6 +35,12 @@ const labelIdentifierFailingTestsUseCase: CreateOneObjectMetadataItemTestingCont labelIdentifierFieldMetadataId: '42422020-f49c-4159-8751-76a24f47b360', }, }, + { + title: 'when labelIdentifier is null', + context: { + labelIdentifierFieldMetadataId: null as any, + }, + }, { title: 'when labelIdentifier is not a TEXT or NAME field', context: ({ numberFieldMetadataId }) => ({ @@ -48,7 +57,13 @@ describe('Object metadata update should fail', () => { beforeAll(async () => { const { data } = await createOneObjectMetadata({ - input: getMockCreateObjectInput(), + expectToFail: false, + input: { + labelPlural: 'whatevers', + labelSingular: 'whatever', + namePlural: 'whatevers', + nameSingular: 'whatever', + }, }); objectMetadataId = data.createOneObject.id; @@ -56,6 +71,7 @@ describe('Object metadata update should fail', () => { const { data: { createOneField }, } = await createOneFieldMetadata({ + expectToFail: false, input: { objectMetadataId: objectMetadataId, name: 'testName', @@ -69,6 +85,15 @@ describe('Object metadata update should fail', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: objectMetadataId, @@ -76,21 +101,26 @@ describe('Object metadata update should fail', () => { }); }); - it.each(allTestsUseCases)('$title', async ({ context }) => { - const updatePayload = - typeof context === 'function' - ? context({ numberFieldMetadataId, objectMetadataId }) - : context; + it.each(eachTestingContextFilter(allTestsUseCases))( + '$title', + async ({ context }) => { + const updatePayload = + typeof context === 'function' + ? context({ numberFieldMetadataId, objectMetadataId }) + : context; - const { errors } = await updateOneObjectMetadata({ - input: { - idToUpdate: objectMetadataId, - updatePayload, - }, - expectToFail: true, - }); + const { errors } = await updateOneObjectMetadata({ + input: { + idToUpdate: objectMetadataId, + updatePayload, + }, + expectToFail: true, + }); - expect(errors).toBeDefined(); - expect(errors).toMatchSnapshot(); - }); + expect(errors).toBeDefined(); + expect(errors).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(errors), + ); + }, + ); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/delete-one-object-metadata-with-morph-relation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/delete-one-object-metadata-with-morph-relation.integration-spec.ts index 29e101a0fd..09bf5ac81d 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/delete-one-object-metadata-with-morph-relation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/delete-one-object-metadata-with-morph-relation.integration-spec.ts @@ -1,14 +1,17 @@ import { findManyFieldsMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata-query-factory.util'; import { createMorphRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util'; +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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; import { FieldMetadataType } from 'twenty-shared/types'; -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test'; +import { isDefined } from 'twenty-shared/utils'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; describe('Delete Object metadata with morph relation should succeed', () => { - let opportunityId = ''; + let opportunityId: undefined | string; let personId = ''; let companyId = ''; let morphRelationField: { id: string }; @@ -62,12 +65,45 @@ describe('Delete Object metadata with morph relation should succeed', () => { }); afterEach(async () => { - await deleteOneObjectMetadata({ input: { idToDelete: opportunityId } }); + if (isDefined(opportunityId)) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: opportunityId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ input: { idToDelete: opportunityId } }); + opportunityId = undefined; + } + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: personId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: personId } }); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: companyId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: companyId } }); }); it('When deleting source object, the relation on the target should be deleted', async () => { + jestExpectToBeDefined(opportunityId); morphRelationField = await createMorphRelationBetweenObjects({ objectMetadataId: opportunityId, firstTargetObjectMetadataId: personId, @@ -76,12 +112,22 @@ describe('Delete Object metadata with morph relation should succeed', () => { relationType: RelationType.MANY_TO_ONE, }); + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: opportunityId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: opportunityId } }); const fieldAfterDeletion = await findFieldMetadata({ fieldMetadataId: morphRelationField.id, }); expect(fieldAfterDeletion).toBeUndefined(); + opportunityId = undefined; }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation-v2.integration-spec.ts index 956696149c..a322eb567c 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation-v2.integration-spec.ts @@ -1,37 +1,18 @@ import { findManyFieldsMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata-query-factory.util'; import { createMorphRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util'; +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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; - describe('Rename an object metadata with morph relation should succeed', () => { let createdObjectMetadataPersonId: string; let createdObjectMetadataOpportunityId: string; let createdObjectMetadataCompanyId: string; - beforeAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: true, - }); - }); - - afterAll(async () => { - await updateFeatureFlag({ - expectToFail: false, - featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - value: false, - }); - }); - beforeEach(async () => { const { data: { @@ -102,57 +83,63 @@ describe('Rename an object metadata with morph relation should succeed', () => { } }); - it('should rename custom object, and update both the field name and join column name of the morph relation that contains the object name', async () => { - const morphRelationField = await createMorphRelationBetweenObjects({ - name: 'owner', - objectMetadataId: createdObjectMetadataOpportunityId, - firstTargetObjectMetadataId: createdObjectMetadataPersonId, - secondTargetObjectMetadataId: createdObjectMetadataCompanyId, - type: FieldMetadataType.MORPH_RELATION, - relationType: RelationType.MANY_TO_ONE, - }); + it.failing( + 'should rename custom object, and update both the field name and join column name of the morph relation that contains the object name', + async () => { + const morphRelationField = await createMorphRelationBetweenObjects({ + name: 'owner', + objectMetadataId: createdObjectMetadataOpportunityId, + firstTargetObjectMetadataId: createdObjectMetadataPersonId, + secondTargetObjectMetadataId: createdObjectMetadataCompanyId, + type: FieldMetadataType.MORPH_RELATION, + relationType: RelationType.MANY_TO_ONE, + }); - expect(morphRelationField.morphRelations.length).toBe(2); + expect(morphRelationField.morphRelations.length).toBe(2); - const { data } = await updateOneObjectMetadata({ - gqlFields: ` + const { data } = await updateOneObjectMetadata({ + expectToFail: false, + gqlFields: ` nameSingular labelSingular namePlural labelPlural `, - input: { - idToUpdate: createdObjectMetadataPersonId, - updatePayload: { - nameSingular: 'personForRenameSecond2', - namePlural: 'peopleForRenameSecond2', - labelSingular: 'Person For Rename2', - labelPlural: 'People For Rename2', + input: { + idToUpdate: createdObjectMetadataPersonId, + updatePayload: { + nameSingular: 'personForRenameSecond2', + namePlural: 'peopleForRenameSecond2', + labelSingular: 'Person For Rename2', + labelPlural: 'People For Rename2', + }, }, - }, - }); + }); - expect(data.updateOneObject.nameSingular).toBe('personForRenameSecond2'); + expect(data.updateOneObject.nameSingular).toBe('personForRenameSecond2'); - const ownerFieldMetadataOnPersonId = morphRelationField.morphRelations.find( - (morphRelation) => - morphRelation.targetObjectMetadata.id === createdObjectMetadataPersonId, - )?.sourceFieldMetadata.id; + const ownerFieldMetadataOnPersonId = + morphRelationField.morphRelations.find( + (morphRelation) => + morphRelation.targetObjectMetadata.id === + createdObjectMetadataPersonId, + )?.sourceFieldMetadata.id; - if (!ownerFieldMetadataOnPersonId) { - throw new Error( - 'Morph Relation Error: Owner field metadata on person not found', + if (!ownerFieldMetadataOnPersonId) { + throw new Error( + 'Morph Relation Error: Owner field metadata on person not found', + ); + } + + const fieldAfterRenaming = await findFieldMetadata({ + fieldMetadataId: ownerFieldMetadataOnPersonId, + }); + + expect(fieldAfterRenaming.settings.joinColumnName).toBe( + 'ownerPersonForRenameSecond2Id', ); - } - - const fieldAfterRenaming = await findFieldMetadata({ - fieldMetadataId: ownerFieldMetadataOnPersonId, - }); - - expect(fieldAfterRenaming.settings.joinColumnName).toBe( - 'ownerPersonForRenameSecondId', - ); - }); + }, + ); }); const findFieldMetadata = async ({ diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/relation/delete-one-object-metadata-with-relation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/relation/delete-one-object-metadata-with-relation.integration-spec.ts index b99c0a97e1..7ea6ff8a43 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/relation/delete-one-object-metadata-with-relation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/relation/delete-one-object-metadata-with-relation.integration-spec.ts @@ -1,10 +1,13 @@ import { findManyFieldsMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata-query-factory.util'; import { createRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-relation-between-objects.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; import { type EachTestingContext } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test'; +import { isDefined } from 'twenty-shared/utils'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; @@ -42,14 +45,10 @@ const successfulDeleteTargetUseCase: DeleteOneObjectMetadataItemTestingContext = ]; describe('Delete Object metadata with relation should succeed', () => { - let createdObjectMetadataPersonId = ''; - let createdObjectMetadataOpportunityId = ''; - let globalTestContext: { - opportunityMetadataId: string; - personMetadataId: string; - relationField: FieldMetadataDTO & { - relation: RelationDTO; - }; + let createdObjectMetadataPersonId: undefined | string; + let createdObjectMetadataOpportunityId: undefined | string; + let relationField: FieldMetadataDTO & { + relation: RelationDTO; }; beforeEach(async () => { @@ -58,6 +57,7 @@ describe('Delete Object metadata with relation should succeed', () => { createOneObject: { id: objectMetadataPersonId }, }, } = await createOneObjectMetadata({ + expectToFail: false, input: { nameSingular: 'personForRelation', namePlural: 'peopleForRelation', @@ -74,6 +74,7 @@ describe('Delete Object metadata with relation should succeed', () => { createOneObject: { id: objectMetadataOpportunityId }, }, } = await createOneObjectMetadata({ + expectToFail: false, input: { nameSingular: 'opportunityForRelation', namePlural: 'opportunitiesForRelation', @@ -85,38 +86,70 @@ describe('Delete Object metadata with relation should succeed', () => { createdObjectMetadataOpportunityId = objectMetadataOpportunityId; - globalTestContext = { - opportunityMetadataId: createdObjectMetadataOpportunityId, - personMetadataId: createdObjectMetadataPersonId, - relationField: - await createRelationBetweenObjects({ - objectMetadataId: createdObjectMetadataOpportunityId, - targetObjectMetadataId: createdObjectMetadataPersonId, - type: FieldMetadataType.RELATION, - relationType: RelationType.MANY_TO_ONE, - }), - }; + relationField = + await createRelationBetweenObjects({ + objectMetadataId: createdObjectMetadataOpportunityId, + targetObjectMetadataId: createdObjectMetadataPersonId, + type: FieldMetadataType.RELATION, + relationType: RelationType.MANY_TO_ONE, + }); }); afterEach(async () => { - await deleteOneObjectMetadata({ - input: { idToDelete: createdObjectMetadataPersonId }, - }); - await deleteOneObjectMetadata({ - input: { idToDelete: createdObjectMetadataOpportunityId }, - }); + if (isDefined(createdObjectMetadataPersonId)) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataPersonId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectMetadataPersonId }, + }); + } + + if (isDefined(createdObjectMetadataOpportunityId)) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataOpportunityId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectMetadataOpportunityId }, + }); + } }); it.each(successfulDeleteSourceUseCase)('$title', async ({ context }) => { + jestExpectToBeDefined(createdObjectMetadataPersonId); const computedContext = context({ - objectMetadataIdToDelete: globalTestContext.personMetadataId, - relationFieldId: globalTestContext.relationField.id, + objectMetadataIdToDelete: createdObjectMetadataPersonId, + relationFieldId: relationField.id, }); + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: computedContext.objectMetadataIdToDelete, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: computedContext.objectMetadataIdToDelete }, }); + createdObjectMetadataPersonId = undefined; const opportunityFieldOnPersonAfterDeletion = await findFieldMetadata({ fieldMetadataId: computedContext.relationFieldId, }); @@ -125,15 +158,27 @@ describe('Delete Object metadata with relation should succeed', () => { }); it.each(successfulDeleteTargetUseCase)('$title', async ({ context }) => { + jestExpectToBeDefined(createdObjectMetadataOpportunityId); const computedContext = context({ - objectMetadataIdToDelete: globalTestContext.opportunityMetadataId, - relationFieldId: globalTestContext.relationField.id, + objectMetadataIdToDelete: createdObjectMetadataOpportunityId, + relationFieldId: relationField.id, }); + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: computedContext.objectMetadataIdToDelete, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ + expectToFail: false, input: { idToDelete: computedContext.objectMetadataIdToDelete }, }); + createdObjectMetadataOpportunityId = undefined; const personFieldOnOpportunityAfterDeletion = await findFieldMetadata({ fieldMetadataId: computedContext.relationFieldId, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts index 58d8a626c8..6a66ad343c 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts @@ -163,58 +163,19 @@ describe('Custom object renaming', () => { expect(data.updateOneObject.namePlural).toBe(HOUSE_NAME_PLURAL); expect(data.updateOneObject.labelSingular).toBe(HOUSE_LABEL_SINGULAR); expect(data.updateOneObject.labelPlural).toBe(HOUSE_LABEL_PLURAL); - - const fieldsResponse = await makeMetadataAPIRequest(fieldsGraphqlOperation); - - const fieldsMetadata = fieldsResponse.body.data.fields.edges.map( - // @ts-expect-error legacy noImplicitAny - (field) => field.node, - ); - - // standard relations have been updated - STANDARD_OBJECT_RELATIONS.forEach((relation) => { - // relation field - const relationFieldMetadataId = - // @ts-expect-error legacy noImplicitAny - standardObjectRelationsMap[relation].relationFieldMetadataId; - - const updatedRelationFieldMetadata = fieldsMetadata.find( - // @ts-expect-error legacy noImplicitAny - (field) => field.id === relationFieldMetadataId, - ); - - expect(updatedRelationFieldMetadata.name).toBe(HOUSE_NAME_SINGULAR); - expect(updatedRelationFieldMetadata.label).toBe(HOUSE_LABEL_SINGULAR); - }); }); - it('3. should fail when trying to rename object to "name"', async () => { - // Act & Assert - const { errors } = await updateOneObjectMetadata({ - gqlFields: ` - nameSingular - labelSingular - namePlural - labelPlural - `, + it('3. should delete custom object', async () => { + await updateOneObjectMetadata({ + expectToFail: false, input: { idToUpdate: listingObjectId, updatePayload: { - nameSingular: 'name', + isActive: false, }, }, - expectToFail: true, }); - // Assert that an error was thrown - expect(errors).toBeDefined(); - expect(errors).toHaveLength(1); - expect(errors[0].extensions.userFriendlyMessage).toContain( - 'Name "name" is not available', - ); - }); - - it('4. should delete custom object', async () => { const { data } = await deleteOneObjectMetadata({ input: { idToDelete: listingObjectId, diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/successful-create-one-object-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/successful-create-one-object-metadata.integration-spec.ts index 6acb31cd37..96e8d1b1e3 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/successful-create-one-object-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/successful-create-one-object-metadata.integration-spec.ts @@ -1,7 +1,9 @@ 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 { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { type EachTestingContext } from 'twenty-shared/testing'; +import { isDefined } from 'twenty-shared/utils'; import { type CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; @@ -19,20 +21,39 @@ const successfulObjectMetadataItemCreateOneUseCase: CreateOneObjectMetadataItemT title: 'with basic input', context: {}, }, - // TODO populate ]; const allTestsUseCases = [...successfulObjectMetadataItemCreateOneUseCase]; describe('Object metadata creation should succeed', () => { + let createdObjectId: string | undefined; + + afterEach(async () => { + if (!isDefined(createdObjectId)) { + return; + } + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectId, + updatePayload: { + isActive: false, + }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: createdObjectId }, + }); + }); + it.each(allTestsUseCases)('$title', async ({ context }) => { const { data } = await createOneObjectMetadata({ + expectToFail: false, input: getMockCreateObjectInput(context), }); expect(data.createOneObject.id).toBeDefined(); - await deleteOneObjectMetadata({ - input: { idToDelete: data.createOneObject.id }, - }).catch(); + createdObjectId = data.createOneObject.id; }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util.ts index 1e1301c582..e13f915fed 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util.ts @@ -13,7 +13,7 @@ import { type ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metad export const createOneObjectMetadata = async ({ input, gqlFields, - expectToFail, + expectToFail = false, }: PerformMetadataQueryParams): CommonResponseBody<{ createOneObject: ObjectMetadataDTO; }> => { diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util.ts index accf7ebad2..5daeec4c49 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util.ts @@ -10,7 +10,7 @@ import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/ export const deleteOneObjectMetadata = async ({ input, gqlFields, - expectToFail, + expectToFail = false, }: PerformMetadataQueryParams) => { const graphqlOperation = deleteOneObjectMetadataQueryFactory({ input, diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util.ts index 450a901de5..3826683f68 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util.ts @@ -24,6 +24,7 @@ export const forceCreateOneObjectMetadata = async ({ input: CreateOneObjectFactoryInput; }) => { const result = await createOneObjectMetadata({ + expectToFail: null, input: { labelSingular, labelPlural, diff --git a/packages/twenty-server/test/integration/metadata/types/perform-metadata-query.type.ts b/packages/twenty-server/test/integration/metadata/types/perform-metadata-query.type.ts index b446c17824..db0ba48267 100644 --- a/packages/twenty-server/test/integration/metadata/types/perform-metadata-query.type.ts +++ b/packages/twenty-server/test/integration/metadata/types/perform-metadata-query.type.ts @@ -1,5 +1,5 @@ export type PerformMetadataQueryParams = { input: T; gqlFields?: string; - expectToFail?: boolean; + expectToFail?: boolean | null; }; diff --git a/packages/twenty-server/test/integration/rest/suites/page-layout-tab.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/page-layout-tab.integration-spec.ts index 4e689dee32..b3e724b3af 100644 --- a/packages/twenty-server/test/integration/rest/suites/page-layout-tab.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/page-layout-tab.integration-spec.ts @@ -2,6 +2,7 @@ import { TEST_NOT_EXISTING_PAGE_LAYOUT_ID } from 'test/integration/constants/tes import { TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID } from 'test/integration/constants/test-page-layout-tab-ids.constants'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { createTestPageLayoutWithRestApi, @@ -59,6 +60,15 @@ describe('Page Layout Tab REST API', () => { afterAll(async () => { await deleteTestPageLayoutWithRestApi(testPageLayoutId); + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/rest/suites/page-layout-widget.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/page-layout-widget.integration-spec.ts index 91580624b3..768778d946 100644 --- a/packages/twenty-server/test/integration/rest/suites/page-layout-widget.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/page-layout-widget.integration-spec.ts @@ -2,6 +2,7 @@ import { TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID } from 'test/integration/constants import { TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID } from 'test/integration/constants/test-page-layout-widget-ids.constants'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { createTestPageLayoutWithRestApi, @@ -74,6 +75,15 @@ describe('Page Layout Widget REST API', () => { afterAll(async () => { await deleteTestPageLayoutTabWithRestApi(testPageLayoutTabId); await deleteTestPageLayoutWithRestApi(testPageLayoutId); + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/rest/suites/page-layout.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/page-layout.integration-spec.ts index eda76a5f91..5d303d3a2f 100644 --- a/packages/twenty-server/test/integration/rest/suites/page-layout.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/page-layout.integration-spec.ts @@ -1,6 +1,7 @@ import { TEST_NOT_EXISTING_PAGE_LAYOUT_ID } from 'test/integration/constants/test-page-layout-ids.constants'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { createTestPageLayoutWithRestApi, @@ -44,6 +45,15 @@ describe('Page Layout REST API', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/rest/suites/view-field.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-field.integration-spec.ts index 8ff8e20d29..3f5a4d0bd6 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-field.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-field.integration-spec.ts @@ -3,7 +3,9 @@ import { TEST_VIEW_1_ID, } from 'test/integration/constants/test-view-ids.constants'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; +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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, @@ -20,7 +22,6 @@ import { cleanupViewRecords, } from 'test/integration/utils/view-test.util'; import { FieldMetadataType } from 'twenty-shared/types'; -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { generateViewFieldExceptionMessage, @@ -76,6 +77,15 @@ describe('View Field REST API', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ expectToFail: false, input: { idToDelete: testObjectMetadataId }, diff --git a/packages/twenty-server/test/integration/rest/suites/view-filter-group.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-filter-group.integration-spec.ts index 9d88674ed1..8d9c1574f1 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-filter-group.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-filter-group.integration-spec.ts @@ -4,6 +4,7 @@ import { } from 'test/integration/constants/test-view-ids.constants'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, @@ -31,10 +32,6 @@ describe('View Filter Group REST API', () => { let testObjectMetadataId: string; beforeAll(async () => { - await deleteOneObjectMetadata({ - input: { idToDelete: testObjectMetadataId }, - }); - const { data: { createOneObject: { id: objectMetadataId }, @@ -53,6 +50,15 @@ describe('View Filter Group REST API', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/rest/suites/view-filter.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-filter.integration-spec.ts index 511422a48e..4b9b5db0dd 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-filter.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-filter.integration-spec.ts @@ -5,6 +5,7 @@ import { import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, @@ -70,6 +71,15 @@ describe('View Filter REST API', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/rest/suites/view-group.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-group.integration-spec.ts index fe24c3f25f..d630342d9d 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-group.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-group.integration-spec.ts @@ -5,6 +5,7 @@ import { import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, @@ -34,15 +35,12 @@ describe('View Group REST API', () => { let testFieldMetadataId: string; beforeAll(async () => { - await deleteOneObjectMetadata({ - input: { idToDelete: testObjectMetadataId }, - }); - const { data: { createOneObject: { id: objectMetadataId }, }, } = await createOneObjectMetadata({ + expectToFail: false, input: { nameSingular: 'myTestObject', namePlural: 'myTestObjects', @@ -80,6 +78,15 @@ describe('View Group REST API', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/rest/suites/view-sort.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-sort.integration-spec.ts index 88f18df320..0597ddd80c 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-sort.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-sort.integration-spec.ts @@ -5,6 +5,7 @@ import { import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, @@ -75,6 +76,15 @@ describe('View Sort REST API', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, }); diff --git a/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts index 6eb6192d7c..9d872ccb95 100644 --- a/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts @@ -1,6 +1,7 @@ import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; 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 { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, @@ -42,6 +43,15 @@ describe('View REST API', () => { }); afterAll(async () => { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: testObjectMetadataId, + updatePayload: { + isActive: false, + }, + }, + }); await deleteOneObjectMetadata({ input: { idToDelete: testObjectMetadataId }, });