@open-mercato/enterprise 0.4.5-develop-17cfa89764 → 0.4.5-develop-eeccf7adf4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/package.json +4 -4
  2. package/dist/modules/record_locks/__integration__/TC-LOCK-001.spec.js +0 -73
  3. package/dist/modules/record_locks/__integration__/TC-LOCK-001.spec.js.map +0 -7
  4. package/dist/modules/record_locks/__integration__/TC-LOCK-002.spec.js +0 -114
  5. package/dist/modules/record_locks/__integration__/TC-LOCK-002.spec.js.map +0 -7
  6. package/dist/modules/record_locks/__integration__/TC-LOCK-003.spec.js +0 -119
  7. package/dist/modules/record_locks/__integration__/TC-LOCK-003.spec.js.map +0 -7
  8. package/dist/modules/record_locks/__integration__/TC-LOCK-004.spec.js +0 -119
  9. package/dist/modules/record_locks/__integration__/TC-LOCK-004.spec.js.map +0 -7
  10. package/dist/modules/record_locks/__integration__/TC-LOCK-005.spec.js +0 -90
  11. package/dist/modules/record_locks/__integration__/TC-LOCK-005.spec.js.map +0 -7
  12. package/dist/modules/record_locks/__integration__/TC-LOCK-006.spec.js +0 -90
  13. package/dist/modules/record_locks/__integration__/TC-LOCK-006.spec.js.map +0 -7
  14. package/dist/modules/record_locks/__integration__/TC-LOCK-007.spec.js +0 -211
  15. package/dist/modules/record_locks/__integration__/TC-LOCK-007.spec.js.map +0 -7
  16. package/dist/modules/record_locks/__integration__/helpers/recordLocks.js +0 -219
  17. package/dist/modules/record_locks/__integration__/helpers/recordLocks.js.map +0 -7
  18. package/src/modules/record_locks/__integration__/TC-LOCK-001.spec.ts +0 -84
  19. package/src/modules/record_locks/__integration__/TC-LOCK-002.spec.ts +0 -129
  20. package/src/modules/record_locks/__integration__/TC-LOCK-003.spec.ts +0 -136
  21. package/src/modules/record_locks/__integration__/TC-LOCK-004.spec.ts +0 -136
  22. package/src/modules/record_locks/__integration__/TC-LOCK-005.spec.ts +0 -106
  23. package/src/modules/record_locks/__integration__/TC-LOCK-006.spec.ts +0 -113
  24. package/src/modules/record_locks/__integration__/TC-LOCK-007.spec.ts +0 -251
  25. package/src/modules/record_locks/__integration__/helpers/recordLocks.ts +0 -366
  26. package/src/modules/record_locks/__tests__/config.test.ts +0 -21
  27. package/src/modules/record_locks/__tests__/crudMutationGuardService.test.ts +0 -106
  28. package/src/modules/record_locks/__tests__/recordLockService.test.ts +0 -1226
  29. package/src/modules/record_locks/__tests__/recordLockWidgetHeaders.test.ts +0 -127
  30. package/src/modules/record_locks/api/__tests__/acquire.route.test.ts +0 -175
  31. package/src/modules/record_locks/api/__tests__/release.route.test.ts +0 -135
  32. package/src/modules/record_locks/api/__tests__/settings.route.test.ts +0 -85
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.4.5-develop-17cfa89764",
3
+ "version": "0.4.5-develop-eeccf7adf4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -64,9 +64,9 @@
64
64
  }
65
65
  },
