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:
Paul Rastoin
2025-11-23 22:37:28 +01:00
committed by GitHub
parent f9ab09c404
commit 0be228fc85
6 changed files with 684 additions and 36 deletions
@@ -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,
},
]
`;
@@ -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),
);
},
);
});