diff --git a/packages/twenty-server/src/engine/api/rest/core/controllers/rest-api-core.controller.ts b/packages/twenty-server/src/engine/api/rest/core/controllers/rest-api-core.controller.ts index f91c48c922..97917d957e 100644 --- a/packages/twenty-server/src/engine/api/rest/core/controllers/rest-api-core.controller.ts +++ b/packages/twenty-server/src/engine/api/rest/core/controllers/rest-api-core.controller.ts @@ -67,7 +67,6 @@ export class RestApiCoreController { res.status(201).send(result); } - //TODO: Refacto-common - Document this endpoint @Get('*path/groupBy') async handleApiGroupBy( @Req() request: AuthenticatedRequest, diff --git a/packages/twenty-server/src/engine/core-modules/open-api/open-api.service.ts b/packages/twenty-server/src/engine/core-modules/open-api/open-api.service.ts index 7e906117a2..27df66864e 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/open-api.service.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/open-api.service.ts @@ -27,6 +27,7 @@ import { import { computeBatchPath, computeDuplicatesResultPath, + computeGroupByResultPath, computeManyResultPath, computeMergeManyResultPath, computeRestoreManyResultPath, @@ -161,6 +162,11 @@ export class OpenApiService { flatObjectMetadataMaps, flatFieldMetadataMaps, ); + paths[`/${item.namePlural}/groupBy`] = computeGroupByResultPath( + item, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + ); return paths; }, schema.paths as OpenAPIV3_1.PathsObject); diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts index 2ac46ff91e..735f942762 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts @@ -60,8 +60,8 @@ describe('computeParameters', () => { expect(computeDepthParameters()).toEqual({ name: 'depth', in: 'query', - description: `Determines the level of nested related objects to include in the response. - - 0: Primary object only + description: `Determines the level of nested related objects to include in the response. + - 0: Primary object only - 1: Primary object + direct relations`, required: false, schema: { @@ -77,7 +77,7 @@ describe('computeParameters', () => { expect(computeFilterParameters()).toEqual({ name: 'filter', in: 'query', - description: `Format: field[COMPARATOR]:value,field2[COMPARATOR]:value2 + description: `Format: field[COMPARATOR]:value,field2[COMPARATOR]:value2 Refer to the filter section at the top of the page for more details.`, required: false, schema: { diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index a553eda44e..cdd63e32cb 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -7,15 +7,20 @@ import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfa import { generateRandomFieldValue } from 'src/engine/core-modules/open-api/utils/generate-random-field-value.util'; import { + computeAggregateParameters, computeDepthParameters, computeEndingBeforeParameters, computeFilterParameters, + computeGroupByParameters, computeIdPathParameter, + computeIncludeRecordsSampleParameters, computeLimitParameters, + computeOrderByForRecordsParameters, computeOrderByParameters, computeSoftDeleteParameters, computeStartingAfterParameters, computeUpsertParameters, + computeViewIdParameters, } from 'src/engine/core-modules/open-api/utils/parameters.utils'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; @@ -277,6 +282,11 @@ export const computeParameterComponents = ( softDelete: computeSoftDeleteParameters(), orderBy: computeOrderByParameters(), limit: computeLimitParameters(fromMetadata), + groupBy: computeGroupByParameters(), + viewId: computeViewIdParameters(), + aggregate: computeAggregateParameters(), + includeRecordsSample: computeIncludeRecordsSampleParameters(), + orderByForRecords: computeOrderByForRecordsParameters(), }; }; diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts index 63ac575ea7..b289bc75bf 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts @@ -49,8 +49,8 @@ export const computeDepthParameters = (): OpenAPIV3_1.ParameterObject => { return { name: 'depth', in: 'query', - description: `Determines the level of nested related objects to include in the response. - - 0: Primary object only + description: `Determines the level of nested related objects to include in the response. + - 0: Primary object only - 1: Primary object + direct relations`, required: false, schema: { @@ -93,7 +93,7 @@ export const computeFilterParameters = (): OpenAPIV3_1.ParameterObject => { return { name: 'filter', in: 'query', - description: `Format: field[COMPARATOR]:value,field2[COMPARATOR]:value2 + description: `Format: field[COMPARATOR]:value,field2[COMPARATOR]:value2 Refer to the filter section at the top of the page for more details.`, required: false, schema: { @@ -157,3 +157,98 @@ export const computeIdPathParameter = (): OpenAPIV3_1.ParameterObject => { }, }; }; + +export const computeGroupByParameters = (): OpenAPIV3_1.ParameterObject => { + return { + name: 'group_by', + in: 'query', + description: `Array of fields to group by. Each element can specify a field and optionally a subfield or granularity for date fields.`, + required: true, + schema: { + type: 'string', + }, + examples: { + simple: { + value: '[{"updatedAt": true}]', + summary: 'Group by a single field', + }, + subfield: { + value: '[{"assignee": {"name": true}}]', + summary: 'Group by a relation field subfield', + }, + dateGranularity: { + value: '[{"createdAt": {"granularity": "MONTH"}}]', + summary: 'Group by date with granularity (DAY, WEEK, MONTH, YEAR)', + }, + }, + }; +}; + +export const computeViewIdParameters = (): OpenAPIV3_1.ParameterObject => { + return { + name: 'view_id', + in: 'query', + description: 'View ID to apply filters from.', + required: false, + schema: { + type: 'string', + format: 'uuid', + }, + }; +}; + +export const computeIncludeRecordsSampleParameters = + (): OpenAPIV3_1.ParameterObject => { + return { + name: 'include_records_sample', + in: 'query', + description: + 'If true, includes a sample of records for each group in the response.', + required: false, + schema: { + type: 'boolean', + default: false, + }, + }; + }; + +export const computeAggregateParameters = (): OpenAPIV3_1.ParameterObject => { + return { + name: 'aggregate', + in: 'query', + description: `Array of aggregate operations to compute for each group.`, + required: false, + schema: { + type: 'string', + }, + examples: { + count: { + value: '["countNotEmptyId"]', + summary: 'Count non-empty IDs in each group', + }, + multiple: { + value: '["countNotEmptyId", "sumAmount"]', + summary: 'Multiple aggregate operations', + }, + }, + }; +}; + +export const computeOrderByForRecordsParameters = + (): OpenAPIV3_1.ParameterObject => { + return { + name: 'order_by_for_records', + in: 'query', + description: `Order by clause for records within each group. Only applicable when include_records_sample is true.`, + required: false, + schema: { + type: 'string', + }, + examples: { + simple: { + value: 'createdAt', + summary: 'Order records by createdAt', + }, + }, + }; + }; diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts index 2f53964f43..2ba588f5da 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts @@ -16,6 +16,7 @@ import { getFindDuplicatesResponse200, getFindManyResponse200, getFindOneResponse200, + getGroupByResponse200, getJsonResponse, getMergeManyResponse200, getRestoreManyResponse200, @@ -325,3 +326,37 @@ export const computeMergeManyResultPath = ( }, } as OpenAPIV3_1.PathItemObject; }; + +export const computeGroupByResultPath = ( + item: Pick, + _flatObjectMetadataMaps: Pick< + AllFlatEntityMaps, + 'flatObjectMetadataMaps' + >['flatObjectMetadataMaps'], + _flatFieldMetadataMaps: Pick< + AllFlatEntityMaps, + 'flatFieldMetadataMaps' + >['flatFieldMetadataMaps'], +): OpenAPIV3_1.PathItemObject => { + return { + get: { + tags: [item.namePlural], + summary: `Group By ${item.namePlural}`, + description: `Groups **${item.namePlural}** by specified fields and optionally computes aggregate values for each group.`, + operationId: `groupBy${capitalize(item.namePlural)}`, + parameters: [ + { $ref: '#/components/parameters/groupBy' }, + { $ref: '#/components/parameters/filter' }, + { $ref: '#/components/parameters/orderBy' }, + { $ref: '#/components/parameters/limit' }, + { $ref: '#/components/parameters/viewId' }, + { $ref: '#/components/parameters/aggregate' }, + { $ref: '#/components/parameters/includeRecordsSample' }, + { $ref: '#/components/parameters/orderByForRecords' }, + ], + responses: { + '200': getGroupByResponse200(item), + }, + }, + } as OpenAPIV3_1.PathItemObject; +}; diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts index feaed463e3..d55bf77610 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts @@ -468,3 +468,57 @@ export const getMergeManyResponse200 = ( }, }; }; + +export const getGroupByResponse200 = ( + item: Pick, +) => { + const schemaRef = `#/components/schemas/${capitalize( + item.nameSingular, + )}ForResponse`; + + return { + description: 'Successful operation', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + [`${item.namePlural}GroupBy`]: { + type: 'array', + items: { + type: 'object', + properties: { + groupByDimensionValues: { + type: 'array', + description: + 'Array of values representing each dimension in the group', + items: { + type: 'string', + }, + }, + records: { + type: 'array', + description: + 'Sample of records for this group (only present when include_records_sample is true)', + items: { + $ref: schemaRef, + }, + }, + }, + additionalProperties: { + type: 'number', + description: 'Aggregate values (e.g., countNotEmptyId)', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; +}; diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/array-field-filter-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/array-field-filter-input-validation.integration-spec.ts index a78368b711..88a74f32ac 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/array-field-filter-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/array-field-filter-input-validation.integration-spec.ts @@ -1,13 +1,16 @@ +import { failingFilterInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/filter-validation/constants/failing-filter-input-by-field-metadata-type.constant'; import { successfulFilterInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/filter-validation/constants/successful-filter-input-by-field-metadata-type.constant'; +import { testGqlFailingScenario } from 'test/integration/graphql/suites/inputs-validation/filter-validation/utils/test-gql-failing-scenario.util'; import { testGqlSuccessfulScenario } from 'test/integration/graphql/suites/inputs-validation/filter-validation/utils/test-gql-successful-scenario.util'; +import { testRestFailingScenario } from 'test/integration/graphql/suites/inputs-validation/filter-validation/utils/test-rest-failing-scenario.util'; import { testRestSuccessfulScenario } from 'test/integration/graphql/suites/inputs-validation/filter-validation/utils/test-rest-successful-scenario.util'; import { destroyManyObjectsMetadata } from 'test/integration/graphql/suites/inputs-validation/utils/destroy-many-objects-metadata'; import { setupTestObjectsWithAllFieldTypes } from 'test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util'; import { FieldMetadataType } from 'twenty-shared/types'; const FIELD_METADATA_TYPE = FieldMetadataType.ARRAY; -// const failingTestCases = -// failingFilterInputByFieldMetadataType[FIELD_METADATA_TYPE]; +const failingTestCases = + failingFilterInputByFieldMetadataType[FIELD_METADATA_TYPE]; const successfulTestCases = successfulFilterInputByFieldMetadataType[FIELD_METADATA_TYPE]; @@ -36,43 +39,42 @@ describe(`Filter args validation - ${FIELD_METADATA_TYPE}`, () => { ]); }); - // describe('Gql filter input - failure', () => { - // it.each( - // failingTestCases.map((testCase) => ({ - // ...testCase, - // stringifiedFilter: JSON.stringify(testCase.gqlFilterInput), - // })), - // )( - // `${FIELD_METADATA_TYPE} field type - should fail with filter : $stringifiedFilter`, - // async ({ gqlFilterInput: filter, gqlErrorMessage: errorMessage }) => { - // await testGqlFailingScenario( - // objectMetadataSingularName, - // objectMetadataPluralName, - // filter, - // errorMessage, - // ); - // }, - // ); - // }); + describe('Gql filter input - failure', () => { + it.each( + failingTestCases.map((testCase) => ({ + ...testCase, + stringifiedFilter: JSON.stringify(testCase.gqlFilterInput), + })), + )( + `${FIELD_METADATA_TYPE} field type - should fail with filter : $stringifiedFilter`, + async ({ gqlFilterInput: filter, gqlErrorMessage: errorMessage }) => { + await testGqlFailingScenario( + objectMetadataSingularName, + objectMetadataPluralName, + filter, + errorMessage, + ); + }, + ); + }); - // // TODO : Refacto-common - Uncomment this - // describe('Rest filter input - failure', () => { - // it.each( - // failingTestCases.map((testCase) => ({ - // ...testCase, - // stringifiedFilter: JSON.stringify(testCase.restFilterInput), - // })), - // )( - // `${FIELD_METADATA_TYPE} field type - should fail with filter : $stringifiedFilter`, - // async ({ restFilterInput: filter, restErrorMessage: errorMessage }) => { - // await testRestFailingScenario( - // objectMetadataPluralName, - // filter, - // errorMessage, - // ); - // }, - // ); - // }); + describe('Rest filter input - failure', () => { + it.each( + failingTestCases.map((testCase) => ({ + ...testCase, + stringifiedFilter: JSON.stringify(testCase.restFilterInput), + })), + )( + `${FIELD_METADATA_TYPE} field type - should fail with filter : $stringifiedFilter`, + async ({ restFilterInput: filter, restErrorMessage: errorMessage }) => { + await testRestFailingScenario( + objectMetadataPluralName, + filter, + errorMessage, + ); + }, + ); + }); describe('Gql filter input - success', () => { it.each( diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/boolean-field-filter-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/boolean-field-filter-input-validation.integration-spec.ts index 44288747f6..70ef65c411 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/boolean-field-filter-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/boolean-field-filter-input-validation.integration-spec.ts @@ -1,12 +1,15 @@ +import { failingFilterInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/filter-validation/constants/failing-filter-input-by-field-metadata-type.constant'; import { successfulFilterInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/filter-validation/constants/successful-filter-input-by-field-metadata-type.constant'; +import { testGqlFailingScenario } from 'test/integration/graphql/suites/inputs-validation/filter-validation/utils/test-gql-failing-scenario.util'; import { testGqlSuccessfulScenario } from 'test/integration/graphql/suites/inputs-validation/filter-validation/utils/test-gql-successful-scenario.util'; +import { testRestFailingScenario } from 'test/integration/graphql/suites/inputs-validation/filter-validation/utils/test-rest-failing-scenario.util'; import { testRestSuccessfulScenario } from 'test/integration/graphql/suites/inputs-validation/filter-validation/utils/test-rest-successful-scenario.util'; import { destroyManyObjectsMetadata } from 'test/integration/graphql/suites/inputs-validation/utils/destroy-many-objects-metadata'; import { setupTestObjectsWithAllFieldTypes } from 'test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util'; import { FieldMetadataType } from 'twenty-shared/types'; const FIELD_METADATA_TYPE = FieldMetadataType.BOOLEAN; -// const failingTestCases = -// failingFilterInputByFieldMetadataType[FIELD_METADATA_TYPE]; +const failingTestCases = + failingFilterInputByFieldMetadataType[FIELD_METADATA_TYPE]; const successfulTestCases = successfulFilterInputByFieldMetadataType[FIELD_METADATA_TYPE]; @@ -35,43 +38,42 @@ describe(`Filter input validation - ${FIELD_METADATA_TYPE}`, () => { ]); }); - // describe('Gql filter input - failure', () => { - // it.each( - // failingTestCases.map((testCase) => ({ - // ...testCase, - // stringifiedFilter: JSON.stringify(testCase.gqlFilterInput), - // })), - // )( - // `${FIELD_METADATA_TYPE} field type - should fail with filter : $stringifiedFilter`, - // async ({ gqlFilterInput: filter, gqlErrorMessage: errorMessage }) => { - // await testGqlFailingScenario( - // objectMetadataSingularName, - // objectMetadataPluralName, - // filter, - // errorMessage, - // ); - // }, - // ); - // }); + describe('Gql filter input - failure', () => { + it.each( + failingTestCases.map((testCase) => ({ + ...testCase, + stringifiedFilter: JSON.stringify(testCase.gqlFilterInput), + })), + )( + `${FIELD_METADATA_TYPE} field type - should fail with filter : $stringifiedFilter`, + async ({ gqlFilterInput: filter, gqlErrorMessage: errorMessage }) => { + await testGqlFailingScenario( + objectMetadataSingularName, + objectMetadataPluralName, + filter, + errorMessage, + ); + }, + ); + }); - // // TODO : Refacto-common - Uncomment this - // describe('Rest filter input - failure', () => { - // it.each( - // failingTestCases.map((testCase) => ({ - // ...testCase, - // stringifiedFilter: JSON.stringify(testCase.restFilterInput), - // })), - // )( - // `${FIELD_METADATA_TYPE} field type - should fail with filter : $stringifiedFilter`, - // async ({ restFilterInput: filter, restErrorMessage: errorMessage }) => { - // await testRestFailingScenario( - // objectMetadataPluralName, - // filter, - // errorMessage, - // ); - // }, - // ); - // }); + describe('Rest filter input - failure', () => { + it.each( + failingTestCases.map((testCase) => ({ + ...testCase, + stringifiedFilter: JSON.stringify(testCase.restFilterInput), + })), + )( + `${FIELD_METADATA_TYPE} field type - should fail with filter : $stringifiedFilter`, + async ({ restFilterInput: filter, restErrorMessage: errorMessage }) => { + await testRestFailingScenario( + objectMetadataPluralName, + filter, + errorMessage, + ); + }, + ); + }); describe('Gql filter input - success', () => { it.each( diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/constants/failing-filter-input-by-field-metadata-type.constant.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/constants/failing-filter-input-by-field-metadata-type.constant.ts index 96a4f5e5a5..5e352ca11e 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/constants/failing-filter-input-by-field-metadata-type.constant.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/constants/failing-filter-input-by-field-metadata-type.constant.ts @@ -197,33 +197,25 @@ export const failingFilterInputByFieldMetadataType: { }, ], [FieldMetadataType.BOOLEAN]: [ - // { - // gqlFilterInput: { booleanField: { eq: 'not-a-boolean' } }, - // gqlErrorMessage: - // 'Boolean cannot represent a non boolean value: "not-a-boolean"', - // // TODO - fix this, should throw an error - // // restFilterInput: 'booleanField[eq]:"not-a-boolean"', - // // restErrorMessage: 'invalid input syntax for type boolean', - // }, - // { - // gqlFilterInput: { booleanField: { eq: [] } }, - // gqlErrorMessage: 'Boolean cannot represent a non boolean value: []', - // // TODO - fix this, should throw an error - // // restFilterInput: 'booleanField[eq]:"[]"', - // // restErrorMessage: 'invalid input syntax for type boolean', - // }, - // { - // gqlFilterInput: { booleanField: { eq: 2 } }, - // gqlErrorMessage: 'Boolean cannot represent a non boolean value: 2', - // // TODO - fix this, should throw an error - // // restFilterInput: 'booleanField[eq]:2', - // // restErrorMessage: 'invalid input syntax for type boolean', - // }, - // TODO - fix this, should throw an error - // { - // gqlFilterInput: { booleanField: { eq: null } }, - // gqlErrorMessage: 'Boolean cannot represent a non boolean value: null', - // }, + { + gqlFilterInput: { booleanField: { eq: 'not-a-boolean' } }, + gqlErrorMessage: + 'Boolean cannot represent a non boolean value: "not-a-boolean"', + restFilterInput: 'booleanField[eq]:"not-a-boolean"', + restErrorMessage: 'invalid input syntax for type boolean', + }, + { + gqlFilterInput: { booleanField: { eq: [] } }, + gqlErrorMessage: 'Boolean cannot represent a non boolean value: []', + restFilterInput: 'booleanField[eq]:"[]"', + restErrorMessage: 'invalid input syntax for type boolean', + }, + { + gqlFilterInput: { booleanField: { eq: 2 } }, + gqlErrorMessage: 'Boolean cannot represent a non boolean value: 2', + restFilterInput: 'booleanField[eq]:2', + restErrorMessage: 'invalid input syntax for type boolean', + }, ], [FieldMetadataType.NUMBER]: [ // { @@ -403,38 +395,29 @@ export const failingFilterInputByFieldMetadataType: { // }, ], [FieldMetadataType.ARRAY]: [ - // { - // gqlFilterInput: { arrayField: { containsIlike: {} } }, - // gqlErrorMessage: 'cannot represent a non string value', - // // TODO - fix this ? containsIlike not existing for rest - // // restFilterInput: 'arrayField[containsIlike]:"{}"', - // // restErrorMessage: '', - // }, - // { - // gqlFilterInput: { arrayField: { containsIlike: [] } }, - // gqlErrorMessage: 'cannot represent a non string value', - // // TODO - fix this ? containsIlike not existing for rest - // // restFilterInput: 'arrayField[containsIlike]:"[]"', - // // restErrorMessage: '', - // }, - // { - // gqlFilterInput: { arrayField: { containsIlike: true } }, - // gqlErrorMessage: 'cannot represent a non string value', - // // TODO - fix this ? containsIlike not existing for rest - // // restFilterInput: 'arrayField[containsIlike]:"true"', - // // restErrorMessage: '', - // }, - // { - // gqlFilterInput: { arrayField: { containsIlike: 2 } }, - // gqlErrorMessage: 'cannot represent a non string value', - // // TODO - fix this ? containsIlike not existing for rest - // // restFilterInput: 'arrayField[containsIlike]:2', - // // restErrorMessage: '', - // }, - // TODO - ensure it should throw - // { - // gqlFilterInput: { arrayField: { containsIlike: null } }, - // gqlErrorMessage: 'cannot represent a non string value', - // }, + { + gqlFilterInput: { arrayField: { containsIlike: {} } }, + gqlErrorMessage: 'cannot represent a non string value', + restFilterInput: 'arrayField[containsAny]:"{}"', + restErrorMessage: 'array value expected', + }, + { + gqlFilterInput: { arrayField: { containsIlike: [] } }, + gqlErrorMessage: 'cannot represent a non string value', + restFilterInput: 'arrayField[containsAny]:"[]"', + restErrorMessage: 'array value expected', + }, + { + gqlFilterInput: { arrayField: { containsIlike: true } }, + gqlErrorMessage: 'cannot represent a non string value', + restFilterInput: 'arrayField[containsAny]:"true"', + restErrorMessage: 'array value expected', + }, + { + gqlFilterInput: { arrayField: { containsIlike: 2 } }, + gqlErrorMessage: 'cannot represent a non string value', + restFilterInput: 'arrayField[containsAny]:2', + restErrorMessage: 'array value expected', + }, ], }; diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/date-field-filter-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/date-field-filter-input-validation.integration-spec.ts index ba312264c2..e04c5fb671 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/date-field-filter-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/date-field-filter-input-validation.integration-spec.ts @@ -58,7 +58,6 @@ describe(`Filter input validation - ${FIELD_METADATA_TYPE}`, () => { ); }); - // TODO : Refacto-common - Uncomment this describe('Rest filter input - failure', () => { it.each( failingTestCases.map((testCase) => ({ diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/date-time-field-filter-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/date-time-field-filter-input-validation.integration-spec.ts index 9abd0df4c7..d4baa7c5d2 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/date-time-field-filter-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/date-time-field-filter-input-validation.integration-spec.ts @@ -58,7 +58,6 @@ describe(`Filter input validation - ${FIELD_METADATA_TYPE}`, () => { ); }); - // TODO : Refacto-common - Uncomment this describe('Rest filter input - failure', () => { it.each( failingTestCases.map((testCase) => ({ diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/morph-relation-field-filter-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/morph-relation-field-filter-input-validation.integration-spec.ts index 100de12b7f..e5011396a0 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/morph-relation-field-filter-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/morph-relation-field-filter-input-validation.integration-spec.ts @@ -58,7 +58,6 @@ describe(`Filter input validation - ${FIELD_METADATA_TYPE}`, () => { ); }); - // TODO : Refacto-common - Uncomment this describe('Rest filter input - failure', () => { it.each( failingTestCases.map((testCase) => ({ diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/rating-field-filter-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/rating-field-filter-input-validation.integration-spec.ts index 4740947ea6..753e8cd426 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/rating-field-filter-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/rating-field-filter-input-validation.integration-spec.ts @@ -58,7 +58,6 @@ describe(`Filter input validation - ${FIELD_METADATA_TYPE}`, () => { ); }); - // TODO : Refacto-common - Uncomment this describe('Rest filter input - failure', () => { it.each( failingTestCases.map((testCase) => ({ diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/relation-field-filter-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/relation-field-filter-input-validation.integration-spec.ts index 62987f97ed..29f5b68ad6 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/relation-field-filter-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/relation-field-filter-input-validation.integration-spec.ts @@ -58,7 +58,6 @@ describe(`Filter input validation - ${FIELD_METADATA_TYPE}`, () => { ); }); - // TODO : Refacto-common - Uncomment this describe('Rest filter input - failure', () => { it.each( failingTestCases.map((testCase) => ({ diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/uuid-field-filter-input-validation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/uuid-field-filter-input-validation.integration-spec.ts index 9d9c37885c..f63e39f75e 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/uuid-field-filter-input-validation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/filter-validation/uuid-field-filter-input-validation.integration-spec.ts @@ -58,7 +58,6 @@ describe(`Filter input validation - ${FIELD_METADATA_TYPE}`, () => { ); }); - // TODO : Refacto-common - Uncomment this describe('Rest filter input - failure', () => { it.each( failingTestCases.map((testCase) => ({