From 7bde2006c52159ba183954f174f12efc51dd4612 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:00:36 +0100 Subject: [PATCH] Add integration tests for /metadata + fix relation deletion (#8706) In this PR 1. Add integration tests for /metadata (master issue: https://github.com/twentyhq/twenty/issues/8719) 2. Fix relation deletion: index on "from" object was not deleted, impeding creation of a new relation between the same two objects A and B after relation between A and B was deleted --- .../index-metadata/index-metadata.service.ts | 45 ++- .../relation-metadata.service.ts | 44 ++- .../rename-custom-object.integration-spec.ts | 318 ++++++++++++++++++ ...create-one-object-metadata-factory.util.ts | 24 ++ ...eate-one-relation-metadata-factory.util.ts | 26 ++ ...delete-one-object-metadata-factory.util.ts | 20 ++ ...lete-one-relation-metadata-factory.util.ts | 22 ++ .../utils/fields-metadata-factory.util.ts | 30 ++ .../utils/make-metadata-api-request.util.ts | 19 ++ .../utils/objects-metadata-factory.util.ts | 30 ++ ...update-one-object-metadata-factory.util.ts | 28 ++ 11 files changed, 597 insertions(+), 9 deletions(-) create mode 100644 packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/utils/create-one-object-metadata-factory.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/utils/create-one-relation-metadata-factory.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/utils/delete-one-object-metadata-factory.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/utils/delete-one-relation-metadata-factory.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/utils/fields-metadata-factory.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/utils/make-metadata-api-request.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/utils/objects-metadata-factory.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/utils/update-one-object-metadata-factory.util.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts index 6b8ff8fcb..e6d095e9e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { isDefined } from 'class-validator'; +import isEmpty from 'lodash.isempty'; import { Repository } from 'typeorm'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -19,6 +19,7 @@ import { } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; +import { isDefined } from 'src/utils/is-defined'; @Injectable() export class IndexMetadataService { @@ -43,6 +44,10 @@ export class IndexMetadataService { (fieldMetadata) => fieldMetadata.name as string, ); + if (isEmpty(columnNames)) { + throw new Error('Column names must not be empty'); + } + const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`; let result: IndexMetadataEntity; @@ -98,6 +103,44 @@ export class IndexMetadataService { ); } + async deleteIndexMetadata( + workspaceId: string, + objectMetadata: ObjectMetadataEntity, + fieldMetadataToIndex: Partial[], + ) { + const tableName = computeObjectTargetTable(objectMetadata); + + const columnNames: string[] = fieldMetadataToIndex.map( + (fieldMetadata) => fieldMetadata.name as string, + ); + + if (isEmpty(columnNames)) { + throw new Error('Column names must not be empty'); + } + + const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`; + + const indexMetadata = await this.indexMetadataRepository.findOne({ + where: { + name: indexName, + objectMetadataId: objectMetadata.id, + workspaceId, + }, + }); + + if (!indexMetadata) { + throw new Error(`Index metadata with name ${indexName} not found`); + } + + try { + await this.indexMetadataRepository.delete(indexMetadata.id); + } catch (error) { + throw new Error( + `Failed to delete index metadata with name ${indexName} (error: ${error.message})`, + ); + } + } + async createIndexCreationMigration( workspaceId: string, objectMetadata: ObjectMetadataEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index 2b149b73f..e5fb7e56f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -36,6 +36,7 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; import { BASE_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { isDefined } from 'src/utils/is-defined'; import { RelationMetadataEntity, @@ -137,22 +138,20 @@ export class RelationMetadataService extends TypeOrmQueryService fieldMetadata.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt, ); - if (!deletedFieldMetadata) { - throw new RelationMetadataException( - `Deleted field metadata not found`, - RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND, - ); - } + this.throwIfDeletedAtFieldMetadataNotFound(deletedAtFieldMetadata); await this.indexMetadataService.createIndexMetadata( relationMetadataInput.workspaceId, toObjectMetadata, - [foreignKeyFieldMetadata, deletedFieldMetadata], + [ + foreignKeyFieldMetadata, + deletedAtFieldMetadata as FieldMetadataEntity<'default'>, + ], false, false, ); @@ -441,6 +440,24 @@ export class RelationMetadataService extends TypeOrmQueryService, + ], + ); + await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( relationMetadata.workspaceId, ); @@ -554,4 +571,15 @@ export class RelationMetadataService extends TypeOrmQueryService | null, + ) { + if (!isDefined(deletedAtFieldMetadata)) { + throw new RelationMetadataException( + `Deleted field metadata not found`, + RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND, + ); + } + } } 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 new file mode 100644 index 000000000..15507473f --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts @@ -0,0 +1,318 @@ +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/utils/create-one-object-metadata-factory.util'; +import { createOneRelationMetadataFactory } from 'test/integration/metadata/suites/utils/create-one-relation-metadata-factory.util'; +import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/utils/delete-one-object-metadata-factory.util'; +import { deleteOneRelationMetadataItemFactory } from 'test/integration/metadata/suites/utils/delete-one-relation-metadata-factory.util'; +import { fieldsMetadataFactory } from 'test/integration/metadata/suites/utils/fields-metadata-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { objectsMetadataFactory } from 'test/integration/metadata/suites/utils/objects-metadata-factory.util'; +import { updateOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/utils/update-one-object-metadata-factory.util'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +const LISTING_NAME_SINGULAR = 'listing'; + +describe('Custom object renaming', () => { + let listingObjectId = ''; + let customRelationId = ''; + + const STANDARD_OBJECT_RELATIONS = [ + 'noteTarget', + 'attachment', + 'favorite', + 'taskTarget', + 'timelineActivity', + ]; + + const standardObjectRelationsMap = STANDARD_OBJECT_RELATIONS.reduce( + (acc, relation) => ({ + ...acc, + [relation]: { + objectMetadataId: '', + foreignKeyFieldMetadataId: '', + relationFieldMetadataId: '', + }, + }), + {}, + ); + + const standardObjectsGraphqlOperation = objectsMetadataFactory({ + gqlFields: ` + id + nameSingular + `, + input: { + filter: { + isCustom: { isNot: true }, + }, + paging: { first: 1000 }, + }, + }); + + const fieldsGraphqlOperation = fieldsMetadataFactory({ + gqlFields: ` + id + name + label + type + object { + id + } + `, + input: { + filter: {}, + paging: { first: 1000 }, + }, + }); + + const fillStandardObjectRelationsMapObjectMetadataId = (standardObjects) => { + STANDARD_OBJECT_RELATIONS.forEach((relation) => { + standardObjectRelationsMap[relation].objectMetadataId = + standardObjects.body.data.objects.edges.find( + (object) => object.node.nameSingular === relation, + ).node.id; + }); + }; + + it('1. should create one custom object with standard relations', async () => { + // Arrange + const standardObjects = await makeMetadataAPIRequest( + standardObjectsGraphqlOperation, + ); + + fillStandardObjectRelationsMapObjectMetadataId(standardObjects); + + const LISTING_OBJECT = { + namePlural: 'listings', + nameSingular: LISTING_NAME_SINGULAR, + labelPlural: 'Listings', + labelSingular: 'Listing', + description: 'Listing object', + icon: 'IconListNumbers', + isLabelSyncedWithName: false, + }; + + // Act + const graphqlOperation = createOneObjectMetadataFactory({ + input: { object: LISTING_OBJECT }, + gqlFields: ` + id + nameSingular + `, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation); + + // Assert + expect(response.body.data.createOneObject.nameSingular).toBe( + LISTING_NAME_SINGULAR, + ); + + listingObjectId = response.body.data.createOneObject.id; + + const fields = await makeMetadataAPIRequest(fieldsGraphqlOperation); + + const foreignKeyFieldsMetadataForListing = fields.body.data.fields.edges + .filter((field) => field.node.name === `${LISTING_NAME_SINGULAR}Id`) + .map((field) => field.node); + + const relationFieldsMetadataForListing = fields.body.data.fields.edges + .filter( + (field) => + field.node.name === `${LISTING_NAME_SINGULAR}` && + field.node.type === FieldMetadataType.RELATION, + ) + .map((field) => field.node); + + expect(foreignKeyFieldsMetadataForListing.length).toBe(5); + + STANDARD_OBJECT_RELATIONS.forEach((relation) => { + // foreignKey field + const foreignKeyFieldMetadataId = foreignKeyFieldsMetadataForListing.find( + (field) => + field.object.id === + standardObjectRelationsMap[relation].objectMetadataId, + ).id; + + expect(foreignKeyFieldMetadataId).not.toBeUndefined(); + + standardObjectRelationsMap[relation].foreignKeyFieldMetadataId = + foreignKeyFieldMetadataId; + + // relation field + const relationFieldMetadataId = relationFieldsMetadataForListing.find( + (field) => + field.object.id === + standardObjectRelationsMap[relation].objectMetadataId, + ).id; + + expect(relationFieldMetadataId).not.toBeUndefined(); + + standardObjectRelationsMap[relation].relationFieldMetadataId = + relationFieldMetadataId; + }); + }); + + let relationFieldMetadataOnPersonId = ''; + const RELATION_FROM_NAME = 'guest'; + + it('2. should create a custom relation with the custom object', async () => { + // Arrange + const standardObjects = await makeMetadataAPIRequest( + standardObjectsGraphqlOperation, + ); + const personObjectId = standardObjects.body.data.objects.edges.find( + (object) => object.node.nameSingular === 'person', + ).node.id; + + // Act + const createRelationGraphqlOperation = createOneRelationMetadataFactory({ + input: { + relation: { + fromDescription: '', + fromIcon: 'IconRelationOneToMany', + fromLabel: 'Guest', + fromName: RELATION_FROM_NAME, + fromObjectMetadataId: listingObjectId, + relationType: RelationMetadataType.ONE_TO_MANY, + toDescription: undefined, + toIcon: 'IconListNumbers', + toLabel: 'Property', + toName: 'property', + toObjectMetadataId: personObjectId, + }, + }, + gqlFields: ` + id + fromFieldMetadataId + `, + }); + + const relationResponse = await makeMetadataAPIRequest( + createRelationGraphqlOperation, + ); + + // Assert + customRelationId = relationResponse.body.data.createOneRelation.id; + + relationFieldMetadataOnPersonId = + relationResponse.body.data.createOneRelation.fromFieldMetadataId; + }); + + it('3. should rename custom object', async () => { + // Arrange + const HOUSE_NAME_SINGULAR = 'house'; + const HOUSE_NAME_PLURAL = 'houses'; + const HOUSE_LABEL_SINGULAR = 'House'; + const HOUSE_LABEL_PLURAL = 'Houses'; + const updateListingNameGraphqlOperation = + updateOneObjectMetadataItemFactory({ + gqlFields: ` + nameSingular + labelSingular + namePlural + labelPlural + `, + input: { + idToUpdate: listingObjectId, + updatePayload: { + nameSingular: HOUSE_NAME_SINGULAR, + namePlural: HOUSE_NAME_PLURAL, + labelSingular: HOUSE_LABEL_SINGULAR, + labelPlural: HOUSE_LABEL_PLURAL, + }, + }, + }); + + // Act + const updateListingNameResponse = await makeMetadataAPIRequest( + updateListingNameGraphqlOperation, + ); + + // Assert + expect( + updateListingNameResponse.body.data.updateOneObject.nameSingular, + ).toBe(HOUSE_NAME_SINGULAR); + expect(updateListingNameResponse.body.data.updateOneObject.namePlural).toBe( + HOUSE_NAME_PLURAL, + ); + expect( + updateListingNameResponse.body.data.updateOneObject.labelSingular, + ).toBe(HOUSE_LABEL_SINGULAR); + expect( + updateListingNameResponse.body.data.updateOneObject.labelPlural, + ).toBe(HOUSE_LABEL_PLURAL); + + const fieldsResponse = await makeMetadataAPIRequest(fieldsGraphqlOperation); + + const fieldsMetadata = fieldsResponse.body.data.fields.edges.map( + (field) => field.node, + ); + + expect( + fieldsMetadata.find( + (field) => field.name === `${LISTING_NAME_SINGULAR}Id`, + ), + ).toBeUndefined(); + + // standard relations have been updated + STANDARD_OBJECT_RELATIONS.forEach((relation) => { + // foreignKey field + const foreignKeyFieldMetadataId = + standardObjectRelationsMap[relation].foreignKeyFieldMetadataId; + + const updatedForeignKeyFieldMetadata = fieldsMetadata.find( + (field) => field.id === foreignKeyFieldMetadataId, + ); + + expect(updatedForeignKeyFieldMetadata.name).toBe( + `${HOUSE_NAME_SINGULAR}Id`, + ); + expect(updatedForeignKeyFieldMetadata.label).toBe( + 'House ID (foreign key)', + ); + + // relation field + const relationFieldMetadataId = + standardObjectRelationsMap[relation].relationFieldMetadataId; + + const updatedRelationFieldMetadataId = fieldsMetadata.find( + (field) => field.id === relationFieldMetadataId, + ); + + expect(updatedRelationFieldMetadataId.name).toBe(HOUSE_NAME_SINGULAR); + expect(updatedRelationFieldMetadataId.label).toBe(HOUSE_LABEL_SINGULAR); + }); + + // custom relation are unchanged + const updatedRelationFieldMetadata = fieldsMetadata.find( + (field) => field.id === relationFieldMetadataOnPersonId, + ); + + expect(updatedRelationFieldMetadata.name).toBe(RELATION_FROM_NAME); + }); + + it('4. should delete custom relation', async () => { + const graphqlOperation = deleteOneRelationMetadataItemFactory({ + idToDelete: customRelationId, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation); + + const deleteRelationResponse = response.body.data.deleteOneRelation; + + expect(deleteRelationResponse.id).toBe(customRelationId); + }); + + it('5. should delete custom object', async () => { + const graphqlOperation = deleteOneObjectMetadataItemFactory({ + idToDelete: listingObjectId, + }); + + const response = await makeGraphqlAPIRequest(graphqlOperation); + + const deleteListingResponse = response.body.data.deleteOneObject; + + expect(deleteListingResponse.id).toBe(listingObjectId); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/create-one-object-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/utils/create-one-object-metadata-factory.util.ts new file mode 100644 index 000000000..473e3bd95 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/utils/create-one-object-metadata-factory.util.ts @@ -0,0 +1,24 @@ +import gql from 'graphql-tag'; + +import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; + +type CreateOneObjectFactoryParams = { + gqlFields: string; + input?: { object: Omit }; +}; + +export const createOneObjectMetadataFactory = ({ + gqlFields, + input, +}: CreateOneObjectFactoryParams) => ({ + query: gql` + mutation CreateOneObjectMetadataItem($input: CreateOneObjectInput!) { + createOneObject(input: $input) { + ${gqlFields} + } + } + `, + variables: { + input, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/create-one-relation-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/utils/create-one-relation-metadata-factory.util.ts new file mode 100644 index 000000000..31fe098f6 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/utils/create-one-relation-metadata-factory.util.ts @@ -0,0 +1,26 @@ +import gql from 'graphql-tag'; + +import { CreateRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/create-relation.input'; + +type CreateOneRelationFactoryParams = { + gqlFields: string; + input?: { + relation: Omit; + }; +}; + +export const createOneRelationMetadataFactory = ({ + gqlFields, + input, +}: CreateOneRelationFactoryParams) => ({ + query: gql` + mutation CreateOneRelationMetadata($input: CreateOneRelationInput!) { + createOneRelation(input: $input) { + ${gqlFields} + } + } + `, + variables: { + input, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/delete-one-object-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/utils/delete-one-object-metadata-factory.util.ts new file mode 100644 index 000000000..439df3bc3 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/utils/delete-one-object-metadata-factory.util.ts @@ -0,0 +1,20 @@ +import gql from 'graphql-tag'; + +type DeleteOneObjectFactoryParams = { + idToDelete: string; +}; + +export const deleteOneObjectMetadataItemFactory = ({ + idToDelete, +}: DeleteOneObjectFactoryParams) => ({ + query: gql` + mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) { + deleteOneObject(input: { id: $idToDelete }) { + id + } + } + `, + variables: { + idToDelete, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/delete-one-relation-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/utils/delete-one-relation-metadata-factory.util.ts new file mode 100644 index 000000000..3487bcbd7 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/utils/delete-one-relation-metadata-factory.util.ts @@ -0,0 +1,22 @@ +import gql from 'graphql-tag'; + +type DeleteOneRelationFactoryParams = { + idToDelete: string; +}; + +export const deleteOneRelationMetadataItemFactory = ({ + idToDelete, +}: DeleteOneRelationFactoryParams) => ({ + query: gql` + mutation DeleteOneRelation($input: DeleteOneRelationInput!) { + deleteOneRelation(input: $input) { + id + } + } + `, + variables: { + input: { + id: idToDelete, + }, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/fields-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/utils/fields-metadata-factory.util.ts new file mode 100644 index 000000000..97eb83291 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/utils/fields-metadata-factory.util.ts @@ -0,0 +1,30 @@ +import gql from 'graphql-tag'; + +type FieldsFactoryParams = { + gqlFields: string; + input: { + filter: object; + paging: object; + }; +}; + +export const fieldsMetadataFactory = ({ + gqlFields, + input, +}: FieldsFactoryParams) => ({ + query: gql` + query FieldsMetadata($filter: fieldFilter!, $paging: CursorPaging!) { + fields(filter: $filter, paging: $paging) { + edges { + node { + ${gqlFields} + } + } + } + } + `, + variables: { + filter: input.filter, + paging: input.paging, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/make-metadata-api-request.util.ts b/packages/twenty-server/test/integration/metadata/suites/utils/make-metadata-api-request.util.ts new file mode 100644 index 000000000..1a6801a75 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/utils/make-metadata-api-request.util.ts @@ -0,0 +1,19 @@ +import { ASTNode, print } from 'graphql'; +import request from 'supertest'; + +type GraphqlOperation = { + query: ASTNode; + variables?: Record; +}; + +export const makeMetadataAPIRequest = (graphqlOperation: GraphqlOperation) => { + const client = request(`http://localhost:${APP_PORT}`); + + return client + .post('/metadata') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send({ + query: print(graphqlOperation.query), + variables: graphqlOperation.variables || {}, + }); +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/objects-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/utils/objects-metadata-factory.util.ts new file mode 100644 index 000000000..e182c0a1d --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/utils/objects-metadata-factory.util.ts @@ -0,0 +1,30 @@ +import gql from 'graphql-tag'; + +type ObjectsFactoryParams = { + gqlFields: string; + input: { + filter: object; + paging: object; + }; +}; + +export const objectsMetadataFactory = ({ + gqlFields, + input, +}: ObjectsFactoryParams) => ({ + query: gql` + query ObjectsMetadata($filter: objectFilter!, $paging: CursorPaging!) { + objects(filter: $filter, paging: $paging) { + edges { + node { + ${gqlFields} + } + } + } + } + `, + variables: { + filter: input.filter, + paging: input.paging, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/update-one-object-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/utils/update-one-object-metadata-factory.util.ts new file mode 100644 index 000000000..28ac92420 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/utils/update-one-object-metadata-factory.util.ts @@ -0,0 +1,28 @@ +import gql from 'graphql-tag'; + +import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; + +type UpdateOneObjectFactoryParams = { + gqlFields: string; + input: { + idToUpdate: string; + updatePayload: UpdateObjectPayload; + }; +}; + +export const updateOneObjectMetadataItemFactory = ({ + gqlFields, + input, +}: UpdateOneObjectFactoryParams) => ({ + query: gql` + mutation UpdateOneObjectMetadataItem($idToUpdate: UUID!, $updatePayload: UpdateObjectPayload!) { + updateOneObject(input: {id: $idToUpdate, update: $updatePayload}) { + ${gqlFields} + } + } + `, + variables: { + idToUpdate: input.idToUpdate, + updatePayload: input.updatePayload, + }, +});