0d5fedfb0a
Fixes https://github.com/twentyhq/twenty/issues/13577. When naming or renaming an object, we are already checking that the name was available compared to other existing objects. But we also need to check that the relation fields that will be created or updated are available as well: we create relation fields on Attachment, Note, Favorite, Task and TimelineActivites, that bear the name of the object. Thus, it is not possible to create an object that has the same name as one of the fields on Attachment, Note etc: name, createdAt, ... are not valid names.
227 lines
6.9 KiB
TypeScript
227 lines
6.9 KiB
TypeScript
import { findManyFieldsMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-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 { findManyObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-query-factory.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';
|
|
|
|
describe('Custom object renaming', () => {
|
|
let listingObjectId = '';
|
|
|
|
const STANDARD_OBJECT_RELATIONS = [
|
|
'noteTarget',
|
|
'attachment',
|
|
'favorite',
|
|
'taskTarget',
|
|
'timelineActivity',
|
|
];
|
|
|
|
const standardObjectRelationsMap = STANDARD_OBJECT_RELATIONS.reduce(
|
|
(acc, relation) => ({
|
|
...acc,
|
|
[relation]: {
|
|
objectMetadataId: '',
|
|
foreignKeyFieldMetadataId: '',
|
|
relationFieldMetadataId: '',
|
|
},
|
|
}),
|
|
{},
|
|
);
|
|
|
|
const standardObjectsGraphqlOperation = findManyObjectMetadataQueryFactory({
|
|
gqlFields: `
|
|
id
|
|
nameSingular
|
|
`,
|
|
input: {
|
|
filter: {
|
|
isCustom: { isNot: true },
|
|
},
|
|
paging: { first: 1000 },
|
|
},
|
|
});
|
|
|
|
const fieldsGraphqlOperation = findManyFieldsMetadataQueryFactory({
|
|
gqlFields: `
|
|
id
|
|
name
|
|
label
|
|
type
|
|
object {
|
|
id
|
|
}
|
|
`,
|
|
input: {
|
|
filter: {},
|
|
paging: { first: 1000 },
|
|
},
|
|
});
|
|
|
|
// @ts-expect-error legacy noImplicitAny
|
|
const fillStandardObjectRelationsMapObjectMetadataId = (standardObjects) => {
|
|
STANDARD_OBJECT_RELATIONS.forEach((relation) => {
|
|
// @ts-expect-error legacy noImplicitAny
|
|
standardObjectRelationsMap[relation].objectMetadataId =
|
|
standardObjects.body.data.objects.edges.find(
|
|
// @ts-expect-error legacy noImplicitAny
|
|
(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 CUSTOM_OBJECT = {
|
|
namePlural: 'customObjectNamePlural',
|
|
nameSingular: 'customObjectNameSingular',
|
|
labelPlural: 'customObjectLabelPlural',
|
|
labelSingular: 'customObjectLabelSingular',
|
|
description: 'Custom object description',
|
|
icon: 'IconListNumbers',
|
|
isLabelSyncedWithName: false,
|
|
};
|
|
|
|
// Act
|
|
const { data } = await createOneObjectMetadata({
|
|
input: CUSTOM_OBJECT,
|
|
gqlFields: `
|
|
id
|
|
nameSingular
|
|
`,
|
|
});
|
|
|
|
// Assert
|
|
expect(data.createOneObject.nameSingular).toBe(CUSTOM_OBJECT.nameSingular);
|
|
|
|
listingObjectId = data.createOneObject.id;
|
|
|
|
const fields = await makeMetadataAPIRequest(fieldsGraphqlOperation);
|
|
|
|
const relationFieldsMetadataForListing = fields.body.data.fields.edges
|
|
.filter(
|
|
// @ts-expect-error legacy noImplicitAny
|
|
(field) =>
|
|
field.node.name === `${CUSTOM_OBJECT.nameSingular}` &&
|
|
field.node.type === FieldMetadataType.RELATION,
|
|
)
|
|
// @ts-expect-error legacy noImplicitAny
|
|
.map((field) => field.node);
|
|
|
|
STANDARD_OBJECT_RELATIONS.forEach((relation) => {
|
|
// relation field
|
|
const relationFieldMetadataId = relationFieldsMetadataForListing.find(
|
|
// @ts-expect-error legacy noImplicitAny
|
|
(field) =>
|
|
field.object.id ===
|
|
// @ts-expect-error legacy noImplicitAny
|
|
standardObjectRelationsMap[relation].objectMetadataId,
|
|
).id;
|
|
|
|
expect(relationFieldMetadataId).not.toBeUndefined();
|
|
|
|
// @ts-expect-error legacy noImplicitAny
|
|
standardObjectRelationsMap[relation].relationFieldMetadataId =
|
|
relationFieldMetadataId;
|
|
});
|
|
});
|
|
|
|
it('2. 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';
|
|
|
|
// Act
|
|
const { data } = await updateOneObjectMetadata({
|
|
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,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Assert
|
|
expect(data.updateOneObject.nameSingular).toBe(HOUSE_NAME_SINGULAR);
|
|
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
|
|
`,
|
|
input: {
|
|
idToUpdate: listingObjectId,
|
|
updatePayload: {
|
|
nameSingular: 'name',
|
|
},
|
|
},
|
|
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,
|
|
},
|
|
});
|
|
|
|
expect(data.deleteOneObject.id).toBe(listingObjectId);
|
|
});
|
|
});
|