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
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { isDefined } from 'class-validator';
|
import isEmpty from 'lodash.isempty';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
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';
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
|
import { isDefined } from 'src/utils/is-defined';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IndexMetadataService {
|
export class IndexMetadataService {
|
||||||
@ -43,6 +44,10 @@ export class IndexMetadataService {
|
|||||||
(fieldMetadata) => fieldMetadata.name as string,
|
(fieldMetadata) => fieldMetadata.name as string,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isEmpty(columnNames)) {
|
||||||
|
throw new Error('Column names must not be empty');
|
||||||
|
}
|
||||||
|
|
||||||
const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`;
|
const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`;
|
||||||
|
|
||||||
let result: IndexMetadataEntity;
|
let result: IndexMetadataEntity;
|
||||||
@ -98,6 +103,44 @@ export class IndexMetadataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteIndexMetadata(
|
||||||
|
workspaceId: string,
|
||||||
|
objectMetadata: ObjectMetadataEntity,
|
||||||
|
fieldMetadataToIndex: Partial<FieldMetadataEntity>[],
|
||||||
|
) {
|
||||||
|
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(
|
async createIndexCreationMigration(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
objectMetadata: ObjectMetadataEntity,
|
objectMetadata: ObjectMetadataEntity,
|
||||||
|
|||||||
@ -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 { 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 { 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 { 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 {
|
import {
|
||||||
RelationMetadataEntity,
|
RelationMetadataEntity,
|
||||||
@ -137,22 +138,20 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedFieldMetadata = toObjectMetadata.fields.find(
|
const deletedAtFieldMetadata = toObjectMetadata.fields.find(
|
||||||
(fieldMetadata) =>
|
(fieldMetadata) =>
|
||||||
fieldMetadata.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
|
fieldMetadata.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!deletedFieldMetadata) {
|
this.throwIfDeletedAtFieldMetadataNotFound(deletedAtFieldMetadata);
|
||||||
throw new RelationMetadataException(
|
|
||||||
`Deleted field metadata not found`,
|
|
||||||
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.indexMetadataService.createIndexMetadata(
|
await this.indexMetadataService.createIndexMetadata(
|
||||||
relationMetadataInput.workspaceId,
|
relationMetadataInput.workspaceId,
|
||||||
toObjectMetadata,
|
toObjectMetadata,
|
||||||
[foreignKeyFieldMetadata, deletedFieldMetadata],
|
[
|
||||||
|
foreignKeyFieldMetadata,
|
||||||
|
deletedAtFieldMetadata as FieldMetadataEntity<'default'>,
|
||||||
|
],
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@ -441,6 +440,24 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
columnName,
|
columnName,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const deletedAtFieldMetadata = await this.fieldMetadataRepository.findOneBy(
|
||||||
|
{
|
||||||
|
objectMetadataId: relationMetadata.toObjectMetadataId,
|
||||||
|
name: 'deletedAt',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.throwIfDeletedAtFieldMetadataNotFound(deletedAtFieldMetadata);
|
||||||
|
|
||||||
|
await this.indexMetadataService.deleteIndexMetadata(
|
||||||
|
workspaceId,
|
||||||
|
relationMetadata.toObjectMetadata,
|
||||||
|
[
|
||||||
|
foreignKeyFieldMetadata,
|
||||||
|
deletedAtFieldMetadata as FieldMetadataEntity<'default'>,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||||
relationMetadata.workspaceId,
|
relationMetadata.workspaceId,
|
||||||
);
|
);
|
||||||
@ -554,4 +571,15 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private throwIfDeletedAtFieldMetadataNotFound(
|
||||||
|
deletedAtFieldMetadata?: FieldMetadataEntity<'default'> | null,
|
||||||
|
) {
|
||||||
|
if (!isDefined(deletedAtFieldMetadata)) {
|
||||||
|
throw new RelationMetadataException(
|
||||||
|
`Deleted field metadata not found`,
|
||||||
|
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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<CreateObjectInput, 'workspaceId' | 'dataSourceId'> };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createOneObjectMetadataFactory = ({
|
||||||
|
gqlFields,
|
||||||
|
input,
|
||||||
|
}: CreateOneObjectFactoryParams) => ({
|
||||||
|
query: gql`
|
||||||
|
mutation CreateOneObjectMetadataItem($input: CreateOneObjectInput!) {
|
||||||
|
createOneObject(input: $input) {
|
||||||
|
${gqlFields}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -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<CreateRelationInput, 'workspaceId'>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createOneRelationMetadataFactory = ({
|
||||||
|
gqlFields,
|
||||||
|
input,
|
||||||
|
}: CreateOneRelationFactoryParams) => ({
|
||||||
|
query: gql`
|
||||||
|
mutation CreateOneRelationMetadata($input: CreateOneRelationInput!) {
|
||||||
|
createOneRelation(input: $input) {
|
||||||
|
${gqlFields}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { ASTNode, print } from 'graphql';
|
||||||
|
import request from 'supertest';
|
||||||
|
|
||||||
|
type GraphqlOperation = {
|
||||||
|
query: ASTNode;
|
||||||
|
variables?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 || {},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user