Attest standard object isActive update regression + TDD tests (#15976)
# Introduction Related https://github.com/twentyhq/twenty/issues/15846 The root cause is that universalIdentifier is still optional in database and fallbacked when extracted out of database to standardId. But all `BaseWorkspaceEntity` and `CustomWorkspaceEntity` share the same standardId for their default standard fields `createdAt` `deletedAt` resulting in such compare result in dispatcher ```ts { "initialDispatcher": { "createdFlatEntityMaps": { "byId": {}, "idByUniversalIdentifier": {}, "universalIdentifiersByApplicationId": {} }, "deletedFlatEntityMaps": { "byId": {}, "idByUniversalIdentifier": {}, "universalIdentifiersByApplicationId": {} }, "updatedFlatEntityMaps": { "byId": { "55e1568c-eb87-4b8a-9f1b-19bbf6042f3e": { "updates": [ { "from": "Deletion date", "to": "Date when the record was deleted", "property": "description" }, { "from": "IconCalendarClock", "to": "IconCalendarMinus", "property": "icon" }, { "from": false, "to": true, "property": "isLabelSyncedWithName" }, { "from": null, "to": { "displayFormat": "RELATIVE" }, "property": "settings" } ] } } } }, "fromFlatEntity": { "universalIdentifier": "20202020-b9a7-48d8-8387-b9a3090a50ec", "applicationId": null, "id": "9c97c8bf-1f64-463c-915c-f68f41d3cd60", "standardId": "20202020-b9a7-48d8-8387-b9a3090a50ec", "objectMetadataId": "e9565126-8351-457b-b003-3ea4c6d253bc", "type": "DATE_TIME", "name": "deletedAt", "label": "Deleted at", "defaultValue": null, "description": "Deletion date", "icon": "IconCalendarClock", "standardOverrides": null, "options": null, "settings": null, "isCustom": false, "isActive": true, "isSystem": false, "isUIReadOnly": true, "isNullable": true, "isUnique": false, "workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419", "isLabelSyncedWithName": false, "relationTargetFieldMetadataId": null, "relationTargetObjectMetadataId": null, "morphId": null, "createdAt": "2025-11-20T17:28:45.474Z", "updatedAt": "2025-11-20T17:28:45.474Z", "kanbanAggregateOperationViewIds": [], "calendarViewIds": [], "viewGroupIds": [], "viewFieldIds": [], "viewFilterIds": [] }, "toFlatEntity": { "universalIdentifier": "20202020-b9a7-48d8-8387-b9a3090a50ec", "applicationId": null, "id": "55e1568c-eb87-4b8a-9f1b-19bbf6042f3e", "standardId": "20202020-b9a7-48d8-8387-b9a3090a50ec", "objectMetadataId": "37263f48-6858-4d28-a6e1-5f7321e49c24", "type": "DATE_TIME", "name": "deletedAt", "label": "Deleted at", "defaultValue": null, "description": "Date when the record was deleted", "icon": "IconCalendarMinus", "standardOverrides": null, "options": null, "settings": { "displayFormat": "RELATIVE" }, "isCustom": false, "isActive": true, "isSystem": false, "isUIReadOnly": true, "isNullable": true, "isUnique": false, "workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419", "isLabelSyncedWithName": true, "relationTargetFieldMetadataId": null, "relationTargetObjectMetadataId": null, "morphId": null, "createdAt": "2025-11-20T17:28:44.267Z", "updatedAt": "2025-11-21T17:17:55.057Z", "kanbanAggregateOperationViewIds": [], "calendarViewIds": [], "viewGroupIds": [], "viewFieldIds": [], "viewFilterIds": [] } } ``` ## Impact - This might be corrupting label and description of an other standard field of an other object - Race condition on latest universalIdentifier assigned in cache making the update sometime accurate sometimes not ## Fix Will be fixed by the in coming work on applicationId and universalIdentifier as required in database + upgrade command that will handle retro-comp. ( won't handle description corruption though, should be anecdotical ) https://github.com/twentyhq/twenty/pull/15911 ( handling this only for new workspace, retro comp upgrade command will be coming just after ) ## PR scope - Introduce TDD integration tests as failing - Added unit test to critical methods that might have been involved in the root cause ( still worth it to keep ) --------- Co-authored-by: guillim <guigloo@msn.com>
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compareTwoFlatEntity It should detect flat field metadata isActive diff from true to false 1`] = `
|
||||
[
|
||||
{
|
||||
"from": true,
|
||||
"property": "isActive",
|
||||
"to": false,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`compareTwoFlatEntity It should detect flat field metadata isActive diff from true to false 2`] = `
|
||||
[
|
||||
{
|
||||
"from": false,
|
||||
"property": "isActive",
|
||||
"to": true,
|
||||
},
|
||||
]
|
||||
`;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
import {
|
||||
eachTestingContextFilter,
|
||||
type EachTestingContext,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType, type FromTo } from 'twenty-shared/types';
|
||||
|
||||
import { ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-properties-to-compare-and-stringify.constant';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { compareTwoFlatEntity } from 'src/engine/metadata-modules/flat-entity/utils/compare-two-flat-entity.util';
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
|
||||
type TestContext<T extends AllMetadataName = AllMetadataName> = FromTo<
|
||||
MetadataFlatEntity<T>,
|
||||
'flatEntity'
|
||||
> & { metadataName: T };
|
||||
|
||||
describe('compareTwoFlatEntity', () => {
|
||||
const testCases = [
|
||||
{
|
||||
title:
|
||||
'It should detect flat field metadata isActive diff from true to false',
|
||||
context: {
|
||||
fromFlatEntity: getFlatFieldMetadataMock({
|
||||
objectMetadataId: 'object-metadata-id',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: 'universal-identifier',
|
||||
isActive: true,
|
||||
}),
|
||||
metadataName: 'fieldMetadata',
|
||||
toFlatEntity: getFlatFieldMetadataMock({
|
||||
objectMetadataId: 'object-metadata-id',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: 'universal-identifier',
|
||||
isActive: false,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'It should detect flat field metadata isActive diff from true to false',
|
||||
context: {
|
||||
fromFlatEntity: getFlatFieldMetadataMock({
|
||||
objectMetadataId: 'object-metadata-id',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: 'universal-identifier',
|
||||
isActive: false,
|
||||
}),
|
||||
metadataName: 'fieldMetadata',
|
||||
toFlatEntity: getFlatFieldMetadataMock({
|
||||
objectMetadataId: 'object-metadata-id',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: 'universal-identifier',
|
||||
isActive: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
] as const satisfies EachTestingContext<TestContext>[];
|
||||
|
||||
test.each(eachTestingContextFilter(testCases))(
|
||||
'$title',
|
||||
({ context: { metadataName, fromFlatEntity, toFlatEntity } }) => {
|
||||
const result = compareTwoFlatEntity({
|
||||
fromFlatEntity,
|
||||
propertiesToCompare:
|
||||
ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY[metadataName]
|
||||
.propertiesToCompare as any,
|
||||
propertiesToStringify:
|
||||
ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY[metadataName]
|
||||
.propertiesToStringify as any,
|
||||
toFlatEntity,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot(
|
||||
extractRecordIdsAndDatesAsExpectAny(result),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user