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(
|
.filter(
|
||||||
(objectMetadataItem) =>
|
(objectMetadataItem) =>
|
||||||
objectMetadataItem.isSearchable &&
|
objectMetadataItem.isSearchable &&
|
||||||
|
objectMetadataItem.isActive &&
|
||||||
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Task &&
|
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Task &&
|
||||||
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Note &&
|
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Note &&
|
||||||
objectMetadataItem.nameSingular !==
|
objectMetadataItem.nameSingular !==
|
||||||
|
|||||||
@ -15,4 +15,5 @@ export enum FieldMetadataExceptionCode {
|
|||||||
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||||
FIELD_METADATA_RELATION_NOT_ENABLED = 'FIELD_METADATA_RELATION_NOT_ENABLED',
|
FIELD_METADATA_RELATION_NOT_ENABLED = 'FIELD_METADATA_RELATION_NOT_ENABLED',
|
||||||
FIELD_METADATA_RELATION_MALFORMED = 'FIELD_METADATA_RELATION_MALFORMED',
|
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';
|
} 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 { 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 { 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 {
|
import {
|
||||||
computeColumnName,
|
computeColumnName,
|
||||||
computeCompositeColumnName,
|
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);
|
assertMutationNotOnRemoteObject(objectMetadata);
|
||||||
|
|
||||||
assertDoesNotNullifyDefaultValueForNonNullableField({
|
assertDoesNotNullifyDefaultValueForNonNullableField({
|
||||||
@ -161,18 +168,13 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
defaultValueFromUpdate: fieldMetadataInput.defaultValue,
|
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) {
|
if (fieldMetadataInput.isActive === false) {
|
||||||
|
checkCanDeactivateFieldOrThrow({
|
||||||
|
labelIdentifierFieldMetadataId:
|
||||||
|
objectMetadata.labelIdentifierFieldMetadataId,
|
||||||
|
existingFieldMetadata,
|
||||||
|
});
|
||||||
|
|
||||||
const viewsRepository =
|
const viewsRepository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
fieldMetadataInput.workspaceId,
|
fieldMetadataInput.workspaceId,
|
||||||
|
|||||||
@ -30,6 +30,8 @@ export interface FieldMetadataInterface<
|
|||||||
relationTargetObjectMetadataId?: string;
|
relationTargetObjectMetadataId?: string;
|
||||||
relationTargetObjectMetadata?: ObjectMetadataEntity;
|
relationTargetObjectMetadata?: ObjectMetadataEntity;
|
||||||
isCustom?: boolean;
|
isCustom?: boolean;
|
||||||
|
isSystem?: boolean;
|
||||||
|
isActive?: boolean;
|
||||||
generatedType?: 'STORED' | 'VIRTUAL';
|
generatedType?: 'STORED' | 'VIRTUAL';
|
||||||
asExpression?: string;
|
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.INTERNAL_SERVER_ERROR:
|
||||||
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
|
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
|
||||||
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED:
|
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED:
|
||||||
|
case FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND:
|
||||||
default:
|
default:
|
||||||
throw new InternalServerError(error.message);
|
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 { EachTestingContext } from 'twenty-shared/testing';
|
||||||
|
|
||||||
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||||
|
|||||||
@ -14,9 +14,9 @@ import {
|
|||||||
LISTING_NAME_PLURAL,
|
LISTING_NAME_PLURAL,
|
||||||
LISTING_NAME_SINGULAR,
|
LISTING_NAME_SINGULAR,
|
||||||
} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant';
|
} 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 { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
import { deleteOneObjectMetadata } 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 { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
|
||||||
import { EachTestingContext } from 'twenty-shared/testing';
|
import { EachTestingContext } from 'twenty-shared/testing';
|
||||||
|
|
||||||
import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
|
import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
|
||||||
@ -46,7 +46,14 @@ describe('SearchResolver', () => {
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
try {
|
try {
|
||||||
const objectsMetadata = await findManyObjectsMetadataItems();
|
const objectsMetadata = await findManyObjectMetadata({
|
||||||
|
input: {
|
||||||
|
filter: {},
|
||||||
|
paging: {
|
||||||
|
first: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
const listingObjectMetadata = objectsMetadata.find(
|
const listingObjectMetadata = objectsMetadata.find(
|
||||||
(object) => object.nameSingular === LISTING_NAME_SINGULAR,
|
(object) => object.nameSingular === LISTING_NAME_SINGULAR,
|
||||||
);
|
);
|
||||||
@ -56,7 +63,19 @@ describe('SearchResolver', () => {
|
|||||||
objectMetadataId: listingObjectMetadata.id,
|
objectMetadataId: listingObjectMetadata.id,
|
||||||
};
|
};
|
||||||
} else {
|
} 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(
|
await performCreateManyOperation(
|
||||||
@ -102,9 +121,9 @@ describe('SearchResolver', () => {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
await deleteOneObjectMetadataItem(
|
await deleteOneObjectMetadata({
|
||||||
listingObjectMetadataId.objectMetadataId,
|
input: { idToDelete: listingObjectMetadataId.objectMetadataId },
|
||||||
).catch((error) => {
|
}).catch((error) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { createCustomTextFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-custom-text-field-metadata.util';
|
import { createOneFieldMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util';
|
||||||
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util';
|
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||||
import { deleteOneFieldMetadataItemFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util';
|
import { deleteOneFieldMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-query-factory.util';
|
||||||
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util';
|
import { updateOneFieldMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-query-factory.util';
|
||||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
|
import { createOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-query-factory.util';
|
||||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util';
|
import { deleteOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-query-factory.util';
|
||||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
import { deleteOneObjectMetadata } 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 { 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 { makeMetadataAPIRequestWithMemberRole } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-member-role.util';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
@ -21,18 +21,33 @@ describe('datamodel permissions', () => {
|
|||||||
let testFieldId = '';
|
let testFieldId = '';
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const { objectMetadataId: createdObjectId } =
|
const { data } = await createOneObjectMetadata({
|
||||||
await createListingCustomObject();
|
input: {
|
||||||
|
nameSingular: 'listing',
|
||||||
|
namePlural: 'listings',
|
||||||
|
labelSingular: 'Listing',
|
||||||
|
labelPlural: 'Listings',
|
||||||
|
icon: 'IconBuildingSkyscraper',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
listingObjectId = createdObjectId;
|
listingObjectId = data.createOneObject.id;
|
||||||
|
|
||||||
const { fieldMetadataId: createdFieldMetadaId } =
|
const { data: createdFieldData } = await createOneFieldMetadata({
|
||||||
await createCustomTextFieldMetadata(createdObjectId);
|
input: {
|
||||||
|
name: 'house',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
label: 'House',
|
||||||
|
objectMetadataId: listingObjectId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
testFieldId = createdFieldMetadaId;
|
testFieldId = createdFieldData.createOneField.id;
|
||||||
});
|
});
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteOneObjectMetadataItem(listingObjectId);
|
await deleteOneObjectMetadata({
|
||||||
|
input: { idToDelete: listingObjectId },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('createOne', () => {
|
describe('createOne', () => {
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
@ -46,8 +61,8 @@ describe('datamodel permissions', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const graphqlOperation = createOneFieldMetadataFactory({
|
const graphqlOperation = createOneFieldMetadataQueryFactory({
|
||||||
input: { field: createFieldInput },
|
input: createFieldInput,
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -77,8 +92,8 @@ describe('datamodel permissions', () => {
|
|||||||
label: 'Updated Name',
|
label: 'Updated Name',
|
||||||
};
|
};
|
||||||
|
|
||||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
const graphqlOperation = updateOneFieldMetadataQueryFactory({
|
||||||
input: { id: testFieldId, update: updateFieldInput },
|
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -103,8 +118,8 @@ describe('datamodel permissions', () => {
|
|||||||
describe('deleteOne', () => {
|
describe('deleteOne', () => {
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const graphqlOperation = deleteOneFieldMetadataItemFactory({
|
const graphqlOperation = deleteOneFieldMetadataQueryFactory({
|
||||||
idToDelete: testFieldId,
|
input: { idToDelete: testFieldId },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response =
|
const response =
|
||||||
@ -127,17 +142,15 @@ describe('datamodel permissions', () => {
|
|||||||
describe('createOne', () => {
|
describe('createOne', () => {
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const graphqlOperation = createOneObjectMetadataFactory({
|
const graphqlOperation = createOneObjectMetadataQueryFactory({
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
`,
|
`,
|
||||||
input: {
|
input: {
|
||||||
object: {
|
labelPlural: 'Test Objects',
|
||||||
labelPlural: 'Test Objects',
|
labelSingular: 'Test Object',
|
||||||
labelSingular: 'Test Object',
|
namePlural: 'testObjects',
|
||||||
namePlural: 'testObjects',
|
nameSingular: 'testObject',
|
||||||
nameSingular: 'testObject',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,18 +173,27 @@ describe('datamodel permissions', () => {
|
|||||||
let listingObjectId = '';
|
let listingObjectId = '';
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const { objectMetadataId: createdObjectId } =
|
const { data } = await createOneObjectMetadata({
|
||||||
await createListingCustomObject();
|
input: {
|
||||||
|
labelPlural: 'Listings',
|
||||||
|
labelSingular: 'Listing',
|
||||||
|
namePlural: 'listings',
|
||||||
|
nameSingular: 'listing',
|
||||||
|
icon: 'IconBuildingSkyscraper',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
listingObjectId = createdObjectId;
|
listingObjectId = data.createOneObject.id;
|
||||||
});
|
});
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteOneObjectMetadataItem(listingObjectId);
|
await deleteOneObjectMetadata({
|
||||||
|
input: { idToDelete: listingObjectId },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('updateOne', () => {
|
describe('updateOne', () => {
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const graphqlOperation = updateOneObjectMetadataItemFactory({
|
const graphqlOperation = updateOneObjectMetadataQueryFactory({
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
`,
|
`,
|
||||||
@ -201,8 +223,8 @@ describe('datamodel permissions', () => {
|
|||||||
describe('deleteOne', () => {
|
describe('deleteOne', () => {
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const graphqlOperation = deleteOneObjectMetadataItemFactory({
|
const graphqlOperation = deleteOneObjectMetadataQueryFactory({
|
||||||
idToDelete: listingObjectId,
|
input: { idToDelete: listingObjectId },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response =
|
const response =
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import request from 'supertest';
|
|||||||
import { deleteOneRoleOperationFactory } from 'test/integration/graphql/utils/delete-one-role-operation-factory.util';
|
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 { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||||
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.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 { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-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 { 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';
|
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
|
||||||
@ -38,19 +38,12 @@ describe('roles permissions', () => {
|
|||||||
let guestRoleId: string;
|
let guestRoleId: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const enablePermissionsQuery = updateFeatureFlagFactory(
|
|
||||||
SEED_APPLE_WORKSPACE_ID,
|
|
||||||
'IsPermissionsEnabled',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const enablePermissionsV2Query = updateFeatureFlagFactory(
|
const enablePermissionsV2Query = updateFeatureFlagFactory(
|
||||||
SEED_APPLE_WORKSPACE_ID,
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
'IsPermissionsV2Enabled',
|
'IsPermissionsV2Enabled',
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
await makeGraphqlAPIRequest(enablePermissionsQuery);
|
|
||||||
await makeGraphqlAPIRequest(enablePermissionsV2Query);
|
await makeGraphqlAPIRequest(enablePermissionsV2Query);
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
@ -460,14 +453,23 @@ describe('roles permissions', () => {
|
|||||||
let listingObjectId = '';
|
let listingObjectId = '';
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const { objectMetadataId: createdObjectId } =
|
const { data } = await createOneObjectMetadata({
|
||||||
await createListingCustomObject();
|
input: {
|
||||||
|
nameSingular: 'house',
|
||||||
|
namePlural: 'houses',
|
||||||
|
labelSingular: 'House',
|
||||||
|
labelPlural: 'Houses',
|
||||||
|
icon: 'IconBuildingSkyscraper',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
listingObjectId = createdObjectId;
|
listingObjectId = data.createOneObject.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteOneObjectMetadataItem(listingObjectId);
|
await deleteOneObjectMetadata({
|
||||||
|
input: { idToDelete: listingObjectId },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const upsertObjectPermissionMutation = ({
|
const upsertObjectPermissionMutation = ({
|
||||||
|
|||||||
@ -1,21 +1,33 @@
|
|||||||
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util';
|
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
import { deleteOneObjectMetadata } 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 { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
describe('createOne', () => {
|
describe('createOne', () => {
|
||||||
describe('FieldMetadataService name/label sync', () => {
|
describe('FieldMetadataService name/label sync', () => {
|
||||||
let listingObjectId = '';
|
let createdObjectMetadataId = '';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { objectMetadataId: createdObjectId } =
|
const {
|
||||||
await createListingCustomObject();
|
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 () => {
|
afterEach(async () => {
|
||||||
await deleteOneObjectMetadataItem(listingObjectId);
|
await deleteOneObjectMetadata({
|
||||||
|
input: { idToDelete: createdObjectMetadataId },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('should create a field when name and label are synced correctly', async () => {
|
it('should create a field when name and label are synced correctly', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
@ -24,13 +36,13 @@ describe('createOne', () => {
|
|||||||
name: FIELD_NAME,
|
name: FIELD_NAME,
|
||||||
label: 'Test Field',
|
label: 'Test Field',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
objectMetadataId: listingObjectId,
|
objectMetadataId: createdObjectMetadataId,
|
||||||
isLabelSyncedWithName: true,
|
isLabelSyncedWithName: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const graphqlOperation = createOneFieldMetadataFactory({
|
const { data } = await createOneFieldMetadata({
|
||||||
input: { field: createFieldInput },
|
input: createFieldInput,
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -39,10 +51,8 @@ describe('createOne', () => {
|
|||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
|
||||||
|
|
||||||
// Assert
|
// 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 () => {
|
it('should set isLabelSyncWithName to false if not in input', async () => {
|
||||||
@ -51,26 +61,22 @@ describe('createOne', () => {
|
|||||||
name: 'testField',
|
name: 'testField',
|
||||||
label: 'Test Field',
|
label: 'Test Field',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
objectMetadataId: listingObjectId,
|
objectMetadataId: createdObjectMetadataId,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const graphqlOperation = createOneFieldMetadataFactory({
|
const { data } = await createOneFieldMetadata({
|
||||||
input: { field: createFieldInput },
|
input: createFieldInput,
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
label
|
label
|
||||||
isLabelSyncedWithName
|
isLabelSyncedWithName
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(response.body.data.createOneField.isLabelSyncedWithName).toBe(
|
expect(data.createOneField.isLabelSyncedWithName).toBe(false);
|
||||||
false,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an error when name and label are not synced but isLabelSyncedWithName is true', async () => {
|
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',
|
name: 'testField',
|
||||||
label: 'Different Label',
|
label: 'Different Label',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
objectMetadataId: listingObjectId,
|
objectMetadataId: createdObjectMetadataId,
|
||||||
isLabelSyncedWithName: true,
|
isLabelSyncedWithName: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const graphqlOperation = createOneFieldMetadataFactory({
|
// Act
|
||||||
input: { field: createFieldInput },
|
const { errors } = await createOneFieldMetadata({
|
||||||
|
input: createFieldInput,
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
label
|
label
|
||||||
isLabelSyncedWithName
|
isLabelSyncedWithName
|
||||||
`,
|
`,
|
||||||
|
expectToFail: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
|
||||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(response.body.errors[0].message).toBe(
|
expect(errors[0].message).toBe(
|
||||||
'Name is not synced with label. Expected name: "differentLabel", got testField',
|
'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 { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
|
||||||
import { findOneOperationFactory } from 'test/integration/graphql/utils/find-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 { 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 { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||||
import { deleteOneFieldMetadataItemFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util';
|
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||||
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util';
|
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
import {
|
||||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
LISTING_NAME_PLURAL,
|
||||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
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('deleteOne', () => {
|
||||||
describe('Kanban aggregate operation', () => {
|
describe('Kanban aggregate operation', () => {
|
||||||
@ -15,14 +19,27 @@ describe('deleteOne', () => {
|
|||||||
let viewId = '';
|
let viewId = '';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { objectMetadataId: createdObjectId } =
|
const { data } = await createOneObjectMetadata({
|
||||||
await createListingCustomObject();
|
input: {
|
||||||
|
nameSingular: LISTING_NAME_SINGULAR,
|
||||||
|
namePlural: LISTING_NAME_PLURAL,
|
||||||
|
labelSingular: 'Listing',
|
||||||
|
labelPlural: 'Listings',
|
||||||
|
icon: 'IconBuildingSkyscraper',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
listingObjectId = createdObjectId;
|
listingObjectId = data.createOneObject.id;
|
||||||
const { fieldMetadataId: createdFieldMetadaId } =
|
const { data: createdFieldData } = await createOneFieldMetadata({
|
||||||
await createCustomTextFieldMetadata(createdObjectId);
|
input: {
|
||||||
|
name: 'house',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
label: 'House',
|
||||||
|
objectMetadataId: listingObjectId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
testFieldId = createdFieldMetadaId;
|
testFieldId = createdFieldData.createOneField.id;
|
||||||
|
|
||||||
// create view
|
// create view
|
||||||
const graphqlOperation = createOneOperationFactory({
|
const graphqlOperation = createOneOperationFactory({
|
||||||
@ -49,7 +66,9 @@ describe('deleteOne', () => {
|
|||||||
viewId = createdView.id;
|
viewId = createdView.id;
|
||||||
});
|
});
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await deleteOneObjectMetadataItem(listingObjectId);
|
await deleteOneObjectMetadata({
|
||||||
|
input: { idToDelete: listingObjectId },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('should reset kanban aggregate operation when deleting a field used as kanbanAggregateOperationFieldMetadataId', async () => {
|
it('should reset kanban aggregate operation when deleting a field used as kanbanAggregateOperationFieldMetadataId', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
@ -76,25 +95,25 @@ describe('deleteOne', () => {
|
|||||||
expect(viewResponse.body.data.view.kanbanAggregateOperation).toBe('MAX');
|
expect(viewResponse.body.data.view.kanbanAggregateOperation).toBe('MAX');
|
||||||
|
|
||||||
// Deactivate field to be able to delete it after
|
// Deactivate field to be able to delete it after
|
||||||
const deactivateFieldOperation = updateOneFieldMetadataFactory({
|
await updateOneFieldMetadata({
|
||||||
input: { id: testFieldId, update: { isActive: false } },
|
input: {
|
||||||
|
idToUpdate: testFieldId,
|
||||||
|
updatePayload: { isActive: false },
|
||||||
|
},
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
isActive
|
isActive
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await makeMetadataAPIRequest(deactivateFieldOperation);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const graphqlOperation = deleteOneFieldMetadataItemFactory({
|
const { data } = await deleteOneFieldMetadata({
|
||||||
idToDelete: testFieldId,
|
input: { idToDelete: testFieldId },
|
||||||
});
|
});
|
||||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// 1. Field is deleted
|
// 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
|
// 2. Kanban aggregate operation has been reset on view using this field as kanbanAggregateOperationFieldMetadataId
|
||||||
const updatedViewResponse =
|
const updatedViewResponse =
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import { createCustomTextFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-custom-text-field-metadata.util';
|
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||||
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util';
|
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
import {
|
||||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
LISTING_NAME_PLURAL,
|
||||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
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('updateOne', () => {
|
||||||
describe('FieldMetadataService name/label sync', () => {
|
describe('FieldMetadataService name/label sync', () => {
|
||||||
@ -10,18 +14,35 @@ describe('updateOne', () => {
|
|||||||
let testFieldId = '';
|
let testFieldId = '';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { objectMetadataId: createdObjectId } =
|
const { data } = await createOneObjectMetadata({
|
||||||
await createListingCustomObject();
|
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 } =
|
const { data: createdFieldMetadata } = await createOneFieldMetadata({
|
||||||
await createCustomTextFieldMetadata(createdObjectId);
|
input: {
|
||||||
|
objectMetadataId: listingObjectId,
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
name: 'testName',
|
||||||
|
label: 'Test name',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
testFieldId = createdFieldMetadaId;
|
testFieldId = createdFieldMetadata.createOneField.id;
|
||||||
});
|
});
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await deleteOneObjectMetadataItem(listingObjectId);
|
await deleteOneObjectMetadata({
|
||||||
|
input: { idToDelete: listingObjectId },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update a field name and label when they are synced correctly', async () => {
|
it('should update a field name and label when they are synced correctly', async () => {
|
||||||
@ -29,11 +50,12 @@ describe('updateOne', () => {
|
|||||||
const updateFieldInput = {
|
const updateFieldInput = {
|
||||||
name: 'newName',
|
name: 'newName',
|
||||||
label: 'New name',
|
label: 'New name',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
const { data } = await updateOneFieldMetadata({
|
||||||
input: { id: testFieldId, update: updateFieldInput },
|
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -42,10 +64,8 @@ describe('updateOne', () => {
|
|||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
|
||||||
|
|
||||||
// Assert
|
// 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 () => {
|
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
|
// Act
|
||||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
const { data } = await updateOneFieldMetadata({
|
||||||
input: { id: testFieldId, update: updateFieldInput },
|
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -67,33 +87,31 @@ describe('updateOne', () => {
|
|||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
|
||||||
|
|
||||||
// Assert
|
// 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 () => {
|
it('should not update a field name if it is not synced correctly with label and labelSync is true', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const updateFieldInput = {
|
const updateFieldInput = {
|
||||||
name: 'newName',
|
name: 'newName',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
const { errors } = await updateOneFieldMetadata({
|
||||||
input: { id: testFieldId, update: updateFieldInput },
|
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
label
|
label
|
||||||
isLabelSyncedWithName
|
isLabelSyncedWithName
|
||||||
`,
|
`,
|
||||||
|
expectToFail: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(response.body.errors[0].message).toBe(
|
expect(errors[0].message).toBe(
|
||||||
'Name is not synced with label. Expected name: "testName", got newName',
|
'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 { 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) => {
|
export const deleteOneFieldMetadata = async ({
|
||||||
const graphqlOperation = deleteOneFieldMetadataItemFactory({
|
input,
|
||||||
idToDelete: fieldMetadataId,
|
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 gql from 'graphql-tag';
|
||||||
|
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||||
|
|
||||||
type FieldsFactoryParams = {
|
export type FindManyFieldsMetadataFactoryInput = {
|
||||||
gqlFields: string;
|
filter: object;
|
||||||
input: {
|
paging: object;
|
||||||
filter: object;
|
|
||||||
paging: object;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fieldsMetadataFactory = ({
|
export const findManyFieldsMetadataQueryFactory = ({
|
||||||
gqlFields,
|
gqlFields = 'id',
|
||||||
input,
|
input,
|
||||||
}: FieldsFactoryParams) => ({
|
}: PerformMetadataQueryParams<FindManyFieldsMetadataFactoryInput>) => ({
|
||||||
query: gql`
|
query: gql`
|
||||||
query FieldsMetadata($filter: FieldFilter!, $paging: CursorPaging!) {
|
query FieldsMetadata($filter: FieldFilter!, $paging: CursorPaging!) {
|
||||||
fields(filter: $filter, paging: $paging) {
|
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_SINGULAR = 'listinga';
|
||||||
export const LISTING_NAME_PLURAL = 'listings';
|
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 { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||||
import { performFailingObjectMetadataCreation } from 'test/integration/utils/object-metadata/perform-failing-object-metadata-creation';
|
import { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input';
|
||||||
import { EachTestingContext } from 'twenty-shared/testing';
|
import { EachTestingContext } from 'twenty-shared/testing';
|
||||||
|
|
||||||
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||||
@ -124,9 +124,10 @@ const allTestsUseCases = [
|
|||||||
|
|
||||||
describe('Object metadata creation should fail', () => {
|
describe('Object metadata creation should fail', () => {
|
||||||
it.each(allTestsUseCases)('$title', async ({ context }) => {
|
it.each(allTestsUseCases)('$title', async ({ context }) => {
|
||||||
const errors = await performFailingObjectMetadataCreation(
|
const { errors } = await createOneObjectMetadata({
|
||||||
getMockCreateObjectInput(context),
|
input: getMockCreateObjectInput(context),
|
||||||
);
|
expectToFail: true,
|
||||||
|
});
|
||||||
|
|
||||||
expect(errors.length).toBe(1);
|
expect(errors.length).toBe(1);
|
||||||
const firstError = errors[0];
|
const firstError = errors[0];
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
import { findManyFieldsMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata-query-factory.util';
|
||||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
|
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util';
|
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||||
import { objectsMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/objects-metadata-factory.util';
|
import { findManyObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-query-factory.util';
|
||||||
import { updateOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-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 { 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 { 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 { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ describe('Custom object renaming', () => {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const standardObjectsGraphqlOperation = objectsMetadataFactory({
|
const standardObjectsGraphqlOperation = findManyObjectMetadataQueryFactory({
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
nameSingular
|
nameSingular
|
||||||
@ -50,7 +49,7 @@ describe('Custom object renaming', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldsGraphqlOperation = fieldsMetadataFactory({
|
const fieldsGraphqlOperation = findManyFieldsMetadataQueryFactory({
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -94,22 +93,18 @@ describe('Custom object renaming', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const graphqlOperation = createOneObjectMetadataFactory({
|
const { data } = await createOneObjectMetadata({
|
||||||
input: { object: LISTING_OBJECT },
|
input: LISTING_OBJECT,
|
||||||
gqlFields: `
|
gqlFields: `
|
||||||
id
|
id
|
||||||
nameSingular
|
nameSingular
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(response.body.data.createOneObject.nameSingular).toBe(
|
expect(data.createOneObject.nameSingular).toBe(LISTING_NAME_SINGULAR);
|
||||||
LISTING_NAME_SINGULAR,
|
|
||||||
);
|
|
||||||
|
|
||||||
listingObjectId = response.body.data.createOneObject.id;
|
listingObjectId = data.createOneObject.id;
|
||||||
|
|
||||||
const fields = await makeMetadataAPIRequest(fieldsGraphqlOperation);
|
const fields = await makeMetadataAPIRequest(fieldsGraphqlOperation);
|
||||||
|
|
||||||
@ -206,43 +201,31 @@ describe('Custom object renaming', () => {
|
|||||||
const HOUSE_NAME_PLURAL = 'houses';
|
const HOUSE_NAME_PLURAL = 'houses';
|
||||||
const HOUSE_LABEL_SINGULAR = 'House';
|
const HOUSE_LABEL_SINGULAR = 'House';
|
||||||
const HOUSE_LABEL_PLURAL = 'Houses';
|
const HOUSE_LABEL_PLURAL = 'Houses';
|
||||||
const updateListingNameGraphqlOperation =
|
|
||||||
updateOneObjectMetadataItemFactory({
|
// Act
|
||||||
gqlFields: `
|
const { data } = await updateOneObjectMetadata({
|
||||||
|
gqlFields: `
|
||||||
nameSingular
|
nameSingular
|
||||||
labelSingular
|
labelSingular
|
||||||
namePlural
|
namePlural
|
||||||
labelPlural
|
labelPlural
|
||||||
`,
|
`,
|
||||||
input: {
|
input: {
|
||||||
idToUpdate: listingObjectId,
|
idToUpdate: listingObjectId,
|
||||||
updatePayload: {
|
updatePayload: {
|
||||||
nameSingular: HOUSE_NAME_SINGULAR,
|
nameSingular: HOUSE_NAME_SINGULAR,
|
||||||
namePlural: HOUSE_NAME_PLURAL,
|
namePlural: HOUSE_NAME_PLURAL,
|
||||||
labelSingular: HOUSE_LABEL_SINGULAR,
|
labelSingular: HOUSE_LABEL_SINGULAR,
|
||||||
labelPlural: HOUSE_LABEL_PLURAL,
|
labelPlural: HOUSE_LABEL_PLURAL,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
});
|
||||||
// Act
|
|
||||||
const updateListingNameResponse = await makeMetadataAPIRequest(
|
|
||||||
updateListingNameGraphqlOperation,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(
|
expect(data.updateOneObject.nameSingular).toBe(HOUSE_NAME_SINGULAR);
|
||||||
updateListingNameResponse.body.data.updateOneObject.nameSingular,
|
expect(data.updateOneObject.namePlural).toBe(HOUSE_NAME_PLURAL);
|
||||||
).toBe(HOUSE_NAME_SINGULAR);
|
expect(data.updateOneObject.labelSingular).toBe(HOUSE_LABEL_SINGULAR);
|
||||||
expect(updateListingNameResponse.body.data.updateOneObject.namePlural).toBe(
|
expect(data.updateOneObject.labelPlural).toBe(HOUSE_LABEL_PLURAL);
|
||||||
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 fieldsResponse = await makeMetadataAPIRequest(fieldsGraphqlOperation);
|
||||||
|
|
||||||
@ -306,14 +289,12 @@ describe('Custom object renaming', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('5. should delete custom object', async () => {
|
it('5. should delete custom object', async () => {
|
||||||
const graphqlOperation = deleteOneObjectMetadataItemFactory({
|
const { data } = await deleteOneObjectMetadata({
|
||||||
idToDelete: listingObjectId,
|
input: {
|
||||||
|
idToDelete: listingObjectId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
expect(data.deleteOneObject.id).toBe(listingObjectId);
|
||||||
|
|
||||||
const deleteListingResponse = response.body.data.deleteOneObject;
|
|
||||||
|
|
||||||
expect(deleteListingResponse.id).toBe(listingObjectId);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
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 { getMockCreateObjectInput } from 'test/integration/utils/object-metadata/generate-mock-create-object-metadata-input';
|
import { deleteOneObjectMetadata } 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 { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input';
|
||||||
import { EachTestingContext } from 'twenty-shared/testing';
|
import { EachTestingContext } from 'twenty-shared/testing';
|
||||||
|
|
||||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
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', () => {
|
describe('Object metadata creation should succeed', () => {
|
||||||
it.each(allTestsUseCases)('$title', async ({ context }) => {
|
it.each(allTestsUseCases)('$title', async ({ context }) => {
|
||||||
const response = await performObjectMetadataCreation(
|
const { data } = await createOneObjectMetadata({
|
||||||
getMockCreateObjectInput(context),
|
input: getMockCreateObjectInput(context),
|
||||||
);
|
});
|
||||||
|
|
||||||
expect(response.body.data.createOneObject.id).toBeDefined();
|
expect(data.createOneObject.id).toBeDefined();
|
||||||
await deleteOneObjectMetadataItem(
|
await deleteOneObjectMetadata({
|
||||||
response.body.data.createOneObject.id,
|
input: { idToDelete: data.createOneObject.id },
|
||||||
).catch();
|
}).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 { 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 (
|
export const deleteOneObjectMetadata = async ({
|
||||||
objectMetadataItemId: string,
|
input,
|
||||||
) => {
|
gqlFields,
|
||||||
const graphqlOperation = deleteOneObjectMetadataItemFactory({
|
expectToFail = false,
|
||||||
idToDelete: objectMetadataItemId,
|
}: 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 gql from 'graphql-tag';
|
||||||
|
import { PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||||
|
|
||||||
type ObjectsFactoryParams = {
|
export type FindManyObjectMetadataFactoryInput = {
|
||||||
gqlFields: string;
|
filter: object;
|
||||||
input: {
|
paging: object;
|
||||||
filter: object;
|
|
||||||
paging: object;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const objectsMetadataFactory = ({
|
export const findManyObjectMetadataQueryFactory = ({
|
||||||
gqlFields,
|
gqlFields = 'id',
|
||||||
input,
|
input,
|
||||||
}: ObjectsFactoryParams) => ({
|
}: PerformMetadataQueryParams<FindManyObjectMetadataFactoryInput>) => ({
|
||||||
query: gql`
|
query: gql`
|
||||||
query ObjectsMetadata($filter: ObjectFilter!, $paging: CursorPaging!) {
|
query ObjectsMetadata($filter: ObjectFilter!, $paging: CursorPaging!) {
|
||||||
objects(filter: $filter, paging: $paging) {
|
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 = (
|
export const getMockCreateObjectInput = (
|
||||||
overrides?: Partial<Omit<CreateObjectInput, 'workspaceId' | 'dataSourceId'>>,
|
overrides?: Partial<Omit<CreateObjectInput, 'workspaceId' | 'dataSourceId'>>,
|
||||||
) => ({
|
) => ({
|
||||||
namePlural: 'listings',
|
namePlural: 'listingas',
|
||||||
nameSingular: 'listing',
|
nameSingular: 'listinga',
|
||||||
labelPlural: 'Listings',
|
labelPlural: 'Listings',
|
||||||
labelSingular: 'Listing',
|
labelSingular: 'Listing',
|
||||||
description: 'Listing object',
|
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