Fix deactivate objects impacts (#11185)
In this PR: - Remove deactivated objects from ActivityTargetInlineCell record picker - Prevent users to deactivate createdAt, updatedAt, deletedAt fields on any objects Still left: - write unit tests on the assert utils - write integration tests on field metadata service - prevent users to deactivate createdAt, updatedAt, deletedAt on FE
This commit is contained in:
@ -28,6 +28,7 @@ export const useOpenActivityTargetInlineCellEditMode = () => {
|
||||
.filter(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.isSearchable &&
|
||||
objectMetadataItem.isActive &&
|
||||
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Task &&
|
||||
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Note &&
|
||||
objectMetadataItem.nameSingular !==
|
||||
|
||||
@ -15,4 +15,5 @@ export enum FieldMetadataExceptionCode {
|
||||
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||
FIELD_METADATA_RELATION_NOT_ENABLED = 'FIELD_METADATA_RELATION_NOT_ENABLED',
|
||||
FIELD_METADATA_RELATION_MALFORMED = 'FIELD_METADATA_RELATION_MALFORMED',
|
||||
LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND = 'LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND',
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ import {
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service';
|
||||
import { assertDoesNotNullifyDefaultValueForNonNullableField } from 'src/engine/metadata-modules/field-metadata/utils/assert-does-not-nullify-default-value-for-non-nullable-field.util';
|
||||
import { checkCanDeactivateFieldOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/check-can-deactivate-field-or-throw';
|
||||
import {
|
||||
computeColumnName,
|
||||
computeCompositeColumnName,
|
||||
@ -154,6 +155,12 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
);
|
||||
}
|
||||
|
||||
if (!objectMetadata.labelIdentifierFieldMetadataId) {
|
||||
throw new FieldMetadataException(
|
||||
'Label identifier field metadata id does not exist',
|
||||
FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
assertMutationNotOnRemoteObject(objectMetadata);
|
||||
|
||||
assertDoesNotNullifyDefaultValueForNonNullableField({
|
||||
@ -161,18 +168,13 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
defaultValueFromUpdate: fieldMetadataInput.defaultValue,
|
||||
});
|
||||
|
||||
if (
|
||||
objectMetadata.labelIdentifierFieldMetadataId ===
|
||||
existingFieldMetadata.id &&
|
||||
fieldMetadataInput.isActive === false
|
||||
) {
|
||||
throw new FieldMetadataException(
|
||||
'Cannot deactivate label identifier field',
|
||||
FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
if (fieldMetadataInput.isActive === false) {
|
||||
checkCanDeactivateFieldOrThrow({
|
||||
labelIdentifierFieldMetadataId:
|
||||
objectMetadata.labelIdentifierFieldMetadataId,
|
||||
existingFieldMetadata,
|
||||
});
|
||||
|
||||
const viewsRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
fieldMetadataInput.workspaceId,
|
||||
|
||||
@ -30,6 +30,8 @@ export interface FieldMetadataInterface<
|
||||
relationTargetObjectMetadataId?: string;
|
||||
relationTargetObjectMetadata?: ObjectMetadataEntity;
|
||||
isCustom?: boolean;
|
||||
isSystem?: boolean;
|
||||
isActive?: boolean;
|
||||
generatedType?: 'STORED' | 'VIRTUAL';
|
||||
asExpression?: string;
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`checkCanDeactivateFieldOrThrow throws if trying to deactivate createdAt field 1`] = `"Cannot deactivate createdAt, updatedAt or deletedAt field"`;
|
||||
|
||||
exports[`checkCanDeactivateFieldOrThrow throws if trying to deactivate deletedAt field 1`] = `"Cannot deactivate createdAt, updatedAt or deletedAt field"`;
|
||||
|
||||
exports[`checkCanDeactivateFieldOrThrow throws if trying to deactivate label identifier field 1`] = `"Cannot deactivate label identifier field"`;
|
||||
|
||||
exports[`checkCanDeactivateFieldOrThrow throws if trying to deactivate system field 1`] = `"Cannot deactivate system field"`;
|
||||
|
||||
exports[`checkCanDeactivateFieldOrThrow throws if trying to deactivate updatedAt field 1`] = `"Cannot deactivate createdAt, updatedAt or deletedAt field"`;
|
||||
@ -0,0 +1,106 @@
|
||||
import { EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { checkCanDeactivateFieldOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/check-can-deactivate-field-or-throw';
|
||||
|
||||
type CheckCanDeactivateFieldOrThrowTestContext = EachTestingContext<{
|
||||
input: Parameters<typeof checkCanDeactivateFieldOrThrow>[0];
|
||||
shouldNotThrow?: true;
|
||||
}>;
|
||||
|
||||
const checkCanDeactivateFieldOrThrowTestCases: CheckCanDeactivateFieldOrThrowTestContext[] =
|
||||
[
|
||||
{
|
||||
title: 'does not throw if nominal case',
|
||||
context: {
|
||||
input: {
|
||||
labelIdentifierFieldMetadataId: 'fieldIdentifierId',
|
||||
existingFieldMetadata: {
|
||||
id: 'myFieldId',
|
||||
isSystem: false,
|
||||
name: 'myFieldName',
|
||||
},
|
||||
},
|
||||
shouldNotThrow: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws if trying to deactivate label identifier field',
|
||||
context: {
|
||||
input: {
|
||||
labelIdentifierFieldMetadataId: 'fieldId',
|
||||
existingFieldMetadata: {
|
||||
id: 'fieldId',
|
||||
isSystem: false,
|
||||
name: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws if trying to deactivate system field',
|
||||
context: {
|
||||
input: {
|
||||
labelIdentifierFieldMetadataId: 'fieldIdentifierId',
|
||||
existingFieldMetadata: {
|
||||
id: 'systemFieldId',
|
||||
isSystem: true,
|
||||
name: 'systemField',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws if trying to deactivate createdAt field',
|
||||
context: {
|
||||
input: {
|
||||
labelIdentifierFieldMetadataId: 'fieldIdentifierId',
|
||||
existingFieldMetadata: {
|
||||
id: 'createdAtId',
|
||||
isSystem: false,
|
||||
name: 'createdAt',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws if trying to deactivate updatedAt field',
|
||||
context: {
|
||||
input: {
|
||||
labelIdentifierFieldMetadataId: 'fieldIdentifierId',
|
||||
existingFieldMetadata: {
|
||||
id: 'updatedAtId',
|
||||
isSystem: false,
|
||||
name: 'updatedAt',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws if trying to deactivate deletedAt field',
|
||||
context: {
|
||||
input: {
|
||||
labelIdentifierFieldMetadataId: 'fieldIdentifierId',
|
||||
existingFieldMetadata: {
|
||||
id: 'deletedAtId',
|
||||
isSystem: false,
|
||||
name: 'deletedAt',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('checkCanDeactivateFieldOrThrow', () => {
|
||||
it.each(checkCanDeactivateFieldOrThrowTestCases)(
|
||||
'$title',
|
||||
({ context: { input, shouldNotThrow } }) => {
|
||||
if (shouldNotThrow) {
|
||||
expect(() => checkCanDeactivateFieldOrThrow(input)).not.toThrow();
|
||||
} else {
|
||||
expect(() =>
|
||||
checkCanDeactivateFieldOrThrow(input),
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -0,0 +1,42 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
|
||||
type CheckCanDeactivateFieldOptions = {
|
||||
labelIdentifierFieldMetadataId: string;
|
||||
existingFieldMetadata: Pick<
|
||||
FieldMetadataInterface,
|
||||
'id' | 'isSystem' | 'name'
|
||||
>;
|
||||
};
|
||||
|
||||
export const checkCanDeactivateFieldOrThrow = ({
|
||||
labelIdentifierFieldMetadataId,
|
||||
existingFieldMetadata,
|
||||
}: CheckCanDeactivateFieldOptions) => {
|
||||
if (existingFieldMetadata.id === labelIdentifierFieldMetadataId) {
|
||||
throw new FieldMetadataException(
|
||||
'Cannot deactivate label identifier field',
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (existingFieldMetadata.isSystem === true) {
|
||||
throw new FieldMetadataException(
|
||||
'Cannot deactivate system field',
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
['deletedAt', 'createdAt', 'updatedAt'].includes(existingFieldMetadata.name)
|
||||
) {
|
||||
throw new FieldMetadataException(
|
||||
'Cannot deactivate createdAt, updatedAt or deletedAt field',
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -30,6 +30,7 @@ export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
|
||||
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED:
|
||||
case FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND:
|
||||
default:
|
||||
throw new InternalServerError(error.message);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getMockCreateObjectInput } from 'test/integration/utils/object-metadata/generate-mock-create-object-metadata-input';
|
||||
import { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input';
|
||||
import { EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||
|
||||
@ -14,9 +14,9 @@ import {
|
||||
LISTING_NAME_PLURAL,
|
||||
LISTING_NAME_SINGULAR,
|
||||
} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { findManyObjectsMetadataItems } from 'test/integration/metadata/suites/object-metadata/utils/find-many-objects-metadata-items.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 { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
|
||||
import { EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
|
||||
@ -46,7 +46,14 @@ describe('SearchResolver', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
try {
|
||||
const objectsMetadata = await findManyObjectsMetadataItems();
|
||||
const objectsMetadata = await findManyObjectMetadata({
|
||||
input: {
|
||||
filter: {},
|
||||
paging: {
|
||||
first: 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
const listingObjectMetadata = objectsMetadata.find(
|
||||
(object) => object.nameSingular === LISTING_NAME_SINGULAR,
|
||||
);
|
||||
@ -56,7 +63,19 @@ describe('SearchResolver', () => {
|
||||
objectMetadataId: listingObjectMetadata.id,
|
||||
};
|
||||
} else {
|
||||
listingObjectMetadataId = await createListingCustomObject();
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: {
|
||||
labelSingular: LISTING_NAME_SINGULAR,
|
||||
labelPlural: LISTING_NAME_PLURAL,
|
||||
nameSingular: LISTING_NAME_SINGULAR,
|
||||
namePlural: LISTING_NAME_PLURAL,
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
},
|
||||
});
|
||||
|
||||
listingObjectMetadataId = {
|
||||
objectMetadataId: data.createOneObject.id,
|
||||
};
|
||||
}
|
||||
|
||||
await performCreateManyOperation(
|
||||
@ -102,9 +121,9 @@ describe('SearchResolver', () => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
await deleteOneObjectMetadataItem(
|
||||
listingObjectMetadataId.objectMetadataId,
|
||||
).catch((error) => {
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: listingObjectMetadataId.objectMetadataId },
|
||||
}).catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { createCustomTextFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-custom-text-field-metadata.util';
|
||||
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util';
|
||||
import { deleteOneFieldMetadataItemFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util';
|
||||
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util';
|
||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-factory.util';
|
||||
import { createOneFieldMetadataQueryFactory } 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 { deleteOneFieldMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-query-factory.util';
|
||||
import { updateOneFieldMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-query-factory.util';
|
||||
import { createOneObjectMetadataQueryFactory } 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 { 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 { makeMetadataAPIRequestWithMemberRole } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-member-role.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
@ -21,18 +21,33 @@ describe('datamodel permissions', () => {
|
||||
let testFieldId = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'listing',
|
||||
namePlural: 'listings',
|
||||
labelSingular: 'Listing',
|
||||
labelPlural: 'Listings',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
},
|
||||
});
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
listingObjectId = data.createOneObject.id;
|
||||
|
||||
const { fieldMetadataId: createdFieldMetadaId } =
|
||||
await createCustomTextFieldMetadata(createdObjectId);
|
||||
const { data: createdFieldData } = await createOneFieldMetadata({
|
||||
input: {
|
||||
name: 'house',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'House',
|
||||
objectMetadataId: listingObjectId,
|
||||
},
|
||||
});
|
||||
|
||||
testFieldId = createdFieldMetadaId;
|
||||
testFieldId = createdFieldData.createOneField.id;
|
||||
});
|
||||
afterAll(async () => {
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: listingObjectId },
|
||||
});
|
||||
});
|
||||
describe('createOne', () => {
|
||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||
@ -46,8 +61,8 @@ describe('datamodel permissions', () => {
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
const graphqlOperation = createOneFieldMetadataQueryFactory({
|
||||
input: createFieldInput,
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
@ -77,8 +92,8 @@ describe('datamodel permissions', () => {
|
||||
label: 'Updated Name',
|
||||
};
|
||||
|
||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
||||
input: { id: testFieldId, update: updateFieldInput },
|
||||
const graphqlOperation = updateOneFieldMetadataQueryFactory({
|
||||
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
@ -103,8 +118,8 @@ describe('datamodel permissions', () => {
|
||||
describe('deleteOne', () => {
|
||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||
// Arrange
|
||||
const graphqlOperation = deleteOneFieldMetadataItemFactory({
|
||||
idToDelete: testFieldId,
|
||||
const graphqlOperation = deleteOneFieldMetadataQueryFactory({
|
||||
input: { idToDelete: testFieldId },
|
||||
});
|
||||
|
||||
const response =
|
||||
@ -127,17 +142,15 @@ describe('datamodel permissions', () => {
|
||||
describe('createOne', () => {
|
||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||
// Arrange
|
||||
const graphqlOperation = createOneObjectMetadataFactory({
|
||||
const graphqlOperation = createOneObjectMetadataQueryFactory({
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
input: {
|
||||
object: {
|
||||
labelPlural: 'Test Objects',
|
||||
labelSingular: 'Test Object',
|
||||
namePlural: 'testObjects',
|
||||
nameSingular: 'testObject',
|
||||
},
|
||||
labelPlural: 'Test Objects',
|
||||
labelSingular: 'Test Object',
|
||||
namePlural: 'testObjects',
|
||||
nameSingular: 'testObject',
|
||||
},
|
||||
});
|
||||
|
||||
@ -160,18 +173,27 @@ describe('datamodel permissions', () => {
|
||||
let listingObjectId = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: {
|
||||
labelPlural: 'Listings',
|
||||
labelSingular: 'Listing',
|
||||
namePlural: 'listings',
|
||||
nameSingular: 'listing',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
},
|
||||
});
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
listingObjectId = data.createOneObject.id;
|
||||
});
|
||||
afterAll(async () => {
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: listingObjectId },
|
||||
});
|
||||
});
|
||||
describe('updateOne', () => {
|
||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||
// Arrange
|
||||
const graphqlOperation = updateOneObjectMetadataItemFactory({
|
||||
const graphqlOperation = updateOneObjectMetadataQueryFactory({
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
@ -201,8 +223,8 @@ describe('datamodel permissions', () => {
|
||||
describe('deleteOne', () => {
|
||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||
// Arrange
|
||||
const graphqlOperation = deleteOneObjectMetadataItemFactory({
|
||||
idToDelete: listingObjectId,
|
||||
const graphqlOperation = deleteOneObjectMetadataQueryFactory({
|
||||
input: { idToDelete: listingObjectId },
|
||||
});
|
||||
|
||||
const response =
|
||||
|
||||
@ -2,8 +2,8 @@ import request from 'supertest';
|
||||
import { deleteOneRoleOperationFactory } from 'test/integration/graphql/utils/delete-one-role-operation-factory.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
|
||||
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
|
||||
@ -38,19 +38,12 @@ describe('roles permissions', () => {
|
||||
let guestRoleId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const enablePermissionsQuery = updateFeatureFlagFactory(
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
'IsPermissionsEnabled',
|
||||
true,
|
||||
);
|
||||
|
||||
const enablePermissionsV2Query = updateFeatureFlagFactory(
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
'IsPermissionsV2Enabled',
|
||||
true,
|
||||
);
|
||||
|
||||
await makeGraphqlAPIRequest(enablePermissionsQuery);
|
||||
await makeGraphqlAPIRequest(enablePermissionsV2Query);
|
||||
|
||||
const query = {
|
||||
@ -460,14 +453,23 @@ describe('roles permissions', () => {
|
||||
let listingObjectId = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'house',
|
||||
namePlural: 'houses',
|
||||
labelSingular: 'House',
|
||||
labelPlural: 'Houses',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
},
|
||||
});
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
listingObjectId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: listingObjectId },
|
||||
});
|
||||
});
|
||||
|
||||
const upsertObjectPermissionMutation = ({
|
||||
|
||||
@ -1,21 +1,33 @@
|
||||
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.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 { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
describe('createOne', () => {
|
||||
describe('FieldMetadataService name/label sync', () => {
|
||||
let listingObjectId = '';
|
||||
let createdObjectMetadataId = '';
|
||||
|
||||
beforeEach(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
labelSingular: 'My Test Object',
|
||||
labelPlural: 'My Test Objects',
|
||||
icon: 'Icon123',
|
||||
},
|
||||
});
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
createdObjectMetadataId = objectMetadataId;
|
||||
});
|
||||
afterEach(async () => {
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
it('should create a field when name and label are synced correctly', async () => {
|
||||
// Arrange
|
||||
@ -24,13 +36,13 @@ describe('createOne', () => {
|
||||
name: FIELD_NAME,
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: listingObjectId,
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
const { data } = await createOneFieldMetadata({
|
||||
input: createFieldInput,
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
@ -39,10 +51,8 @@ describe('createOne', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.createOneField.name).toBe(FIELD_NAME);
|
||||
expect(data.createOneField.name).toBe(FIELD_NAME);
|
||||
});
|
||||
|
||||
it('should set isLabelSyncWithName to false if not in input', async () => {
|
||||
@ -51,26 +61,22 @@ describe('createOne', () => {
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: listingObjectId,
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
const { data } = await createOneFieldMetadata({
|
||||
input: createFieldInput,
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.createOneField.isLabelSyncedWithName).toBe(
|
||||
false,
|
||||
);
|
||||
expect(data.createOneField.isLabelSyncedWithName).toBe(false);
|
||||
});
|
||||
|
||||
it('should return an error when name and label are not synced but isLabelSyncedWithName is true', async () => {
|
||||
@ -79,25 +85,24 @@ describe('createOne', () => {
|
||||
name: 'testField',
|
||||
label: 'Different Label',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: listingObjectId,
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
// Act
|
||||
const { errors } = await createOneFieldMetadata({
|
||||
input: createFieldInput,
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
`,
|
||||
expectToFail: true,
|
||||
});
|
||||
|
||||
// Act
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.errors[0].message).toBe(
|
||||
expect(errors[0].message).toBe(
|
||||
'Name is not synced with label. Expected name: "differentLabel", got testField',
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
|
||||
import { findOneOperationFactory } from 'test/integration/graphql/utils/find-one-operation-factory.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { createCustomTextFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-custom-text-field-metadata.util';
|
||||
import { deleteOneFieldMetadataItemFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util';
|
||||
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.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 {
|
||||
LISTING_NAME_PLURAL,
|
||||
LISTING_NAME_SINGULAR,
|
||||
} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
describe('deleteOne', () => {
|
||||
describe('Kanban aggregate operation', () => {
|
||||
@ -15,14 +19,27 @@ describe('deleteOne', () => {
|
||||
let viewId = '';
|
||||
|
||||
beforeEach(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: LISTING_NAME_SINGULAR,
|
||||
namePlural: LISTING_NAME_PLURAL,
|
||||
labelSingular: 'Listing',
|
||||
labelPlural: 'Listings',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
},
|
||||
});
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
const { fieldMetadataId: createdFieldMetadaId } =
|
||||
await createCustomTextFieldMetadata(createdObjectId);
|
||||
listingObjectId = data.createOneObject.id;
|
||||
const { data: createdFieldData } = await createOneFieldMetadata({
|
||||
input: {
|
||||
name: 'house',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'House',
|
||||
objectMetadataId: listingObjectId,
|
||||
},
|
||||
});
|
||||
|
||||
testFieldId = createdFieldMetadaId;
|
||||
testFieldId = createdFieldData.createOneField.id;
|
||||
|
||||
// create view
|
||||
const graphqlOperation = createOneOperationFactory({
|
||||
@ -49,7 +66,9 @@ describe('deleteOne', () => {
|
||||
viewId = createdView.id;
|
||||
});
|
||||
afterEach(async () => {
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: listingObjectId },
|
||||
});
|
||||
});
|
||||
it('should reset kanban aggregate operation when deleting a field used as kanbanAggregateOperationFieldMetadataId', async () => {
|
||||
// Arrange
|
||||
@ -76,25 +95,25 @@ describe('deleteOne', () => {
|
||||
expect(viewResponse.body.data.view.kanbanAggregateOperation).toBe('MAX');
|
||||
|
||||
// Deactivate field to be able to delete it after
|
||||
const deactivateFieldOperation = updateOneFieldMetadataFactory({
|
||||
input: { id: testFieldId, update: { isActive: false } },
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
idToUpdate: testFieldId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
isActive
|
||||
`,
|
||||
});
|
||||
|
||||
await makeMetadataAPIRequest(deactivateFieldOperation);
|
||||
|
||||
// Act
|
||||
const graphqlOperation = deleteOneFieldMetadataItemFactory({
|
||||
idToDelete: testFieldId,
|
||||
const { data } = await deleteOneFieldMetadata({
|
||||
input: { idToDelete: testFieldId },
|
||||
});
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
// 1. Field is deleted
|
||||
expect(response.body.data.deleteOneField.id).toBe(testFieldId);
|
||||
expect(data.deleteOneField.id).toBe(testFieldId);
|
||||
|
||||
// 2. Kanban aggregate operation has been reset on view using this field as kanbanAggregateOperationFieldMetadataId
|
||||
const updatedViewResponse =
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { createCustomTextFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-custom-text-field-metadata.util';
|
||||
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import {
|
||||
LISTING_NAME_PLURAL,
|
||||
LISTING_NAME_SINGULAR,
|
||||
} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
describe('updateOne', () => {
|
||||
describe('FieldMetadataService name/label sync', () => {
|
||||
@ -10,18 +14,35 @@ describe('updateOne', () => {
|
||||
let testFieldId = '';
|
||||
|
||||
beforeEach(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: {
|
||||
labelSingular: LISTING_NAME_SINGULAR,
|
||||
labelPlural: LISTING_NAME_PLURAL,
|
||||
nameSingular: LISTING_NAME_SINGULAR,
|
||||
namePlural: LISTING_NAME_PLURAL,
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isLabelSyncedWithName: true,
|
||||
},
|
||||
});
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
listingObjectId = data.createOneObject.id;
|
||||
|
||||
const { fieldMetadataId: createdFieldMetadaId } =
|
||||
await createCustomTextFieldMetadata(createdObjectId);
|
||||
const { data: createdFieldMetadata } = await createOneFieldMetadata({
|
||||
input: {
|
||||
objectMetadataId: listingObjectId,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'testName',
|
||||
label: 'Test name',
|
||||
isLabelSyncedWithName: true,
|
||||
},
|
||||
});
|
||||
|
||||
testFieldId = createdFieldMetadaId;
|
||||
testFieldId = createdFieldMetadata.createOneField.id;
|
||||
});
|
||||
afterEach(async () => {
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: listingObjectId },
|
||||
});
|
||||
});
|
||||
|
||||
it('should update a field name and label when they are synced correctly', async () => {
|
||||
@ -29,11 +50,12 @@ describe('updateOne', () => {
|
||||
const updateFieldInput = {
|
||||
name: 'newName',
|
||||
label: 'New name',
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
||||
input: { id: testFieldId, update: updateFieldInput },
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
@ -42,10 +64,8 @@ describe('updateOne', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.updateOneField.name).toBe('newName');
|
||||
expect(data.updateOneField.name).toBe('newName');
|
||||
});
|
||||
|
||||
it('should update a field name and label when they are not synced correctly and labelSync is false', async () => {
|
||||
@ -57,8 +77,8 @@ describe('updateOne', () => {
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
||||
input: { id: testFieldId, update: updateFieldInput },
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
@ -67,33 +87,31 @@ describe('updateOne', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.updateOneField.name).toBe('differentName');
|
||||
expect(data.updateOneField.name).toBe('differentName');
|
||||
});
|
||||
|
||||
it('should not update a field name if it is not synced correctly with label and labelSync is true', async () => {
|
||||
// Arrange
|
||||
const updateFieldInput = {
|
||||
name: 'newName',
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
||||
input: { id: testFieldId, update: updateFieldInput },
|
||||
const { errors } = await updateOneFieldMetadata({
|
||||
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
expectToFail: true,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.errors[0].message).toBe(
|
||||
expect(errors[0].message).toBe(
|
||||
'Name is not synced with label. Expected name: "testName", got newName',
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
const FIELD_NAME = 'testName';
|
||||
|
||||
export const createCustomTextFieldMetadata = async (
|
||||
objectMetadataItemId: string,
|
||||
) => {
|
||||
const createFieldInput = {
|
||||
name: FIELD_NAME,
|
||||
label: 'Test name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: objectMetadataItemId,
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
return { fieldMetadataId: response.body.data.createOneField.id };
|
||||
};
|
||||
@ -1,24 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
type CreateOneFieldFactoryParams = {
|
||||
gqlFields: string;
|
||||
input?: { field: Omit<CreateFieldInput, 'workspaceId' | 'dataSourceId'> };
|
||||
};
|
||||
|
||||
export const createOneFieldMetadataFactory = ({
|
||||
gqlFields,
|
||||
input,
|
||||
}: CreateOneFieldFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {
|
||||
createOneField(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
export type CreateOneFieldFactoryInput = Omit<
|
||||
CreateFieldInput,
|
||||
'workspaceId' | 'dataSourceId'
|
||||
>;
|
||||
|
||||
export const createOneFieldMetadataQueryFactory = ({
|
||||
input,
|
||||
gqlFields = 'id',
|
||||
}: PerformMetadataQueryParams<CreateOneFieldFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {
|
||||
createOneField(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: { field: input },
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import {
|
||||
CreateOneFieldFactoryInput,
|
||||
createOneFieldMetadataQueryFactory,
|
||||
} from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const createOneFieldMetadata = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
}: PerformMetadataQueryParams<CreateOneFieldFactoryInput>) => {
|
||||
const graphqlOperation = createOneFieldMetadataQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Field Metadata creation should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
type DeleteOneFieldFactoryParams = {
|
||||
idToDelete: string;
|
||||
};
|
||||
|
||||
export const deleteOneFieldMetadataItemFactory = ({
|
||||
idToDelete,
|
||||
}: DeleteOneFieldFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {
|
||||
deleteOneField(input: { id: $idToDelete }) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
export type DeleteOneFieldFactoryInput = {
|
||||
idToDelete: string;
|
||||
};
|
||||
|
||||
export const deleteOneFieldMetadataQueryFactory = ({
|
||||
input,
|
||||
gqlFields = 'id',
|
||||
}: PerformMetadataQueryParams<DeleteOneFieldFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {
|
||||
deleteOneField(input: { id: $idToDelete }) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
idToDelete: input.idToDelete,
|
||||
},
|
||||
});
|
||||
@ -1,10 +1,29 @@
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { deleteOneFieldMetadataItemFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util';
|
||||
import {
|
||||
DeleteOneFieldFactoryInput,
|
||||
deleteOneFieldMetadataQueryFactory,
|
||||
} from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-query-factory.util';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const deleteFieldMetadata = async (fieldMetadataId: string) => {
|
||||
const graphqlOperation = deleteOneFieldMetadataItemFactory({
|
||||
idToDelete: fieldMetadataId,
|
||||
export const deleteOneFieldMetadata = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
}: PerformMetadataQueryParams<DeleteOneFieldFactoryInput>) => {
|
||||
const graphqlOperation = deleteOneFieldMetadataQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(graphqlOperation);
|
||||
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Field Metadata deletion should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
type FieldsFactoryParams = {
|
||||
gqlFields: string;
|
||||
input: {
|
||||
filter: object;
|
||||
paging: object;
|
||||
};
|
||||
export type FindManyFieldsMetadataFactoryInput = {
|
||||
filter: object;
|
||||
paging: object;
|
||||
};
|
||||
|
||||
export const fieldsMetadataFactory = ({
|
||||
gqlFields,
|
||||
export const findManyFieldsMetadataQueryFactory = ({
|
||||
gqlFields = 'id',
|
||||
input,
|
||||
}: FieldsFactoryParams) => ({
|
||||
}: PerformMetadataQueryParams<FindManyFieldsMetadataFactoryInput>) => ({
|
||||
query: gql`
|
||||
query FieldsMetadata($filter: FieldFilter!, $paging: CursorPaging!) {
|
||||
fields(filter: $filter, paging: $paging) {
|
||||
@ -0,0 +1,29 @@
|
||||
import {
|
||||
FindManyFieldsMetadataFactoryInput,
|
||||
findManyFieldsMetadataQueryFactory,
|
||||
} from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const findManyFieldsMetadata = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
}: PerformMetadataQueryParams<FindManyFieldsMetadataFactoryInput>) => {
|
||||
const graphqlOperation = findManyFieldsMetadataQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Field Metadata retrieval should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
return response.body.data.fields.edges.map((edge) => edge.node);
|
||||
};
|
||||
@ -1,25 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
type UpdateOneFieldFactoryParams = {
|
||||
gqlFields: string;
|
||||
input: { id: string; update: Omit<UpdateFieldInput, 'workspaceId' | 'id'> };
|
||||
};
|
||||
|
||||
export const updateOneFieldMetadataFactory = ({
|
||||
gqlFields,
|
||||
input,
|
||||
}: UpdateOneFieldFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation UpdateOneFieldMetadataItem($idToUpdate: UUID!, $updatePayload: UpdateFieldInput!) {
|
||||
updateOneField(input: {id: $idToUpdate, update: $updatePayload}) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
idToUpdate: input.id,
|
||||
updatePayload: input.update,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,26 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
export type UpdateOneFieldFactoryInput = {
|
||||
idToUpdate: string;
|
||||
updatePayload: Omit<UpdateFieldInput, 'workspaceId' | 'id'>;
|
||||
};
|
||||
|
||||
export const updateOneFieldMetadataQueryFactory = ({
|
||||
gqlFields = 'id',
|
||||
input,
|
||||
}: PerformMetadataQueryParams<UpdateOneFieldFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation UpdateOneFieldMetadataItem($idToUpdate: UUID!, $updatePayload: UpdateFieldInput!) {
|
||||
updateOneField(input: {id: $idToUpdate, update: $updatePayload}) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
idToUpdate: input.idToUpdate,
|
||||
updatePayload: input.updatePayload,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import {
|
||||
UpdateOneFieldFactoryInput,
|
||||
updateOneFieldMetadataQueryFactory,
|
||||
} from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-query-factory.util';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const updateOneFieldMetadata = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
}: PerformMetadataQueryParams<UpdateOneFieldFactoryInput>) => {
|
||||
const graphqlOperation = updateOneFieldMetadataQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Field Metadata update should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
@ -1,2 +1,2 @@
|
||||
export const LISTING_NAME_SINGULAR = 'listing';
|
||||
export const LISTING_NAME_PLURAL = 'listings';
|
||||
export const LISTING_NAME_SINGULAR = 'listinga';
|
||||
export const LISTING_NAME_PLURAL = 'listingas';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMockCreateObjectInput } from 'test/integration/utils/object-metadata/generate-mock-create-object-metadata-input';
|
||||
import { performFailingObjectMetadataCreation } from 'test/integration/utils/object-metadata/perform-failing-object-metadata-creation';
|
||||
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 { EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
@ -124,9 +124,10 @@ const allTestsUseCases = [
|
||||
|
||||
describe('Object metadata creation should fail', () => {
|
||||
it.each(allTestsUseCases)('$title', async ({ context }) => {
|
||||
const errors = await performFailingObjectMetadataCreation(
|
||||
getMockCreateObjectInput(context),
|
||||
);
|
||||
const { errors } = await createOneObjectMetadata({
|
||||
input: getMockCreateObjectInput(context),
|
||||
expectToFail: true,
|
||||
});
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
const firstError = errors[0];
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
|
||||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util';
|
||||
import { objectsMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/objects-metadata-factory.util';
|
||||
import { updateOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-factory.util';
|
||||
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 { createOneRelationMetadataFactory } from 'test/integration/metadata/suites/utils/create-one-relation-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 { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
@ -37,7 +36,7 @@ describe('Custom object renaming', () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const standardObjectsGraphqlOperation = objectsMetadataFactory({
|
||||
const standardObjectsGraphqlOperation = findManyObjectMetadataQueryFactory({
|
||||
gqlFields: `
|
||||
id
|
||||
nameSingular
|
||||
@ -50,7 +49,7 @@ describe('Custom object renaming', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const fieldsGraphqlOperation = fieldsMetadataFactory({
|
||||
const fieldsGraphqlOperation = findManyFieldsMetadataQueryFactory({
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
@ -94,22 +93,18 @@ describe('Custom object renaming', () => {
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = createOneObjectMetadataFactory({
|
||||
input: { object: LISTING_OBJECT },
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: LISTING_OBJECT,
|
||||
gqlFields: `
|
||||
id
|
||||
nameSingular
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.createOneObject.nameSingular).toBe(
|
||||
LISTING_NAME_SINGULAR,
|
||||
);
|
||||
expect(data.createOneObject.nameSingular).toBe(LISTING_NAME_SINGULAR);
|
||||
|
||||
listingObjectId = response.body.data.createOneObject.id;
|
||||
listingObjectId = data.createOneObject.id;
|
||||
|
||||
const fields = await makeMetadataAPIRequest(fieldsGraphqlOperation);
|
||||
|
||||
@ -206,43 +201,31 @@ describe('Custom object renaming', () => {
|
||||
const HOUSE_NAME_PLURAL = 'houses';
|
||||
const HOUSE_LABEL_SINGULAR = 'House';
|
||||
const HOUSE_LABEL_PLURAL = 'Houses';
|
||||
const updateListingNameGraphqlOperation =
|
||||
updateOneObjectMetadataItemFactory({
|
||||
gqlFields: `
|
||||
|
||||
// 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,
|
||||
},
|
||||
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);
|
||||
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);
|
||||
|
||||
@ -306,14 +289,12 @@ describe('Custom object renaming', () => {
|
||||
});
|
||||
|
||||
it('5. should delete custom object', async () => {
|
||||
const graphqlOperation = deleteOneObjectMetadataItemFactory({
|
||||
idToDelete: listingObjectId,
|
||||
const { data } = await deleteOneObjectMetadata({
|
||||
input: {
|
||||
idToDelete: listingObjectId,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||
|
||||
const deleteListingResponse = response.body.data.deleteOneObject;
|
||||
|
||||
expect(deleteListingResponse.id).toBe(listingObjectId);
|
||||
expect(data.deleteOneObject.id).toBe(listingObjectId);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { getMockCreateObjectInput } from 'test/integration/utils/object-metadata/generate-mock-create-object-metadata-input';
|
||||
import { performObjectMetadataCreation } from 'test/integration/utils/object-metadata/perform-object-metadata-creation';
|
||||
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 { EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
@ -26,13 +26,13 @@ const allTestsUseCases = [...successfulObjectMetadataItemCreateOneUseCase];
|
||||
|
||||
describe('Object metadata creation should succeed', () => {
|
||||
it.each(allTestsUseCases)('$title', async ({ context }) => {
|
||||
const response = await performObjectMetadataCreation(
|
||||
getMockCreateObjectInput(context),
|
||||
);
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: getMockCreateObjectInput(context),
|
||||
});
|
||||
|
||||
expect(response.body.data.createOneObject.id).toBeDefined();
|
||||
await deleteOneObjectMetadataItem(
|
||||
response.body.data.createOneObject.id,
|
||||
).catch();
|
||||
expect(data.createOneObject.id).toBeDefined();
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: data.createOneObject.id },
|
||||
}).catch();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
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,25 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
|
||||
export type CreateOneObjectFactoryInput = Omit<
|
||||
CreateObjectInput,
|
||||
'workspaceId' | 'dataSourceId'
|
||||
>;
|
||||
|
||||
export const createOneObjectMetadataQueryFactory = ({
|
||||
input,
|
||||
gqlFields = 'id',
|
||||
}: PerformMetadataQueryParams<CreateOneObjectFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation CreateOneObjectMetadataItem($input: CreateOneObjectInput!) {
|
||||
createOneObject(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: { object: input },
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import {
|
||||
CreateOneObjectFactoryInput,
|
||||
createOneObjectMetadataQueryFactory,
|
||||
} from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const createOneObjectMetadata = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
}: PerformMetadataQueryParams<CreateOneObjectFactoryInput>) => {
|
||||
const graphqlOperation = createOneObjectMetadataQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Object Metadata creation should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
@ -1,32 +0,0 @@
|
||||
import {
|
||||
LISTING_NAME_PLURAL,
|
||||
LISTING_NAME_SINGULAR,
|
||||
} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant';
|
||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
|
||||
const LISTING_OBJECT = {
|
||||
namePlural: LISTING_NAME_PLURAL,
|
||||
nameSingular: LISTING_NAME_SINGULAR,
|
||||
labelPlural: 'Listings',
|
||||
labelSingular: 'Listing',
|
||||
description: 'Listing object',
|
||||
icon: 'IconListNumbers',
|
||||
isLabelSyncedWithName: false,
|
||||
};
|
||||
|
||||
export const createListingCustomObject = async () => {
|
||||
const createObjectOperation = createOneObjectMetadataFactory({
|
||||
input: { object: LISTING_OBJECT },
|
||||
gqlFields: `
|
||||
id
|
||||
nameSingular
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(createObjectOperation);
|
||||
|
||||
return {
|
||||
objectMetadataId: response.body.data.createOneObject.id,
|
||||
};
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
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';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
export type DeleteOneObjectFactoryInput = {
|
||||
idToDelete: string;
|
||||
};
|
||||
|
||||
export const deleteOneObjectMetadataQueryFactory = ({
|
||||
input,
|
||||
gqlFields = 'id',
|
||||
}: PerformMetadataQueryParams<DeleteOneObjectFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {
|
||||
deleteOneObject(input: { id: $idToDelete }) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
idToDelete: input.idToDelete,
|
||||
},
|
||||
});
|
||||
@ -1,12 +1,29 @@
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util';
|
||||
import {
|
||||
DeleteOneObjectFactoryInput,
|
||||
deleteOneObjectMetadataQueryFactory,
|
||||
} from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-query-factory.util';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const deleteOneObjectMetadataItem = async (
|
||||
objectMetadataItemId: string,
|
||||
) => {
|
||||
const graphqlOperation = deleteOneObjectMetadataItemFactory({
|
||||
idToDelete: objectMetadataItemId,
|
||||
export const deleteOneObjectMetadata = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
}: PerformMetadataQueryParams<DeleteOneObjectFactoryInput>) => {
|
||||
const graphqlOperation = deleteOneObjectMetadataQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(graphqlOperation);
|
||||
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Object Metadata deletion should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
type ObjectsFactoryParams = {
|
||||
gqlFields: string;
|
||||
input: {
|
||||
filter: object;
|
||||
paging: object;
|
||||
};
|
||||
export type FindManyObjectMetadataFactoryInput = {
|
||||
filter: object;
|
||||
paging: object;
|
||||
};
|
||||
|
||||
export const objectsMetadataFactory = ({
|
||||
gqlFields,
|
||||
export const findManyObjectMetadataQueryFactory = ({
|
||||
gqlFields = 'id',
|
||||
input,
|
||||
}: ObjectsFactoryParams) => ({
|
||||
}: PerformMetadataQueryParams<FindManyObjectMetadataFactoryInput>) => ({
|
||||
query: gql`
|
||||
query ObjectsMetadata($filter: ObjectFilter!, $paging: CursorPaging!) {
|
||||
objects(filter: $filter, paging: $paging) {
|
||||
@ -0,0 +1,29 @@
|
||||
import {
|
||||
FindManyObjectMetadataFactoryInput,
|
||||
findManyObjectMetadataQueryFactory,
|
||||
} from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const findManyObjectMetadata = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
}: PerformMetadataQueryParams<FindManyObjectMetadataFactoryInput>) => {
|
||||
const graphqlOperation = findManyObjectMetadataQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Object Metadata retrieval should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
return response.body.data.objects.edges.map((edge) => edge.node);
|
||||
};
|
||||
@ -1,28 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
|
||||
export const findManyObjectsMetadataItems = async () => {
|
||||
const query = {
|
||||
query: gql`
|
||||
query ObjectMetadataItems {
|
||||
objects(paging: { first: 1000 }) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
const response = await makeMetadataAPIRequest(query);
|
||||
|
||||
return response.body.data.objects.edges.map((edge) => edge.node) as {
|
||||
id: string;
|
||||
nameSingular: string;
|
||||
namePlural: string;
|
||||
}[];
|
||||
};
|
||||
@ -4,8 +4,8 @@ import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/d
|
||||
export const getMockCreateObjectInput = (
|
||||
overrides?: Partial<Omit<CreateObjectInput, 'workspaceId' | 'dataSourceId'>>,
|
||||
) => ({
|
||||
namePlural: 'listings',
|
||||
nameSingular: 'listing',
|
||||
namePlural: 'listingas',
|
||||
nameSingular: 'listinga',
|
||||
labelPlural: 'Listings',
|
||||
labelSingular: 'Listing',
|
||||
description: 'Listing object',
|
||||
@ -1,28 +0,0 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,26 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||
|
||||
export type UpdateOneObjectFactoryInput = {
|
||||
idToUpdate: string;
|
||||
updatePayload: UpdateObjectPayload;
|
||||
};
|
||||
|
||||
export const updateOneObjectMetadataQueryFactory = ({
|
||||
gqlFields = 'id',
|
||||
input,
|
||||
}: PerformMetadataQueryParams<UpdateOneObjectFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation UpdateOneObjectMetadataItem($idToUpdate: UUID!, $updatePayload: UpdateObjectPayload!) {
|
||||
updateOneObject(input: {id: $idToUpdate, update: $updatePayload}) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
idToUpdate: input.idToUpdate,
|
||||
updatePayload: input.updatePayload,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import {
|
||||
UpdateOneObjectFactoryInput,
|
||||
updateOneObjectMetadataQueryFactory,
|
||||
} from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-query-factory.util';
|
||||
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const updateOneObjectMetadata = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
}: PerformMetadataQueryParams<UpdateOneObjectFactoryInput>) => {
|
||||
const graphqlOperation = updateOneObjectMetadataQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'Object Metadata update should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export type PerformMetadataQueryParams<T> = {
|
||||
input: T;
|
||||
gqlFields?: string;
|
||||
expectToFail?: boolean;
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
import { isDefined } from 'class-validator';
|
||||
import { Response } from 'supertest';
|
||||
|
||||
type WarnIfNoErrorButExpectedToFailInput = {
|
||||
response: Response;
|
||||
errorMessage: string;
|
||||
};
|
||||
|
||||
export const warnIfNoErrorButExpectedToFail = ({
|
||||
response,
|
||||
errorMessage,
|
||||
}: WarnIfNoErrorButExpectedToFailInput) => {
|
||||
if (isDefined(response.body.data)) {
|
||||
expect(false).toEqual(errorMessage);
|
||||
}
|
||||
expect(response.body.errors.length).toBeGreaterThan(0);
|
||||
};
|
||||
@ -1,28 +0,0 @@
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { performObjectMetadataCreation } from 'test/integration/utils/object-metadata/perform-object-metadata-creation';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
|
||||
export const performFailingObjectMetadataCreation = async (
|
||||
objectInput: Omit<CreateObjectInput, 'workspaceId' | 'dataSourceId'>,
|
||||
) => {
|
||||
const response = await performObjectMetadataCreation(objectInput);
|
||||
|
||||
if (isDefined(response.body.data)) {
|
||||
try {
|
||||
const createdId = response.body.data.createOneObject.id;
|
||||
|
||||
await deleteOneObjectMetadataItem(createdId);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
expect(false).toEqual(
|
||||
'Object Metadata Item should have failed but did not',
|
||||
);
|
||||
}
|
||||
expect(response.body.errors.length).toBeGreaterThan(0);
|
||||
|
||||
return response.body.errors;
|
||||
};
|
||||
@ -1,18 +0,0 @@
|
||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
|
||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
|
||||
export const performObjectMetadataCreation = async (
|
||||
args: Omit<CreateObjectInput, 'workspaceId' | 'dataSourceId'>,
|
||||
) => {
|
||||
const graphqlOperation = createOneObjectMetadataFactory({
|
||||
input: { object: args },
|
||||
gqlFields: `
|
||||
id
|
||||
nameSingular
|
||||
`,
|
||||
});
|
||||
|
||||
return await makeMetadataAPIRequest(graphqlOperation);
|
||||
};
|
||||
Reference in New Issue
Block a user