66
66
  "dependencies": {
67
- "@open-mercato/core": "0.4.5-develop-17cfa89764",
68
- "@open-mercato/shared": "0.4.5-develop-17cfa89764",
69
- "@open-mercato/ui": "0.4.5-develop-17cfa89764"
67
+ "@open-mercato/core": "0.4.5-develop-eeccf7adf4",
68
+ "@open-mercato/shared": "0.4.5-develop-eeccf7adf4",
69
+ "@open-mercato/ui": "0.4.5-develop-eeccf7adf4"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/jest": "^30.0.0",
@@ -1,73 +0,0 @@
1
- import { expect, test } from "@playwright/test";
2
- import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
3
- import { createCompanyFixture } from "@open-mercato/core/modules/core/__integration__/helpers/crmFixtures";
4
- import {
5
- acquireRecordLock,
6
- cleanupCompany,
7
- getRecordLockSettings,
8
- releaseRecordLock,
9
- saveRecordLockSettings,
10
- updateCompany
11
- } from "./helpers/recordLocks.js";
12
- test.describe("TC-LOCK-001: Pessimistic lock blocks a second editor", () => {
13
- test.describe.configure({ timeout: 9e4 });
14
- test("should return 423 for secondary editor update while lock is active", async ({ request }) => {
15
- const superadminToken = await getAuthToken(request, "superadmin");
16
- const adminToken = await getAuthToken(request, "admin");
17
- let previousSettings = null;
18
- let companyId = null;
19
- let ownerLockToken = null;
20
- try {
21
- previousSettings = await getRecordLockSettings(request, superadminToken);
22
- await saveRecordLockSettings(request, superadminToken, {
23
- ...previousSettings,
24
- enabled: true,
25
- strategy: "pessimistic",
26
- enabledResources: ["customers.company"]
27
- });
28
- companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-001 Company ${Date.now()}`);
29
- const ownerAcquire = await acquireRecordLock(request, superadminToken, "customers.company", companyId);
30
- expect(ownerAcquire.status).toBe(200);
31
- expect(ownerAcquire.body?.ok).toBe(true);
32
- ownerLockToken = ownerAcquire.body?.lock?.token ?? null;
33
- expect(ownerLockToken).toBeTruthy();
34
- const blockedUpdate = await updateCompany(
35
- request,
36
- adminToken,
37
- companyId,
38
- `QA TC-LOCK-001 Blocked Update ${Date.now()}`
39
- );
40
- expect(blockedUpdate.status).toBe(423);
41
- expect(blockedUpdate.body?.code).toBe("record_locked");
42
- const ownerRelease = await releaseRecordLock(
43
- request,
44
- superadminToken,
45
- "customers.company",
46
- companyId,
47
- ownerLockToken
48
- );
49
- expect(ownerRelease.status).toBe(200);
50
- expect(ownerRelease.body?.released).toBe(true);
51
- ownerLockToken = null;
52
- const updateAfterRelease = await updateCompany(
53
- request,
54
- adminToken,
55
- companyId,
56
- `QA TC-LOCK-001 Unblocked Update ${Date.now()}`
57
- );
58
- expect(updateAfterRelease.status).toBe(200);
59
- expect(updateAfterRelease.body?.ok).toBe(true);
60
- } finally {
61
- if (ownerLockToken && companyId) {
62
- await releaseRecordLock(request, superadminToken, "customers.company", companyId, ownerLockToken).catch(() => {
63
- });
64
- }
65
- await cleanupCompany(request, adminToken, companyId);
66
- if (previousSettings) {
67
- await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {
68
- });
69
- }
70
- }
71
- });
72
- });
73
- //# sourceMappingURL=TC-LOCK-001.spec.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/record_locks/__integration__/TC-LOCK-001.spec.ts"],
4
- "sourcesContent": ["import { expect, test } from '@playwright/test';\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api';\nimport { createCompanyFixture } from '@open-mercato/core/modules/core/__integration__/helpers/crmFixtures';\nimport {\n acquireRecordLock,\n cleanupCompany,\n getRecordLockSettings,\n releaseRecordLock,\n saveRecordLockSettings,\n updateCompany,\n type RecordLockSettings,\n} from './helpers/recordLocks';\n\n/**\n * TC-LOCK-001: Pessimistic lock blocks a second editor\n */\ntest.describe('TC-LOCK-001: Pessimistic lock blocks a second editor', () => {\n test.describe.configure({ timeout: 90_000 });\n\n test('should return 423 for secondary editor update while lock is active', async ({ request }) => {\n const superadminToken = await getAuthToken(request, 'superadmin');\n const adminToken = await getAuthToken(request, 'admin');\n\n let previousSettings: RecordLockSettings | null = null;\n let companyId: string | null = null;\n let ownerLockToken: string | null = null;\n\n try {\n previousSettings = await getRecordLockSettings(request, superadminToken);\n await saveRecordLockSettings(request, superadminToken, {\n ...previousSettings,\n enabled: true,\n strategy: 'pessimistic',\n enabledResources: ['customers.company'],\n });\n\n companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-001 Company ${Date.now()}`);\n\n const ownerAcquire = await acquireRecordLock(request, superadminToken, 'customers.company', companyId);\n expect(ownerAcquire.status).toBe(200);\n expect(ownerAcquire.body?.ok).toBe(true);\n ownerLockToken =\n (ownerAcquire.body?.lock as { token?: string | null } | undefined)?.token ?? null;\n expect(ownerLockToken).toBeTruthy();\n\n const blockedUpdate = await updateCompany(\n request,\n adminToken,\n companyId,\n `QA TC-LOCK-001 Blocked Update ${Date.now()}`,\n );\n expect(blockedUpdate.status).toBe(423);\n expect(blockedUpdate.body?.code).toBe('record_locked');\n\n const ownerRelease = await releaseRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n ownerLockToken as string,\n );\n expect(ownerRelease.status).toBe(200);\n expect(ownerRelease.body?.released).toBe(true);\n ownerLockToken = null;\n\n const updateAfterRelease = await updateCompany(\n request,\n adminToken,\n companyId,\n `QA TC-LOCK-001 Unblocked Update ${Date.now()}`,\n );\n expect(updateAfterRelease.status).toBe(200);\n expect(updateAfterRelease.body?.ok).toBe(true);\n } finally {\n if (ownerLockToken && companyId) {\n await releaseRecordLock(request, superadminToken, 'customers.company', companyId, ownerLockToken).catch(() => {});\n }\n await cleanupCompany(request, adminToken, companyId);\n if (previousSettings) {\n await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {});\n }\n }\n });\n});\n"],
5
- "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAKP,KAAK,SAAS,wDAAwD,MAAM;AAC1E,OAAK,SAAS,UAAU,EAAE,SAAS,IAAO,CAAC;AAE3C,OAAK,sEAAsE,OAAO,EAAE,QAAQ,MAAM;AAChG,UAAM,kBAAkB,MAAM,aAAa,SAAS,YAAY;AAChE,UAAM,aAAa,MAAM,aAAa,SAAS,OAAO;AAEtD,QAAI,mBAA8C;AAClD,QAAI,YAA2B;AAC/B,QAAI,iBAAgC;AAEpC,QAAI;AACF,yBAAmB,MAAM,sBAAsB,SAAS,eAAe;AACvE,YAAM,uBAAuB,SAAS,iBAAiB;AAAA,QACrD,GAAG;AAAA,QACH,SAAS;AAAA,QACT,UAAU;AAAA,QACV,kBAAkB,CAAC,mBAAmB;AAAA,MACxC,CAAC;AAED,kBAAY,MAAM,qBAAqB,SAAS,YAAY,0BAA0B,KAAK,IAAI,CAAC,EAAE;AAElG,YAAM,eAAe,MAAM,kBAAkB,SAAS,iBAAiB,qBAAqB,SAAS;AACrG,aAAO,aAAa,MAAM,EAAE,KAAK,GAAG;AACpC,aAAO,aAAa,MAAM,EAAE,EAAE,KAAK,IAAI;AACvC,uBACG,aAAa,MAAM,MAAgD,SAAS;AAC/E,aAAO,cAAc,EAAE,WAAW;AAElC,YAAM,gBAAgB,MAAM;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,QACA,iCAAiC,KAAK,IAAI,CAAC;AAAA,MAC7C;AACA,aAAO,cAAc,MAAM,EAAE,KAAK,GAAG;AACrC,aAAO,cAAc,MAAM,IAAI,EAAE,KAAK,eAAe;AAErD,YAAM,eAAe,MAAM;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,aAAO,aAAa,MAAM,EAAE,KAAK,GAAG;AACpC,aAAO,aAAa,MAAM,QAAQ,EAAE,KAAK,IAAI;AAC7C,uBAAiB;AAEjB,YAAM,qBAAqB,MAAM;AAAA,QAC/B;AAAA,QACA;AAAA,QACA;AAAA,QACA,mCAAmC,KAAK,IAAI,CAAC;AAAA,MAC/C;AACA,aAAO,mBAAmB,MAAM,EAAE,KAAK,GAAG;AAC1C,aAAO,mBAAmB,MAAM,EAAE,EAAE,KAAK,IAAI;AAAA,IAC/C,UAAE;AACA,UAAI,kBAAkB,WAAW;AAC/B,cAAM,kBAAkB,SAAS,iBAAiB,qBAAqB,WAAW,cAAc,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAClH;AACA,YAAM,eAAe,SAAS,YAAY,SAAS;AACnD,UAAI,kBAAkB;AACpB,cAAM,uBAAuB,SAAS,iBAAiB,gBAAgB,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACzF;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;",
6
- "names": []
7
- }
@@ -1,114 +0,0 @@
1
- import { expect, test } from "@playwright/test";
2
- import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
3
- import { createCompanyFixture } from "@open-mercato/core/modules/core/__integration__/helpers/crmFixtures";
4
- import {
5
- acquireRecordLock,
6
- cleanupCompany,
7
- buildScopeCookieFromToken,
8
- getCompanyDisplayName,
9
- getRecordLockSettings,
10
- releaseRecordLock,
11
- saveRecordLockSettings,
12
- updateCompany,
13
- waitForNotification
14
- } from "./helpers/recordLocks.js";
15
- test.describe("TC-LOCK-002: Optimistic conflict with accept incoming path", () => {
16
- test.describe.configure({ timeout: 9e4 });
17
- test("should keep incoming change when conflicted editor accepts incoming", async ({ request }) => {
18
- const superadminToken = await getAuthToken(request, "superadmin");
19
- const adminToken = await getAuthToken(request, "admin");
20
- const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);
21
- const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : void 0;
22
- let previousSettings = null;
23
- let companyId = null;
24
- let ownerLockToken = null;
25
- try {
26
- previousSettings = await getRecordLockSettings(request, superadminToken);
27
- await saveRecordLockSettings(request, superadminToken, {
28
- ...previousSettings,
29
- enabled: true,
30
- strategy: "optimistic",
31
- enabledResources: ["customers.company"],
32
- notifyOnConflict: true
33
- });
34
- companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-002 Company ${Date.now()}`);
35
- const acquire = await acquireRecordLock(
36
- request,
37
- superadminToken,
38
- "customers.company",
39
- companyId,
40
- superadminScopeHeaders
41
- );
42
- expect(acquire.status).toBe(200);
43
- ownerLockToken = acquire.body?.lock?.token ?? null;
44
- const baseLogId = acquire.body?.latestActionLogId ?? null;
45
- expect(ownerLockToken).toBeTruthy();
46
- expect(baseLogId).toBeTruthy();
47
- const incomingName = `QA TC-LOCK-002 Incoming ${Date.now()}`;
48
- const incomingUpdate = await updateCompany(request, adminToken, companyId, incomingName);
49
- expect(incomingUpdate.status).toBe(200);
50
- const staleUpdate = await updateCompany(
51
- request,
52
- superadminToken,
53
- companyId,
54
- `QA TC-LOCK-002 Mine ${Date.now()}`,
55
- {
56
- token: ownerLockToken,
57
- baseLogId,
58
- resolution: "normal"
59
- },
60
- superadminScopeHeaders
61
- );
62
- expect(staleUpdate.status).toBe(409);
63
- expect(staleUpdate.body?.code).toBe("record_lock_conflict");
64
- const conflictId = staleUpdate.body?.conflict?.id ?? null;
65
- expect(conflictId).toBeTruthy();
66
- const releaseResult = await releaseRecordLock(
67
- request,
68
- superadminToken,
69
- "customers.company",
70
- companyId,
71
- ownerLockToken,
72
- "conflict_resolved",
73
- {
74
- conflictId,
75
- resolution: "accept_incoming"
76
- },
77
- superadminScopeHeaders
78
- );
79
- expect(releaseResult.status).toBe(200);
80
- expect(releaseResult.body?.released).toBe(true);
81
- expect(releaseResult.body?.conflictResolved).toBe(true);
82
- ownerLockToken = null;
83
- const finalName = await getCompanyDisplayName(request, adminToken, companyId);
84
- expect(finalName).toBe(incomingName);
85
- const resolvedNotification = await waitForNotification(
86
- request,
87
- adminToken,
88
- "record_locks.conflict.resolved",
89
- (item) => item.sourceEntityId === conflictId
90
- );
91
- expect(resolvedNotification.bodyVariables?.resolution).toBe("accept_incoming");
92
- } finally {
93
- if (ownerLockToken && companyId) {
94
- await releaseRecordLock(
95
- request,
96
- superadminToken,
97
- "customers.company",
98
- companyId,
99
- ownerLockToken,
100
- "cancelled",
101
- void 0,
102
- superadminScopeHeaders
103
- ).catch(() => {
104
- });
105
- }
106
- await cleanupCompany(request, adminToken, companyId);
107
- if (previousSettings) {
108
- await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {
109
- });
110
- }
111
- }
112
- });
113
- });
114
- //# sourceMappingURL=TC-LOCK-002.spec.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/record_locks/__integration__/TC-LOCK-002.spec.ts"],
4
- "sourcesContent": ["import { expect, test } from '@playwright/test';\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api';\nimport { createCompanyFixture } from '@open-mercato/core/modules/core/__integration__/helpers/crmFixtures';\nimport {\n acquireRecordLock,\n cleanupCompany,\n buildScopeCookieFromToken,\n getCompanyDisplayName,\n getRecordLockSettings,\n releaseRecordLock,\n saveRecordLockSettings,\n updateCompany,\n waitForNotification,\n type RecordLockSettings,\n} from './helpers/recordLocks';\n\n/**\n * TC-LOCK-002: Optimistic conflict with accept incoming path\n */\ntest.describe('TC-LOCK-002: Optimistic conflict with accept incoming path', () => {\n test.describe.configure({ timeout: 90_000 });\n\n test('should keep incoming change when conflicted editor accepts incoming', async ({ request }) => {\n const superadminToken = await getAuthToken(request, 'superadmin');\n const adminToken = await getAuthToken(request, 'admin');\n const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);\n const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : undefined;\n\n let previousSettings: RecordLockSettings | null = null;\n let companyId: string | null = null;\n let ownerLockToken: string | null = null;\n\n try {\n previousSettings = await getRecordLockSettings(request, superadminToken);\n await saveRecordLockSettings(request, superadminToken, {\n ...previousSettings,\n enabled: true,\n strategy: 'optimistic',\n enabledResources: ['customers.company'],\n notifyOnConflict: true,\n });\n\n companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-002 Company ${Date.now()}`);\n\n const acquire = await acquireRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n superadminScopeHeaders,\n );\n expect(acquire.status).toBe(200);\n ownerLockToken = (acquire.body?.lock as { token?: string | null } | undefined)?.token ?? null;\n const baseLogId =\n (acquire.body as { latestActionLogId?: string | null } | null)?.latestActionLogId ?? null;\n expect(ownerLockToken).toBeTruthy();\n expect(baseLogId).toBeTruthy();\n\n const incomingName = `QA TC-LOCK-002 Incoming ${Date.now()}`;\n const incomingUpdate = await updateCompany(request, adminToken, companyId, incomingName);\n expect(incomingUpdate.status).toBe(200);\n\n const staleUpdate = await updateCompany(\n request,\n superadminToken,\n companyId,\n `QA TC-LOCK-002 Mine ${Date.now()}`,\n {\n token: ownerLockToken,\n baseLogId,\n resolution: 'normal',\n },\n superadminScopeHeaders,\n );\n\n expect(staleUpdate.status).toBe(409);\n expect(staleUpdate.body?.code).toBe('record_lock_conflict');\n const conflictId =\n (staleUpdate.body?.conflict as { id?: string } | undefined)?.id ?? null;\n expect(conflictId).toBeTruthy();\n\n const releaseResult = await releaseRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n ownerLockToken as string,\n 'conflict_resolved',\n {\n conflictId,\n resolution: 'accept_incoming',\n },\n superadminScopeHeaders,\n );\n expect(releaseResult.status).toBe(200);\n expect(releaseResult.body?.released).toBe(true);\n expect(releaseResult.body?.conflictResolved).toBe(true);\n ownerLockToken = null;\n\n const finalName = await getCompanyDisplayName(request, adminToken, companyId);\n expect(finalName).toBe(incomingName);\n\n const resolvedNotification = await waitForNotification(\n request,\n adminToken,\n 'record_locks.conflict.resolved',\n (item) => item.sourceEntityId === conflictId,\n );\n expect(resolvedNotification.bodyVariables?.resolution).toBe('accept_incoming');\n } finally {\n if (ownerLockToken && companyId) {\n await releaseRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n ownerLockToken,\n 'cancelled',\n undefined,\n superadminScopeHeaders,\n ).catch(() => {});\n }\n await cleanupCompany(request, adminToken, companyId);\n if (previousSettings) {\n await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {});\n }\n }\n });\n});\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,119 +0,0 @@
1
- import { expect, test } from "@playwright/test";
2
- import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
3
- import { createCompanyFixture } from "@open-mercato/core/modules/core/__integration__/helpers/crmFixtures";
4
- import {
5
- acquireRecordLock,
6
- cleanupCompany,
7
- buildScopeCookieFromToken,
8
- getCompanyDisplayName,
9
- getRecordLockSettings,
10
- releaseRecordLock,
11
- saveRecordLockSettings,
12
- updateCompany,
13
- waitForNotification
14
- } from "./helpers/recordLocks.js";
15
- test.describe("TC-LOCK-003: Accept mine conflict resolution and notification", () => {
16
- test.describe.configure({ timeout: 9e4 });
17
- test("should resolve conflict with accept_mine and notify incoming actor", async ({ request }) => {
18
- const superadminToken = await getAuthToken(request, "superadmin");
19
- const adminToken = await getAuthToken(request, "admin");
20
- const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);
21
- const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : void 0;
22
- let previousSettings = null;
23
- let companyId = null;
24
- let ownerLockToken = null;
25
- try {
26
- previousSettings = await getRecordLockSettings(request, superadminToken);
27
- await saveRecordLockSettings(request, superadminToken, {
28
- ...previousSettings,
29
- enabled: true,
30
- strategy: "optimistic",
31
- enabledResources: ["customers.company"],
32
- notifyOnConflict: true
33
- });
34
- companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-003 Company ${Date.now()}`);
35
- const acquire = await acquireRecordLock(
36
- request,
37
- superadminToken,
38
- "customers.company",
39
- companyId,
40
- superadminScopeHeaders
41
- );
42
- expect(acquire.status).toBe(200);
43
- ownerLockToken = acquire.body?.lock?.token ?? null;
44
- const baseLogId = acquire.body?.latestActionLogId ?? null;
45
- expect(ownerLockToken).toBeTruthy();
46
- expect(baseLogId).toBeTruthy();
47
- const incomingName = `QA TC-LOCK-003 Incoming ${Date.now()}`;
48
- const incomingUpdate = await updateCompany(request, adminToken, companyId, incomingName);
49
- expect(incomingUpdate.status).toBe(200);
50
- const conflictAttempt = await updateCompany(
51
- request,
52
- superadminToken,
53
- companyId,
54
- `QA TC-LOCK-003 Mine ${Date.now()}`,
55
- {
56
- token: ownerLockToken,
57
- baseLogId,
58
- resolution: "normal"
59
- },
60
- superadminScopeHeaders
61
- );
62
- expect(conflictAttempt.status).toBe(409);
63
- expect(conflictAttempt.body?.code).toBe("record_lock_conflict");
64
- const conflictId = conflictAttempt.body?.conflict?.id ?? null;
65
- expect(conflictId).toBeTruthy();
66
- await waitForNotification(
67
- request,
68
- superadminToken,
69
- "record_locks.conflict.detected",
70
- (item) => item.sourceEntityId === conflictId
71
- );
72
- const mineName = `QA TC-LOCK-003 Keep Mine ${Date.now()}`;
73
- const resolveAttempt = await updateCompany(
74
- request,
75
- superadminToken,
76
- companyId,
77
- mineName,
78
- {
79
- token: ownerLockToken,
80
- baseLogId,
81
- resolution: "accept_mine",
82
- conflictId
83
- },
84
- superadminScopeHeaders
85
- );
86
- expect(resolveAttempt.status).toBe(200);
87
- ownerLockToken = null;
88
- const finalName = await getCompanyDisplayName(request, adminToken, companyId);
89
- expect(finalName).toBe(mineName);
90
- const resolvedNotification = await waitForNotification(
91
- request,
92
- adminToken,
93
- "record_locks.conflict.resolved",
94
- (item) => item.sourceEntityId === conflictId
95
- );
96
- expect(resolvedNotification.bodyVariables?.resolution ?? "accept_mine").toBe("accept_mine");
97
- } finally {
98
- if (ownerLockToken && companyId) {
99
- await releaseRecordLock(
100
- request,
101
- superadminToken,
102
- "customers.company",
103
- companyId,
104
- ownerLockToken,
105
- "cancelled",
106
- void 0,
107
- superadminScopeHeaders
108
- ).catch(() => {
109
- });
110
- }
111
- await cleanupCompany(request, adminToken, companyId);
112
- if (previousSettings) {
113
- await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {
114
- });
115
- }
116
- }
117
- });
118
- });
119
- //# sourceMappingURL=TC-LOCK-003.spec.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/record_locks/__integration__/TC-LOCK-003.spec.ts"],
4
- "sourcesContent": ["import { expect, test } from '@playwright/test';\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api';\nimport { createCompanyFixture } from '@open-mercato/core/modules/core/__integration__/helpers/crmFixtures';\nimport {\n acquireRecordLock,\n cleanupCompany,\n buildScopeCookieFromToken,\n getCompanyDisplayName,\n getRecordLockSettings,\n releaseRecordLock,\n saveRecordLockSettings,\n updateCompany,\n waitForNotification,\n type RecordLockSettings,\n} from './helpers/recordLocks';\n\n/**\n * TC-LOCK-003: Accept mine conflict resolution and notification\n */\ntest.describe('TC-LOCK-003: Accept mine conflict resolution and notification', () => {\n test.describe.configure({ timeout: 90_000 });\n\n test('should resolve conflict with accept_mine and notify incoming actor', async ({ request }) => {\n const superadminToken = await getAuthToken(request, 'superadmin');\n const adminToken = await getAuthToken(request, 'admin');\n const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);\n const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : undefined;\n\n let previousSettings: RecordLockSettings | null = null;\n let companyId: string | null = null;\n let ownerLockToken: string | null = null;\n\n try {\n previousSettings = await getRecordLockSettings(request, superadminToken);\n await saveRecordLockSettings(request, superadminToken, {\n ...previousSettings,\n enabled: true,\n strategy: 'optimistic',\n enabledResources: ['customers.company'],\n notifyOnConflict: true,\n });\n\n companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-003 Company ${Date.now()}`);\n\n const acquire = await acquireRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n superadminScopeHeaders,\n );\n expect(acquire.status).toBe(200);\n ownerLockToken = (acquire.body?.lock as { token?: string | null } | undefined)?.token ?? null;\n const baseLogId =\n (acquire.body as { latestActionLogId?: string | null } | null)?.latestActionLogId ?? null;\n expect(ownerLockToken).toBeTruthy();\n expect(baseLogId).toBeTruthy();\n\n const incomingName = `QA TC-LOCK-003 Incoming ${Date.now()}`;\n const incomingUpdate = await updateCompany(request, adminToken, companyId, incomingName);\n expect(incomingUpdate.status).toBe(200);\n\n const conflictAttempt = await updateCompany(\n request,\n superadminToken,\n companyId,\n `QA TC-LOCK-003 Mine ${Date.now()}`,\n {\n token: ownerLockToken,\n baseLogId,\n resolution: 'normal',\n },\n superadminScopeHeaders,\n );\n expect(conflictAttempt.status).toBe(409);\n expect(conflictAttempt.body?.code).toBe('record_lock_conflict');\n\n const conflictId =\n (conflictAttempt.body?.conflict as { id?: string } | undefined)?.id ?? null;\n expect(conflictId).toBeTruthy();\n\n await waitForNotification(\n request,\n superadminToken,\n 'record_locks.conflict.detected',\n (item) => item.sourceEntityId === conflictId,\n );\n\n const mineName = `QA TC-LOCK-003 Keep Mine ${Date.now()}`;\n const resolveAttempt = await updateCompany(\n request,\n superadminToken,\n companyId,\n mineName,\n {\n token: ownerLockToken,\n baseLogId,\n resolution: 'accept_mine',\n conflictId,\n },\n superadminScopeHeaders,\n );\n expect(resolveAttempt.status).toBe(200);\n\n ownerLockToken = null;\n\n const finalName = await getCompanyDisplayName(request, adminToken, companyId);\n expect(finalName).toBe(mineName);\n\n const resolvedNotification = await waitForNotification(\n request,\n adminToken,\n 'record_locks.conflict.resolved',\n (item) => item.sourceEntityId === conflictId,\n );\n expect(resolvedNotification.bodyVariables?.resolution ?? 'accept_mine').toBe('accept_mine');\n } finally {\n if (ownerLockToken && companyId) {\n await releaseRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n ownerLockToken,\n 'cancelled',\n undefined,\n superadminScopeHeaders,\n ).catch(() => {});\n }\n await cleanupCompany(request, adminToken, companyId);\n if (previousSettings) {\n await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {});\n }\n }\n });\n});\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,119 +0,0 @@
1
- import { expect, test } from "@playwright/test";
2
- import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
3
- import { createCompanyFixture } from "@open-mercato/core/modules/core/__integration__/helpers/crmFixtures";
4
- import {
5
- acquireRecordLock,
6
- cleanupCompany,
7
- buildScopeCookieFromToken,
8
- getCompanyDisplayName,
9
- getRecordLockSettings,
10
- releaseRecordLock,
11
- saveRecordLockSettings,
12
- updateCompany,
13
- waitForNotification
14
- } from "./helpers/recordLocks.js";
15
- test.describe("TC-LOCK-004: Merged conflict resolution and notification", () => {
16
- test.describe.configure({ timeout: 9e4 });
17
- test("should resolve conflict with merged resolution and notify incoming actor", async ({ request }) => {
18
- const superadminToken = await getAuthToken(request, "superadmin");
19
- const adminToken = await getAuthToken(request, "admin");
20
- const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);
21
- const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : void 0;
22
- let previousSettings = null;
23
- let companyId = null;
24
- let ownerLockToken = null;
25
- try {
26
- previousSettings = await getRecordLockSettings(request, superadminToken);
27
- await saveRecordLockSettings(request, superadminToken, {
28
- ...previousSettings,
29
- enabled: true,
30
- strategy: "optimistic",
31
- enabledResources: ["customers.company"],
32
- notifyOnConflict: true
33
- });
34
- companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-004 Company ${Date.now()}`);
35
- const acquire = await acquireRecordLock(
36
- request,
37
- superadminToken,
38
- "customers.company",
39
- companyId,
40
- superadminScopeHeaders
41
- );
42
- expect(acquire.status).toBe(200);
43
- ownerLockToken = acquire.body?.lock?.token ?? null;
44
- const baseLogId = acquire.body?.latestActionLogId ?? null;
45
- expect(ownerLockToken).toBeTruthy();
46
- expect(baseLogId).toBeTruthy();
47
- const incomingName = `QA TC-LOCK-004 Incoming ${Date.now()}`;
48
- const incomingUpdate = await updateCompany(request, adminToken, companyId, incomingName);
49
- expect(incomingUpdate.status).toBe(200);
50
- const conflictAttempt = await updateCompany(
51
- request,
52
- superadminToken,
53
- companyId,
54
- `QA TC-LOCK-004 Mine ${Date.now()}`,
55
- {
56
- token: ownerLockToken,
57
- baseLogId,
58
- resolution: "normal"
59
- },
60
- superadminScopeHeaders
61
- );
62
- expect(conflictAttempt.status).toBe(409);
63
- expect(conflictAttempt.body?.code).toBe("record_lock_conflict");
64
- const conflictId = conflictAttempt.body?.conflict?.id ?? null;
65
- expect(conflictId).toBeTruthy();
66
- await waitForNotification(
67
- request,
68
- superadminToken,
69
- "record_locks.conflict.detected",
70
- (item) => item.sourceEntityId === conflictId
71
- );
72
- const mergedName = `QA TC-LOCK-004 Merged ${Date.now()}`;
73
- const mergedAttempt = await updateCompany(
74
- request,
75
- superadminToken,
76
- companyId,
77
- mergedName,
78
- {
79
- token: ownerLockToken,
80
- baseLogId,
81
- resolution: "merged",
82
- conflictId
83
- },
84
- superadminScopeHeaders
85
- );
86
- expect(mergedAttempt.status).toBe(200);
87
- ownerLockToken = null;
88
- const finalName = await getCompanyDisplayName(request, adminToken, companyId);
89
- expect(finalName).toBe(mergedName);
90
- const resolvedNotification = await waitForNotification(
91
- request,
92
- adminToken,
93
- "record_locks.conflict.resolved",
94
- (item) => item.sourceEntityId === conflictId
95
- );
96
- expect(resolvedNotification.bodyVariables?.resolution).toBe("merged");
97
- } finally {
98
- if (ownerLockToken && companyId) {
99
- await releaseRecordLock(
100
- request,
101
- superadminToken,
102
- "customers.company",
103
- companyId,
104
- ownerLockToken,
105
- "cancelled",
106
- void 0,
107
- superadminScopeHeaders
108
- ).catch(() => {
109
- });
110
- }
111
- await cleanupCompany(request, adminToken, companyId);
112
- if (previousSettings) {
113
- await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {
114
- });
115
- }
116
- }
117
- });
118
- });
119
- //# sourceMappingURL=TC-LOCK-004.spec.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/record_locks/__integration__/TC-LOCK-004.spec.ts"],
4
- "sourcesContent": ["import { expect, test } from '@playwright/test';\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api';\nimport { createCompanyFixture } from '@open-mercato/core/modules/core/__integration__/helpers/crmFixtures';\nimport {\n acquireRecordLock,\n cleanupCompany,\n buildScopeCookieFromToken,\n getCompanyDisplayName,\n getRecordLockSettings,\n releaseRecordLock,\n saveRecordLockSettings,\n updateCompany,\n waitForNotification,\n type RecordLockSettings,\n} from './helpers/recordLocks';\n\n/**\n * TC-LOCK-004: Merged conflict resolution and notification\n */\ntest.describe('TC-LOCK-004: Merged conflict resolution and notification', () => {\n test.describe.configure({ timeout: 90_000 });\n\n test('should resolve conflict with merged resolution and notify incoming actor', async ({ request }) => {\n const superadminToken = await getAuthToken(request, 'superadmin');\n const adminToken = await getAuthToken(request, 'admin');\n const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);\n const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : undefined;\n\n let previousSettings: RecordLockSettings | null = null;\n let companyId: string | null = null;\n let ownerLockToken: string | null = null;\n\n try {\n previousSettings = await getRecordLockSettings(request, superadminToken);\n await saveRecordLockSettings(request, superadminToken, {\n ...previousSettings,\n enabled: true,\n strategy: 'optimistic',\n enabledResources: ['customers.company'],\n notifyOnConflict: true,\n });\n\n companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-004 Company ${Date.now()}`);\n\n const acquire = await acquireRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n superadminScopeHeaders,\n );\n expect(acquire.status).toBe(200);\n ownerLockToken = (acquire.body?.lock as { token?: string | null } | undefined)?.token ?? null;\n const baseLogId =\n (acquire.body as { latestActionLogId?: string | null } | null)?.latestActionLogId ?? null;\n expect(ownerLockToken).toBeTruthy();\n expect(baseLogId).toBeTruthy();\n\n const incomingName = `QA TC-LOCK-004 Incoming ${Date.now()}`;\n const incomingUpdate = await updateCompany(request, adminToken, companyId, incomingName);\n expect(incomingUpdate.status).toBe(200);\n\n const conflictAttempt = await updateCompany(\n request,\n superadminToken,\n companyId,\n `QA TC-LOCK-004 Mine ${Date.now()}`,\n {\n token: ownerLockToken,\n baseLogId,\n resolution: 'normal',\n },\n superadminScopeHeaders,\n );\n expect(conflictAttempt.status).toBe(409);\n expect(conflictAttempt.body?.code).toBe('record_lock_conflict');\n\n const conflictId =\n (conflictAttempt.body?.conflict as { id?: string } | undefined)?.id ?? null;\n expect(conflictId).toBeTruthy();\n\n await waitForNotification(\n request,\n superadminToken,\n 'record_locks.conflict.detected',\n (item) => item.sourceEntityId === conflictId,\n );\n\n const mergedName = `QA TC-LOCK-004 Merged ${Date.now()}`;\n const mergedAttempt = await updateCompany(\n request,\n superadminToken,\n companyId,\n mergedName,\n {\n token: ownerLockToken,\n baseLogId,\n resolution: 'merged',\n conflictId,\n },\n superadminScopeHeaders,\n );\n expect(mergedAttempt.status).toBe(200);\n\n ownerLockToken = null;\n\n const finalName = await getCompanyDisplayName(request, adminToken, companyId);\n expect(finalName).toBe(mergedName);\n\n const resolvedNotification = await waitForNotification(\n request,\n adminToken,\n 'record_locks.conflict.resolved',\n (item) => item.sourceEntityId === conflictId,\n );\n expect(resolvedNotification.bodyVariables?.resolution).toBe('merged');\n } finally {\n if (ownerLockToken && companyId) {\n await releaseRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n ownerLockToken,\n 'cancelled',\n undefined,\n superadminScopeHeaders,\n ).catch(() => {});\n }\n await cleanupCompany(request, adminToken, companyId);\n if (previousSettings) {\n await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {});\n }\n }\n });\n});\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,90 +0,0 @@
1
- import { expect, test } from "@playwright/test";
2
- import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
3
- import { createCompanyFixture } from "@open-mercato/core/modules/core/__integration__/helpers/crmFixtures";
4
- import {
5
- acquireRecordLock,
6
- cleanupCompany,
7
- forceReleaseRecordLock,
8
- getRecordLockSettings,
9
- listNotificationsByType,
10
- saveRecordLockSettings,
11
- updateCompany,
12
- waitForNotification
13
- } from "./helpers/recordLocks.js";
14
- test.describe("TC-LOCK-005: Pessimistic force release and takeover", () => {
15
- test.describe.configure({ timeout: 9e4 });
16
- test("admin can force release lock and continue mutation flow", async ({ request }) => {
17
- const superadminToken = await getAuthToken(request, "superadmin");
18
- const adminToken = await getAuthToken(request, "admin");
19
- let previousSettings = null;
20
- let companyId = null;
21
- try {
22
- previousSettings = await getRecordLockSettings(request, superadminToken);
23
- await saveRecordLockSettings(request, superadminToken, {
24
- ...previousSettings,
25
- enabled: true,
26
- strategy: "pessimistic",
27
- enabledResources: ["customers.company"],
28
- allowForceUnlock: true
29
- });
30
- companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-005 Company ${Date.now()}`);
31
- const ownerAcquire = await acquireRecordLock(request, superadminToken, "customers.company", companyId);
32
- expect(ownerAcquire.status).toBe(200);
33
- expect(ownerAcquire.body?.ok).toBe(true);
34
- const ownerLockToken = ownerAcquire.body?.lock?.token ?? null;
35
- expect(ownerLockToken).toBeTruthy();
36
- const blockedUpdate = await updateCompany(
37
- request,
38
- adminToken,
39
- companyId,
40
- `QA TC-LOCK-005 Blocked ${Date.now()}`
41
- );
42
- expect(blockedUpdate.status).toBe(423);
43
- expect(blockedUpdate.body?.code).toBe("record_locked");
44
- const existingForceReleaseNotifications = await listNotificationsByType(
45
- request,
46
- superadminToken,
47
- "record_locks.lock.force_released"
48
- );
49
- const knownNotificationIds = new Set(existingForceReleaseNotifications.map((entry) => entry.id));
50
- const forceRelease = await forceReleaseRecordLock(
51
- request,
52
- adminToken,
53
- "customers.company",
54
- companyId,
55
- "qa_tc_lock_005_takeover"
56
- );
57
- expect(forceRelease.status).toBe(200);
58
- expect(forceRelease.body?.released).toBe(true);
59
- const nextLock = forceRelease.body?.lock ?? null;
60
- if (nextLock) {
61
- expect(nextLock.status).toBe("active");
62
- expect(nextLock.lockedByUserId).toBeTruthy();
63
- }
64
- const forceReleaseNotification = await waitForNotification(
65
- request,
66
- superadminToken,
67
- "record_locks.lock.force_released",
68
- (item) => !knownNotificationIds.has(item.id),
69
- 3e4,
70
- 500
71
- );
72
- expect(forceReleaseNotification.type).toBe("record_locks.lock.force_released");
73
- const updateAfterForceRelease = await updateCompany(
74
- request,
75
- adminToken,
76
- companyId,
77
- `QA TC-LOCK-005 Updated ${Date.now()}`
78
- );
79
- expect(updateAfterForceRelease.status).toBe(200);
80
- expect(updateAfterForceRelease.body?.ok).toBe(true);
81
- } finally {
82
- await cleanupCompany(request, adminToken, companyId);
83
- if (previousSettings) {
84
- await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {
85
- });
86
- }
87
- }
88
- });
89
- });
90
- //# sourceMappingURL=TC-LOCK-005.spec.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/record_locks/__integration__/TC-LOCK-005.spec.ts"],
4
- "sourcesContent": ["import { expect, test } from '@playwright/test';\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api';\nimport { createCompanyFixture } from '@open-mercato/core/modules/core/__integration__/helpers/crmFixtures';\nimport {\n acquireRecordLock,\n cleanupCompany,\n forceReleaseRecordLock,\n getRecordLockSettings,\n listNotificationsByType,\n saveRecordLockSettings,\n updateCompany,\n waitForNotification,\n type RecordLockSettings,\n} from './helpers/recordLocks';\n\n/**\n * TC-LOCK-005: Pessimistic force release and takeover\n */\ntest.describe('TC-LOCK-005: Pessimistic force release and takeover', () => {\n test.describe.configure({ timeout: 90_000 });\n\n test('admin can force release lock and continue mutation flow', async ({ request }) => {\n const superadminToken = await getAuthToken(request, 'superadmin');\n const adminToken = await getAuthToken(request, 'admin');\n\n let previousSettings: RecordLockSettings | null = null;\n let companyId: string | null = null;\n\n try {\n previousSettings = await getRecordLockSettings(request, superadminToken);\n await saveRecordLockSettings(request, superadminToken, {\n ...previousSettings,\n enabled: true,\n strategy: 'pessimistic',\n enabledResources: ['customers.company'],\n allowForceUnlock: true,\n });\n\n companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-005 Company ${Date.now()}`);\n\n const ownerAcquire = await acquireRecordLock(request, superadminToken, 'customers.company', companyId);\n expect(ownerAcquire.status).toBe(200);\n expect(ownerAcquire.body?.ok).toBe(true);\n const ownerLockToken =\n (ownerAcquire.body?.lock as { token?: string | null } | undefined)?.token ?? null;\n expect(ownerLockToken).toBeTruthy();\n\n const blockedUpdate = await updateCompany(\n request,\n adminToken,\n companyId,\n `QA TC-LOCK-005 Blocked ${Date.now()}`,\n );\n expect(blockedUpdate.status).toBe(423);\n expect(blockedUpdate.body?.code).toBe('record_locked');\n\n const existingForceReleaseNotifications = await listNotificationsByType(\n request,\n superadminToken,\n 'record_locks.lock.force_released',\n );\n const knownNotificationIds = new Set(existingForceReleaseNotifications.map((entry) => entry.id));\n\n const forceRelease = await forceReleaseRecordLock(\n request,\n adminToken,\n 'customers.company',\n companyId,\n 'qa_tc_lock_005_takeover',\n );\n expect(forceRelease.status).toBe(200);\n expect(forceRelease.body?.released).toBe(true);\n\n const nextLock = (forceRelease.body?.lock as { id?: string; status?: string; lockedByUserId?: string } | undefined) ?? null;\n if (nextLock) {\n expect(nextLock.status).toBe('active');\n expect(nextLock.lockedByUserId).toBeTruthy();\n }\n\n const forceReleaseNotification = await waitForNotification(\n request,\n superadminToken,\n 'record_locks.lock.force_released',\n (item) =>\n !knownNotificationIds.has(item.id),\n 30_000,\n 500,\n );\n expect(forceReleaseNotification.type).toBe('record_locks.lock.force_released');\n\n const updateAfterForceRelease = await updateCompany(\n request,\n adminToken,\n companyId,\n `QA TC-LOCK-005 Updated ${Date.now()}`,\n );\n expect(updateAfterForceRelease.status).toBe(200);\n expect(updateAfterForceRelease.body?.ok).toBe(true);\n } finally {\n await cleanupCompany(request, adminToken, companyId);\n if (previousSettings) {\n await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {});\n }\n }\n });\n});\n"],
5
- "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAKP,KAAK,SAAS,uDAAuD,MAAM;AACzE,OAAK,SAAS,UAAU,EAAE,SAAS,IAAO,CAAC;AAE3C,OAAK,2DAA2D,OAAO,EAAE,QAAQ,MAAM;AACrF,UAAM,kBAAkB,MAAM,aAAa,SAAS,YAAY;AAChE,UAAM,aAAa,MAAM,aAAa,SAAS,OAAO;AAEtD,QAAI,mBAA8C;AAClD,QAAI,YAA2B;AAE/B,QAAI;AACF,yBAAmB,MAAM,sBAAsB,SAAS,eAAe;AACvE,YAAM,uBAAuB,SAAS,iBAAiB;AAAA,QACrD,GAAG;AAAA,QACH,SAAS;AAAA,QACT,UAAU;AAAA,QACV,kBAAkB,CAAC,mBAAmB;AAAA,QACtC,kBAAkB;AAAA,MACpB,CAAC;AAED,kBAAY,MAAM,qBAAqB,SAAS,YAAY,0BAA0B,KAAK,IAAI,CAAC,EAAE;AAElG,YAAM,eAAe,MAAM,kBAAkB,SAAS,iBAAiB,qBAAqB,SAAS;AACrG,aAAO,aAAa,MAAM,EAAE,KAAK,GAAG;AACpC,aAAO,aAAa,MAAM,EAAE,EAAE,KAAK,IAAI;AACvC,YAAM,iBACH,aAAa,MAAM,MAAgD,SAAS;AAC/E,aAAO,cAAc,EAAE,WAAW;AAElC,YAAM,gBAAgB,MAAM;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,QACA,0BAA0B,KAAK,IAAI,CAAC;AAAA,MACtC;AACA,aAAO,cAAc,MAAM,EAAE,KAAK,GAAG;AACrC,aAAO,cAAc,MAAM,IAAI,EAAE,KAAK,eAAe;AAErD,YAAM,oCAAoC,MAAM;AAAA,QAC9C;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,uBAAuB,IAAI,IAAI,kCAAkC,IAAI,CAAC,UAAU,MAAM,EAAE,CAAC;AAE/F,YAAM,eAAe,MAAM;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,aAAO,aAAa,MAAM,EAAE,KAAK,GAAG;AACpC,aAAO,aAAa,MAAM,QAAQ,EAAE,KAAK,IAAI;AAE7C,YAAM,WAAY,aAAa,MAAM,QAAkF;AACvH,UAAI,UAAU;AACZ,eAAO,SAAS,MAAM,EAAE,KAAK,QAAQ;AACrC,eAAO,SAAS,cAAc,EAAE,WAAW;AAAA,MAC7C;AAEA,YAAM,2BAA2B,MAAM;AAAA,QACrC;AAAA,QACA;AAAA,QACA;AAAA,QACA,CAAC,SACC,CAAC,qBAAqB,IAAI,KAAK,EAAE;AAAA,QACnC;AAAA,QACA;AAAA,MACF;AACA,aAAO,yBAAyB,IAAI,EAAE,KAAK,kCAAkC;AAE7E,YAAM,0BAA0B,MAAM;AAAA,QACpC;AAAA,QACA;AAAA,QACA;AAAA,QACA,0BAA0B,KAAK,IAAI,CAAC;AAAA,MACtC;AACA,aAAO,wBAAwB,MAAM,EAAE,KAAK,GAAG;AAC/C,aAAO,wBAAwB,MAAM,EAAE,EAAE,KAAK,IAAI;AAAA,IACpD,UAAE;AACA,YAAM,eAAe,SAAS,YAAY,SAAS;AACnD,UAAI,kBAAkB;AACpB,cAAM,uBAAuB,SAAS,iBAAiB,gBAAgB,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACzF;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;",
6
- "names": []
7
- }