@open-mercato/enterprise 0.4.5-develop-2e9903a57a → 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.
- package/package.json +4 -4
- package/dist/modules/record_locks/__integration__/TC-LOCK-001.spec.js +0 -73
- package/dist/modules/record_locks/__integration__/TC-LOCK-001.spec.js.map +0 -7
- package/dist/modules/record_locks/__integration__/TC-LOCK-002.spec.js +0 -114
- package/dist/modules/record_locks/__integration__/TC-LOCK-002.spec.js.map +0 -7
- package/dist/modules/record_locks/__integration__/TC-LOCK-003.spec.js +0 -119
- package/dist/modules/record_locks/__integration__/TC-LOCK-003.spec.js.map +0 -7
- package/dist/modules/record_locks/__integration__/TC-LOCK-004.spec.js +0 -119
- package/dist/modules/record_locks/__integration__/TC-LOCK-004.spec.js.map +0 -7
- package/dist/modules/record_locks/__integration__/TC-LOCK-005.spec.js +0 -90
- package/dist/modules/record_locks/__integration__/TC-LOCK-005.spec.js.map +0 -7
- package/dist/modules/record_locks/__integration__/TC-LOCK-006.spec.js +0 -90
- package/dist/modules/record_locks/__integration__/TC-LOCK-006.spec.js.map +0 -7
- package/dist/modules/record_locks/__integration__/TC-LOCK-007.spec.js +0 -211
- package/dist/modules/record_locks/__integration__/TC-LOCK-007.spec.js.map +0 -7
- package/dist/modules/record_locks/__integration__/helpers/recordLocks.js +0 -219
- package/dist/modules/record_locks/__integration__/helpers/recordLocks.js.map +0 -7
- package/src/modules/record_locks/__integration__/TC-LOCK-001.spec.ts +0 -84
- package/src/modules/record_locks/__integration__/TC-LOCK-002.spec.ts +0 -129
- package/src/modules/record_locks/__integration__/TC-LOCK-003.spec.ts +0 -136
- package/src/modules/record_locks/__integration__/TC-LOCK-004.spec.ts +0 -136
- package/src/modules/record_locks/__integration__/TC-LOCK-005.spec.ts +0 -106
- package/src/modules/record_locks/__integration__/TC-LOCK-006.spec.ts +0 -113
- package/src/modules/record_locks/__integration__/TC-LOCK-007.spec.ts +0 -251
- package/src/modules/record_locks/__integration__/helpers/recordLocks.ts +0 -366
- package/src/modules/record_locks/__tests__/config.test.ts +0 -21
- package/src/modules/record_locks/__tests__/crudMutationGuardService.test.ts +0 -106
- package/src/modules/record_locks/__tests__/recordLockService.test.ts +0 -1226
- package/src/modules/record_locks/__tests__/recordLockWidgetHeaders.test.ts +0 -127
- package/src/modules/record_locks/api/__tests__/acquire.route.test.ts +0 -175
- package/src/modules/record_locks/api/__tests__/release.route.test.ts +0 -135
- package/src/modules/record_locks/api/__tests__/settings.route.test.ts +0 -85
|
@@ -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
|
-
buildScopeCookieFromToken,
|
|
7
|
-
cleanupCompany,
|
|
8
|
-
getRecordLockSettings,
|
|
9
|
-
releaseRecordLock,
|
|
10
|
-
saveRecordLockSettings
|
|
11
|
-
} from "./helpers/recordLocks.js";
|
|
12
|
-
test.describe("TC-LOCK-006: Lock payload exposes participant ring with redacted email only", () => {
|
|
13
|
-
test.describe.configure({ timeout: 9e4 });
|
|
14
|
-
test("should return participant queue data with masked email when another user views the same record", async ({ request }) => {
|
|
15
|
-
const superadminToken = await getAuthToken(request, "superadmin");
|
|
16
|
-
const adminToken = await getAuthToken(request, "admin");
|
|
17
|
-
const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);
|
|
18
|
-
const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : void 0;
|
|
19
|
-
const ownerIp = "198.51.100.24";
|
|
20
|
-
let previousSettings = null;
|
|
21
|
-
let companyId = null;
|
|
22
|
-
let ownerLockToken = null;
|
|
23
|
-
try {
|
|
24
|
-
previousSettings = await getRecordLockSettings(request, superadminToken);
|
|
25
|
-
await saveRecordLockSettings(request, superadminToken, {
|
|
26
|
-
...previousSettings,
|
|
27
|
-
enabled: true,
|
|
28
|
-
strategy: "optimistic",
|
|
29
|
-
enabledResources: ["customers.company"],
|
|
30
|
-
allowForceUnlock: true
|
|
31
|
-
});
|
|
32
|
-
companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-006 Company ${Date.now()}`);
|
|
33
|
-
const ownerAcquire = await acquireRecordLock(
|
|
34
|
-
request,
|
|
35
|
-
superadminToken,
|
|
36
|
-
"customers.company",
|
|
37
|
-
companyId,
|
|
38
|
-
{
|
|
39
|
-
...superadminScopeHeaders ?? {},
|
|
40
|
-
"x-forwarded-for": `${ownerIp}, 10.0.0.1`
|
|
41
|
-
}
|
|
42
|
-
);
|
|
43
|
-
expect(ownerAcquire.status).toBe(200);
|
|
44
|
-
ownerLockToken = ownerAcquire.body?.lock?.token ?? null;
|
|
45
|
-
expect(ownerLockToken).toBeTruthy();
|
|
46
|
-
const viewerAcquire = await acquireRecordLock(
|
|
47
|
-
request,
|
|
48
|
-
adminToken,
|
|
49
|
-
"customers.company",
|
|
50
|
-
companyId
|
|
51
|
-
);
|
|
52
|
-
expect(viewerAcquire.status).toBe(200);
|
|
53
|
-
expect(viewerAcquire.body?.acquired).toBe(true);
|
|
54
|
-
const lock = viewerAcquire.body?.lock ?? null;
|
|
55
|
-
expect(lock).toBeTruthy();
|
|
56
|
-
expect(lock?.activeParticipantCount).toBeGreaterThanOrEqual(2);
|
|
57
|
-
expect(lock?.lockedByIp ?? null).toBeNull();
|
|
58
|
-
expect(lock?.lockedByName ?? null).toBeNull();
|
|
59
|
-
expect(lock?.lockedByEmail ?? null).toMatch(/^[a-z0-9]{1,2}\*\*@[a-z0-9]{1,4}\*\*\.[a-z0-9.]+$/);
|
|
60
|
-
const viewerId = viewerAcquire.body?.currentUserId ?? null;
|
|
61
|
-
expect(viewerId).toBeTruthy();
|
|
62
|
-
const otherParticipants = (lock?.participants ?? []).filter((entry) => entry.userId !== viewerId);
|
|
63
|
-
const ownerParticipant = otherParticipants.find((entry) => entry.userId);
|
|
64
|
-
expect(ownerParticipant?.lockedByIp).toBeUndefined();
|
|
65
|
-
expect(ownerParticipant?.lockedByName).toBeUndefined();
|
|
66
|
-
expect(ownerParticipant?.lockedByEmail ?? null).toMatch(/^[a-z0-9]{1,2}\*\*@[a-z0-9]{1,4}\*\*\.[a-z0-9.]+$/);
|
|
67
|
-
expect(otherParticipants.length).toBeGreaterThanOrEqual(1);
|
|
68
|
-
} finally {
|
|
69
|
-
if (ownerLockToken && companyId) {
|
|
70
|
-
await releaseRecordLock(
|
|
71
|
-
request,
|
|
72
|
-
superadminToken,
|
|
73
|
-
"customers.company",
|
|
74
|
-
companyId,
|
|
75
|
-
ownerLockToken,
|
|
76
|
-
"cancelled",
|
|
77
|
-
void 0,
|
|
78
|
-
superadminScopeHeaders
|
|
79
|
-
).catch(() => {
|
|
80
|
-
});
|
|
81
|
-
}
|
|
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-006.spec.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/modules/record_locks/__integration__/TC-LOCK-006.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 buildScopeCookieFromToken,\n cleanupCompany,\n getRecordLockSettings,\n releaseRecordLock,\n saveRecordLockSettings,\n type RecordLockSettings,\n} from './helpers/recordLocks';\n\n/**\n * TC-LOCK-006: Lock payload exposes participant ring with redacted email only\n */\ntest.describe('TC-LOCK-006: Lock payload exposes participant ring with redacted email only', () => {\n test.describe.configure({ timeout: 90_000 });\n\n test('should return participant queue data with masked email when another user views the same record', 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 const ownerIp = '198.51.100.24';\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 allowForceUnlock: true,\n });\n\n companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-006 Company ${Date.now()}`);\n\n const ownerAcquire = await acquireRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n {\n ...(superadminScopeHeaders ?? {}),\n 'x-forwarded-for': `${ownerIp}, 10.0.0.1`,\n },\n );\n expect(ownerAcquire.status).toBe(200);\n ownerLockToken = (ownerAcquire.body?.lock as { token?: string | null } | undefined)?.token ?? null;\n expect(ownerLockToken).toBeTruthy();\n\n const viewerAcquire = await acquireRecordLock(\n request,\n adminToken,\n 'customers.company',\n companyId,\n );\n expect(viewerAcquire.status).toBe(200);\n expect(viewerAcquire.body?.acquired).toBe(true);\n\n const lock = (viewerAcquire.body?.lock as {\n lockedByUserId?: string;\n lockedByName?: string | null;\n lockedByEmail?: string | null;\n lockedByIp?: string | null;\n activeParticipantCount?: number;\n participants?: Array<{\n userId?: string;\n lockedByName?: string | null;\n lockedByEmail?: string | null;\n lockedByIp?: string | null;\n }>;\n } | null | undefined) ?? null;\n\n expect(lock).toBeTruthy();\n expect(lock?.activeParticipantCount).toBeGreaterThanOrEqual(2);\n expect(lock?.lockedByIp ?? null).toBeNull();\n expect(lock?.lockedByName ?? null).toBeNull();\n expect(lock?.lockedByEmail ?? null).toMatch(/^[a-z0-9]{1,2}\\*\\*@[a-z0-9]{1,4}\\*\\*\\.[a-z0-9.]+$/);\n const viewerId =\n (viewerAcquire.body as { currentUserId?: string | null } | null)?.currentUserId ?? null;\n expect(viewerId).toBeTruthy();\n const otherParticipants = (lock?.participants ?? []).filter((entry) => entry.userId !== viewerId);\n const ownerParticipant = otherParticipants.find((entry) => entry.userId);\n expect(ownerParticipant?.lockedByIp).toBeUndefined();\n expect(ownerParticipant?.lockedByName).toBeUndefined();\n expect(ownerParticipant?.lockedByEmail ?? null).toMatch(/^[a-z0-9]{1,2}\\*\\*@[a-z0-9]{1,4}\\*\\*\\.[a-z0-9.]+$/);\n expect(otherParticipants.length).toBeGreaterThanOrEqual(1);\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": "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,+EAA+E,MAAM;AACjG,OAAK,SAAS,UAAU,EAAE,SAAS,IAAO,CAAC;AAE3C,OAAK,kGAAkG,OAAO,EAAE,QAAQ,MAAM;AAC5H,UAAM,kBAAkB,MAAM,aAAa,SAAS,YAAY;AAChE,UAAM,aAAa,MAAM,aAAa,SAAS,OAAO;AACtD,UAAM,wBAAwB,0BAA0B,eAAe;AACvE,UAAM,yBAAyB,wBAAwB,EAAE,QAAQ,sBAAsB,IAAI;AAC3F,UAAM,UAAU;AAEhB,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,QACtC,kBAAkB;AAAA,MACpB,CAAC;AAED,kBAAY,MAAM,qBAAqB,SAAS,YAAY,0BAA0B,KAAK,IAAI,CAAC,EAAE;AAElG,YAAM,eAAe,MAAM;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,UACE,GAAI,0BAA0B,CAAC;AAAA,UAC/B,mBAAmB,GAAG,OAAO;AAAA,QAC/B;AAAA,MACF;AACA,aAAO,aAAa,MAAM,EAAE,KAAK,GAAG;AACpC,uBAAkB,aAAa,MAAM,MAAgD,SAAS;AAC9F,aAAO,cAAc,EAAE,WAAW;AAElC,YAAM,gBAAgB,MAAM;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,aAAO,cAAc,MAAM,EAAE,KAAK,GAAG;AACrC,aAAO,cAAc,MAAM,QAAQ,EAAE,KAAK,IAAI;AAE9C,YAAM,OAAQ,cAAc,MAAM,QAYT;AAEzB,aAAO,IAAI,EAAE,WAAW;AACxB,aAAO,MAAM,sBAAsB,EAAE,uBAAuB,CAAC;AAC7D,aAAO,MAAM,cAAc,IAAI,EAAE,SAAS;AAC1C,aAAO,MAAM,gBAAgB,IAAI,EAAE,SAAS;AAC5C,aAAO,MAAM,iBAAiB,IAAI,EAAE,QAAQ,mDAAmD;AAC/F,YAAM,WACH,cAAc,MAAmD,iBAAiB;AACrF,aAAO,QAAQ,EAAE,WAAW;AAC5B,YAAM,qBAAqB,MAAM,gBAAgB,CAAC,GAAG,OAAO,CAAC,UAAU,MAAM,WAAW,QAAQ;AAChG,YAAM,mBAAmB,kBAAkB,KAAK,CAAC,UAAU,MAAM,MAAM;AACvE,aAAO,kBAAkB,UAAU,EAAE,cAAc;AACnD,aAAO,kBAAkB,YAAY,EAAE,cAAc;AACrD,aAAO,kBAAkB,iBAAiB,IAAI,EAAE,QAAQ,mDAAmD;AAC3G,aAAO,kBAAkB,MAAM,EAAE,uBAAuB,CAAC;AAAA,IAC3D,UAAE;AACA,UAAI,kBAAkB,WAAW;AAC/B,cAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAClB;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,211 +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
|
-
buildScopeCookieFromToken,
|
|
7
|
-
cleanupCompany,
|
|
8
|
-
getCompanyDisplayName,
|
|
9
|
-
getRecordLockSettings,
|
|
10
|
-
releaseRecordLock,
|
|
11
|
-
saveRecordLockSettings,
|
|
12
|
-
updateCompany,
|
|
13
|
-
waitForNotification
|
|
14
|
-
} from "./helpers/recordLocks.js";
|
|
15
|
-
async function createConflictScenario(request, superadminToken, adminToken, companyId, superadminScopeHeaders) {
|
|
16
|
-
const acquire = await acquireRecordLock(
|
|
17
|
-
request,
|
|
18
|
-
superadminToken,
|
|
19
|
-
"customers.company",
|
|
20
|
-
companyId,
|
|
21
|
-
superadminScopeHeaders
|
|
22
|
-
);
|
|
23
|
-
expect(acquire.status).toBe(200);
|
|
24
|
-
const ownerLockToken = acquire.body?.lock?.token ?? null;
|
|
25
|
-
const baseLogId = acquire.body?.latestActionLogId ?? null;
|
|
26
|
-
expect(ownerLockToken).toBeTruthy();
|
|
27
|
-
expect(baseLogId).toBeTruthy();
|
|
28
|
-
const incomingName = `QA TC-LOCK-007 Incoming ${Date.now()}`;
|
|
29
|
-
const incomingUpdate = await updateCompany(request, adminToken, companyId, incomingName);
|
|
30
|
-
expect(incomingUpdate.status).toBe(200);
|
|
31
|
-
const conflictAttempt = await updateCompany(
|
|
32
|
-
request,
|
|
33
|
-
superadminToken,
|
|
34
|
-
companyId,
|
|
35
|
-
`QA TC-LOCK-007 Mine ${Date.now()}`,
|
|
36
|
-
{
|
|
37
|
-
token: ownerLockToken,
|
|
38
|
-
baseLogId,
|
|
39
|
-
resolution: "normal"
|
|
40
|
-
},
|
|
41
|
-
superadminScopeHeaders
|
|
42
|
-
);
|
|
43
|
-
expect(conflictAttempt.status).toBe(409);
|
|
44
|
-
expect(conflictAttempt.body?.code).toBe("record_lock_conflict");
|
|
45
|
-
const conflictId = conflictAttempt.body?.conflict?.id ?? null;
|
|
46
|
-
expect(conflictId).toBeTruthy();
|
|
47
|
-
const notification = await waitForNotification(
|
|
48
|
-
request,
|
|
49
|
-
superadminToken,
|
|
50
|
-
"record_locks.conflict.detected",
|
|
51
|
-
(item) => item.sourceEntityId === conflictId
|
|
52
|
-
);
|
|
53
|
-
return {
|
|
54
|
-
conflictId,
|
|
55
|
-
notification,
|
|
56
|
-
ownerLockToken,
|
|
57
|
-
baseLogId,
|
|
58
|
-
incomingName
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
test.describe("TC-LOCK-007: Conflict notification changed fields and apply/reject actions", () => {
|
|
62
|
-
test.describe.configure({ timeout: 9e4 });
|
|
63
|
-
test("should include changed incoming fields and execute accept_incoming action from notification", async ({ request }) => {
|
|
64
|
-
const superadminToken = await getAuthToken(request, "superadmin");
|
|
65
|
-
const adminToken = await getAuthToken(request, "admin");
|
|
66
|
-
const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);
|
|
67
|
-
const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : void 0;
|
|
68
|
-
let previousSettings = null;
|
|
69
|
-
let companyId = null;
|
|
70
|
-
let ownerLockToken = null;
|
|
71
|
-
try {
|
|
72
|
-
previousSettings = await getRecordLockSettings(request, superadminToken);
|
|
73
|
-
await saveRecordLockSettings(request, superadminToken, {
|
|
74
|
-
...previousSettings,
|
|
75
|
-
enabled: true,
|
|
76
|
-
strategy: "optimistic",
|
|
77
|
-
enabledResources: ["customers.company"],
|
|
78
|
-
notifyOnConflict: true
|
|
79
|
-
});
|
|
80
|
-
companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-007 Company A ${Date.now()}`);
|
|
81
|
-
const conflict = await createConflictScenario(
|
|
82
|
-
request,
|
|
83
|
-
superadminToken,
|
|
84
|
-
adminToken,
|
|
85
|
-
companyId,
|
|
86
|
-
superadminScopeHeaders
|
|
87
|
-
);
|
|
88
|
-
ownerLockToken = conflict.ownerLockToken;
|
|
89
|
-
expect(conflict.notification.bodyVariables?.changedFields?.toLowerCase()).toContain("display");
|
|
90
|
-
const actionIds = (conflict.notification.actions ?? []).map((item) => item.id);
|
|
91
|
-
expect(actionIds).toEqual([]);
|
|
92
|
-
const releaseResult = await releaseRecordLock(
|
|
93
|
-
request,
|
|
94
|
-
superadminToken,
|
|
95
|
-
"customers.company",
|
|
96
|
-
companyId,
|
|
97
|
-
conflict.ownerLockToken,
|
|
98
|
-
"conflict_resolved",
|
|
99
|
-
{
|
|
100
|
-
conflictId: conflict.conflictId,
|
|
101
|
-
resolution: "accept_incoming"
|
|
102
|
-
},
|
|
103
|
-
superadminScopeHeaders
|
|
104
|
-
);
|
|
105
|
-
expect(releaseResult.status).toBe(200);
|
|
106
|
-
expect(releaseResult.body?.ok).toBe(true);
|
|
107
|
-
expect(releaseResult.body?.conflictResolved).toBe(true);
|
|
108
|
-
const finalName = await getCompanyDisplayName(request, adminToken, companyId);
|
|
109
|
-
expect(finalName).toBe(conflict.incomingName);
|
|
110
|
-
await waitForNotification(
|
|
111
|
-
request,
|
|
112
|
-
adminToken,
|
|
113
|
-
"record_locks.conflict.resolved",
|
|
114
|
-
(item) => item.sourceEntityId === conflict.conflictId && item.bodyVariables?.resolution === "accept_incoming"
|
|
115
|
-
);
|
|
116
|
-
ownerLockToken = null;
|
|
117
|
-
} finally {
|
|
118
|
-
if (ownerLockToken && companyId) {
|
|
119
|
-
await releaseRecordLock(
|
|
120
|
-
request,
|
|
121
|
-
superadminToken,
|
|
122
|
-
"customers.company",
|
|
123
|
-
companyId,
|
|
124
|
-
ownerLockToken,
|
|
125
|
-
"cancelled",
|
|
126
|
-
void 0,
|
|
127
|
-
superadminScopeHeaders
|
|
128
|
-
).catch(() => {
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
await cleanupCompany(request, adminToken, companyId);
|
|
132
|
-
if (previousSettings) {
|
|
133
|
-
await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
test("should execute accept_mine action from notification and emit resolved notification", async ({ request }) => {
|
|
139
|
-
const superadminToken = await getAuthToken(request, "superadmin");
|
|
140
|
-
const adminToken = await getAuthToken(request, "admin");
|
|
141
|
-
const superadminScopeCookie = buildScopeCookieFromToken(superadminToken);
|
|
142
|
-
const superadminScopeHeaders = superadminScopeCookie ? { cookie: superadminScopeCookie } : void 0;
|
|
143
|
-
let previousSettings = null;
|
|
144
|
-
let companyId = null;
|
|
145
|
-
let ownerLockToken = null;
|
|
146
|
-
try {
|
|
147
|
-
previousSettings = await getRecordLockSettings(request, superadminToken);
|
|
148
|
-
await saveRecordLockSettings(request, superadminToken, {
|
|
149
|
-
...previousSettings,
|
|
150
|
-
enabled: true,
|
|
151
|
-
strategy: "optimistic",
|
|
152
|
-
enabledResources: ["customers.company"],
|
|
153
|
-
notifyOnConflict: true
|
|
154
|
-
});
|
|
155
|
-
companyId = await createCompanyFixture(request, adminToken, `QA TC-LOCK-007 Company B ${Date.now()}`);
|
|
156
|
-
const conflict = await createConflictScenario(
|
|
157
|
-
request,
|
|
158
|
-
superadminToken,
|
|
159
|
-
adminToken,
|
|
160
|
-
companyId,
|
|
161
|
-
superadminScopeHeaders
|
|
162
|
-
);
|
|
163
|
-
ownerLockToken = conflict.ownerLockToken;
|
|
164
|
-
const keepMineName = `QA TC-LOCK-007 Keep Mine ${Date.now()}`;
|
|
165
|
-
const updateResult = await updateCompany(
|
|
166
|
-
request,
|
|
167
|
-
superadminToken,
|
|
168
|
-
companyId,
|
|
169
|
-
keepMineName,
|
|
170
|
-
{
|
|
171
|
-
token: conflict.ownerLockToken,
|
|
172
|
-
baseLogId: conflict.baseLogId,
|
|
173
|
-
resolution: "accept_mine",
|
|
174
|
-
conflictId: conflict.conflictId
|
|
175
|
-
},
|
|
176
|
-
superadminScopeHeaders
|
|
177
|
-
);
|
|
178
|
-
expect(updateResult.status).toBe(200);
|
|
179
|
-
expect(updateResult.body?.ok).toBe(true);
|
|
180
|
-
const finalName = await getCompanyDisplayName(request, adminToken, companyId);
|
|
181
|
-
expect(finalName).toBe(keepMineName);
|
|
182
|
-
await waitForNotification(
|
|
183
|
-
request,
|
|
184
|
-
adminToken,
|
|
185
|
-
"record_locks.conflict.resolved",
|
|
186
|
-
(item) => item.sourceEntityId === conflict.conflictId && item.bodyVariables?.resolution === "accept_mine"
|
|
187
|
-
);
|
|
188
|
-
ownerLockToken = null;
|
|
189
|
-
} finally {
|
|
190
|
-
if (ownerLockToken && companyId) {
|
|
191
|
-
await releaseRecordLock(
|
|
192
|
-
request,
|
|
193
|
-
superadminToken,
|
|
194
|
-
"customers.company",
|
|
195
|
-
companyId,
|
|
196
|
-
ownerLockToken,
|
|
197
|
-
"cancelled",
|
|
198
|
-
void 0,
|
|
199
|
-
superadminScopeHeaders
|
|
200
|
-
).catch(() => {
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
await cleanupCompany(request, adminToken, companyId);
|
|
204
|
-
if (previousSettings) {
|
|
205
|
-
await saveRecordLockSettings(request, superadminToken, previousSettings).catch(() => {
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
//# sourceMappingURL=TC-LOCK-007.spec.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/modules/record_locks/__integration__/TC-LOCK-007.spec.ts"],
|
|
4
|
-
"sourcesContent": ["import { expect, test, type APIRequestContext } 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 buildScopeCookieFromToken,\n cleanupCompany,\n getCompanyDisplayName,\n getRecordLockSettings,\n releaseRecordLock,\n saveRecordLockSettings,\n updateCompany,\n waitForNotification,\n type RecordLockSettings,\n type NotificationItem,\n} from './helpers/recordLocks';\n\ntype ConflictContext = {\n conflictId: string;\n notification: NotificationItem;\n ownerLockToken: string;\n baseLogId: string;\n incomingName: string;\n};\n\nasync function createConflictScenario(\n request: APIRequestContext,\n superadminToken: string,\n adminToken: string,\n companyId: string,\n superadminScopeHeaders?: Record<string, string>,\n): Promise<ConflictContext> {\n const acquire = await acquireRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n superadminScopeHeaders,\n );\n expect(acquire.status).toBe(200);\n\n const 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-007 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-007 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 = (conflictAttempt.body?.conflict as { id?: string } | undefined)?.id ?? null;\n expect(conflictId).toBeTruthy();\n\n const notification = await waitForNotification(\n request,\n superadminToken,\n 'record_locks.conflict.detected',\n (item) => item.sourceEntityId === conflictId,\n );\n\n return {\n conflictId: conflictId as string,\n notification,\n ownerLockToken: ownerLockToken as string,\n baseLogId: baseLogId as string,\n incomingName,\n };\n}\n\n/**\n * TC-LOCK-007: Conflict notification changed fields and apply/reject actions\n */\ntest.describe('TC-LOCK-007: Conflict notification changed fields and apply/reject actions', () => {\n test.describe.configure({ timeout: 90_000 });\n\n test('should include changed incoming fields and execute accept_incoming action from notification', 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-007 Company A ${Date.now()}`);\n\n const conflict = await createConflictScenario(\n request,\n superadminToken,\n adminToken,\n companyId,\n superadminScopeHeaders,\n );\n ownerLockToken = conflict.ownerLockToken;\n\n expect(conflict.notification.bodyVariables?.changedFields?.toLowerCase()).toContain('display');\n const actionIds = (conflict.notification.actions ?? []).map((item) => item.id);\n expect(actionIds).toEqual([]);\n\n const releaseResult = await releaseRecordLock(\n request,\n superadminToken,\n 'customers.company',\n companyId,\n conflict.ownerLockToken,\n 'conflict_resolved',\n {\n conflictId: conflict.conflictId,\n resolution: 'accept_incoming',\n },\n superadminScopeHeaders,\n );\n expect(releaseResult.status).toBe(200);\n expect(releaseResult.body?.ok).toBe(true);\n expect((releaseResult.body as { conflictResolved?: boolean } | null)?.conflictResolved).toBe(true);\n\n const finalName = await getCompanyDisplayName(request, adminToken, companyId);\n expect(finalName).toBe(conflict.incomingName);\n\n await waitForNotification(\n request,\n adminToken,\n 'record_locks.conflict.resolved',\n (item) => item.sourceEntityId === conflict.conflictId && item.bodyVariables?.resolution === 'accept_incoming',\n );\n ownerLockToken = null;\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 test('should execute accept_mine action from notification and emit resolved notification', 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-007 Company B ${Date.now()}`);\n\n const conflict = await createConflictScenario(\n request,\n superadminToken,\n adminToken,\n companyId,\n superadminScopeHeaders,\n );\n ownerLockToken = conflict.ownerLockToken;\n const keepMineName = `QA TC-LOCK-007 Keep Mine ${Date.now()}`;\n\n const updateResult = await updateCompany(\n request,\n superadminToken,\n companyId,\n keepMineName,\n {\n token: conflict.ownerLockToken,\n baseLogId: conflict.baseLogId,\n resolution: 'accept_mine',\n conflictId: conflict.conflictId,\n },\n superadminScopeHeaders,\n );\n expect(updateResult.status).toBe(200);\n expect(updateResult.body?.ok).toBe(true);\n\n const finalName = await getCompanyDisplayName(request, adminToken, companyId);\n expect(finalName).toBe(keepMineName);\n\n await waitForNotification(\n request,\n adminToken,\n 'record_locks.conflict.resolved',\n (item) => item.sourceEntityId === conflict.conflictId && item.bodyVariables?.resolution === 'accept_mine',\n );\n ownerLockToken = null;\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,219 +0,0 @@
|
|
|
1
|
-
import { expect } from "@playwright/test";
|
|
2
|
-
import { apiRequest } from "@open-mercato/core/modules/core/__integration__/helpers/api";
|
|
3
|
-
import { deleteEntityIfExists } from "@open-mercato/core/modules/core/__integration__/helpers/crmFixtures";
|
|
4
|
-
const BASE_URL = process.env.BASE_URL || "http://localhost:3000";
|
|
5
|
-
function sleep(ms) {
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
setTimeout(resolve, ms);
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
async function readJsonSafe(response) {
|
|
11
|
-
const raw = await response.text();
|
|
12
|
-
if (!raw) return null;
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(raw);
|
|
15
|
-
} catch {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
async function requestJson(request, method, path, token, data, extraHeaders) {
|
|
20
|
-
const headers = {
|
|
21
|
-
Authorization: `Bearer ${token}`,
|
|
22
|
-
"Content-Type": "application/json",
|
|
23
|
-
...extraHeaders ?? {}
|
|
24
|
-
};
|
|
25
|
-
const response = await request.fetch(`${BASE_URL}${path}`, {
|
|
26
|
-
method,
|
|
27
|
-
headers,
|
|
28
|
-
data: data === void 0 ? void 0 : data
|
|
29
|
-
});
|
|
30
|
-
const body = await readJsonSafe(response);
|
|
31
|
-
return {
|
|
32
|
-
response,
|
|
33
|
-
status: response.status(),
|
|
34
|
-
body
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
async function getRecordLockSettings(request, token) {
|
|
38
|
-
const result = await requestJson(
|
|
39
|
-
request,
|
|
40
|
-
"GET",
|
|
41
|
-
"/api/record_locks/settings",
|
|
42
|
-
token
|
|
43
|
-
);
|
|
44
|
-
expect(result.status).toBe(200);
|
|
45
|
-
expect(result.body?.settings).toBeTruthy();
|
|
46
|
-
return result.body?.settings;
|
|
47
|
-
}
|
|
48
|
-
async function saveRecordLockSettings(request, token, settings) {
|
|
49
|
-
const result = await requestJson(
|
|
50
|
-
request,
|
|
51
|
-
"POST",
|
|
52
|
-
"/api/record_locks/settings",
|
|
53
|
-
token,
|
|
54
|
-
settings
|
|
55
|
-
);
|
|
56
|
-
expect(result.status).toBe(200);
|
|
57
|
-
expect(result.body?.settings).toBeTruthy();
|
|
58
|
-
return result.body?.settings;
|
|
59
|
-
}
|
|
60
|
-
async function acquireRecordLock(request, token, resourceKind, resourceId, extraHeaders) {
|
|
61
|
-
return requestJson(
|
|
62
|
-
request,
|
|
63
|
-
"POST",
|
|
64
|
-
"/api/record_locks/acquire",
|
|
65
|
-
token,
|
|
66
|
-
{ resourceKind, resourceId },
|
|
67
|
-
extraHeaders
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
async function releaseRecordLock(request, token, resourceKind, resourceId, lockToken, reason = "cancelled", options, extraHeaders) {
|
|
71
|
-
return requestJson(
|
|
72
|
-
request,
|
|
73
|
-
"POST",
|
|
74
|
-
"/api/record_locks/release",
|
|
75
|
-
token,
|
|
76
|
-
{
|
|
77
|
-
resourceKind,
|
|
78
|
-
resourceId,
|
|
79
|
-
token: lockToken,
|
|
80
|
-
reason,
|
|
81
|
-
...options?.conflictId ? { conflictId: options.conflictId } : {},
|
|
82
|
-
...options?.resolution ? { resolution: options.resolution } : {}
|
|
83
|
-
},
|
|
84
|
-
extraHeaders
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
async function forceReleaseRecordLock(request, token, resourceKind, resourceId, reason, extraHeaders) {
|
|
88
|
-
return requestJson(
|
|
89
|
-
request,
|
|
90
|
-
"POST",
|
|
91
|
-
"/api/record_locks/force-release",
|
|
92
|
-
token,
|
|
93
|
-
{
|
|
94
|
-
resourceKind,
|
|
95
|
-
resourceId,
|
|
96
|
-
...reason ? { reason } : {}
|
|
97
|
-
},
|
|
98
|
-
extraHeaders
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
async function updateCompany(request, token, companyId, displayName, lockHeaders, extraHeaders) {
|
|
102
|
-
const requestHeaders = { ...extraHeaders ?? {} };
|
|
103
|
-
if (lockHeaders) {
|
|
104
|
-
requestHeaders["x-om-record-lock-kind"] = "customers.company";
|
|
105
|
-
requestHeaders["x-om-record-lock-resource-id"] = companyId;
|
|
106
|
-
if (lockHeaders.token) {
|
|
107
|
-
requestHeaders["x-om-record-lock-token"] = lockHeaders.token;
|
|
108
|
-
}
|
|
109
|
-
if (lockHeaders.baseLogId) {
|
|
110
|
-
requestHeaders["x-om-record-lock-base-log-id"] = lockHeaders.baseLogId;
|
|
111
|
-
}
|
|
112
|
-
if (lockHeaders.resolution) {
|
|
113
|
-
requestHeaders["x-om-record-lock-resolution"] = lockHeaders.resolution;
|
|
114
|
-
}
|
|
115
|
-
if (lockHeaders.conflictId) {
|
|
116
|
-
requestHeaders["x-om-record-lock-conflict-id"] = lockHeaders.conflictId;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return requestJson(
|
|
120
|
-
request,
|
|
121
|
-
"PUT",
|
|
122
|
-
"/api/customers/companies",
|
|
123
|
-
token,
|
|
124
|
-
{
|
|
125
|
-
id: companyId,
|
|
126
|
-
displayName
|
|
127
|
-
},
|
|
128
|
-
requestHeaders
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
function buildScopeCookieFromToken(token) {
|
|
132
|
-
const parts = token.split(".");
|
|
133
|
-
if (parts.length < 2) return null;
|
|
134
|
-
try {
|
|
135
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
136
|
-
const tenantId = typeof payload.tenantId === "string" && payload.tenantId.trim().length > 0 ? payload.tenantId.trim() : null;
|
|
137
|
-
const orgId = typeof payload.orgId === "string" && payload.orgId.trim().length > 0 ? payload.orgId.trim() : null;
|
|
138
|
-
const cookies = [];
|
|
139
|
-
if (tenantId) cookies.push(`om_selected_tenant=${encodeURIComponent(tenantId)}`);
|
|
140
|
-
if (orgId) cookies.push(`om_selected_org=${encodeURIComponent(orgId)}`);
|
|
141
|
-
return cookies.length ? cookies.join("; ") : null;
|
|
142
|
-
} catch {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
async function getCompanyDisplayName(request, token, companyId) {
|
|
147
|
-
const response = await apiRequest(
|
|
148
|
-
request,
|
|
149
|
-
"GET",
|
|
150
|
-
`/api/customers/companies?id=${encodeURIComponent(companyId)}&pageSize=5`,
|
|
151
|
-
{ token }
|
|
152
|
-
);
|
|
153
|
-
expect(response.ok(), `Failed to read company ${companyId}: ${response.status()}`).toBeTruthy();
|
|
154
|
-
const payload = await readJsonSafe(response);
|
|
155
|
-
const rows = Array.isArray(payload?.items) ? payload.items : [];
|
|
156
|
-
const row = rows.find((item) => typeof item.id === "string" && item.id === companyId) ?? rows[0] ?? null;
|
|
157
|
-
if (!row) return null;
|
|
158
|
-
const snake = row.display_name;
|
|
159
|
-
if (typeof snake === "string") return snake;
|
|
160
|
-
const camel = row.displayName;
|
|
161
|
-
if (typeof camel === "string") return camel;
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
async function listNotificationsByType(request, token, type) {
|
|
165
|
-
const response = await apiRequest(
|
|
166
|
-
request,
|
|
167
|
-
"GET",
|
|
168
|
-
`/api/notifications?type=${encodeURIComponent(type)}&pageSize=100`,
|
|
169
|
-
{ token }
|
|
170
|
-
);
|
|
171
|
-
expect(response.ok(), `Failed to list notifications: ${response.status()}`).toBeTruthy();
|
|
172
|
-
const payload = await readJsonSafe(response);
|
|
173
|
-
const items = Array.isArray(payload?.items) ? payload.items : [];
|
|
174
|
-
return items.filter((entry) => {
|
|
175
|
-
if (!entry || typeof entry !== "object") return false;
|
|
176
|
-
const candidate = entry;
|
|
177
|
-
return typeof candidate.id === "string" && typeof candidate.type === "string";
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
async function waitForNotification(request, token, type, predicate, timeoutMs = 15e3, pollMs = 250) {
|
|
181
|
-
const startedAt = Date.now();
|
|
182
|
-
while (Date.now() - startedAt <= timeoutMs) {
|
|
183
|
-
const items = await listNotificationsByType(request, token, type);
|
|
184
|
-
const found = items.find(predicate);
|
|
185
|
-
if (found) return found;
|
|
186
|
-
await sleep(pollMs);
|
|
187
|
-
}
|
|
188
|
-
throw new Error(`Notification ${type} not found within ${timeoutMs}ms`);
|
|
189
|
-
}
|
|
190
|
-
async function executeNotificationAction(request, token, notificationId, actionId, payload) {
|
|
191
|
-
return requestJson(
|
|
192
|
-
request,
|
|
193
|
-
"POST",
|
|
194
|
-
`/api/notifications/${encodeURIComponent(notificationId)}/action`,
|
|
195
|
-
token,
|
|
196
|
-
{
|
|
197
|
-
actionId,
|
|
198
|
-
...payload ? { payload } : {}
|
|
199
|
-
}
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
async function cleanupCompany(request, token, companyId) {
|
|
203
|
-
await deleteEntityIfExists(request, token, "/api/customers/companies", companyId);
|
|
204
|
-
}
|
|
205
|
-
export {
|
|
206
|
-
acquireRecordLock,
|
|
207
|
-
buildScopeCookieFromToken,
|
|
208
|
-
cleanupCompany,
|
|
209
|
-
executeNotificationAction,
|
|
210
|
-
forceReleaseRecordLock,
|
|
211
|
-
getCompanyDisplayName,
|
|
212
|
-
getRecordLockSettings,
|
|
213
|
-
listNotificationsByType,
|
|
214
|
-
releaseRecordLock,
|
|
215
|
-
saveRecordLockSettings,
|
|
216
|
-
updateCompany,
|
|
217
|
-
waitForNotification
|
|
218
|
-
};
|
|
219
|
-
//# sourceMappingURL=recordLocks.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../../src/modules/record_locks/__integration__/helpers/recordLocks.ts"],
|
|
4
|
-
"sourcesContent": ["import { expect, type APIRequestContext, type APIResponse } from '@playwright/test';\nimport { apiRequest } from '@open-mercato/core/modules/core/__integration__/helpers/api';\nimport { deleteEntityIfExists } from '@open-mercato/core/modules/core/__integration__/helpers/crmFixtures';\n\nconst BASE_URL = process.env.BASE_URL || 'http://localhost:3000';\n\nexport type RecordLockSettings = {\n enabled: boolean;\n strategy: 'optimistic' | 'pessimistic';\n timeoutSeconds: number;\n heartbeatSeconds: number;\n enabledResources: string[];\n allowForceUnlock: boolean;\n allowIncomingOverride?: boolean;\n notifyOnConflict: boolean;\n};\n\nexport type RecordLockMutationResolution = 'normal' | 'accept_mine' | 'merged';\n\nexport type RecordLockMutationHeaders = {\n token?: string | null;\n baseLogId?: string | null;\n resolution?: RecordLockMutationResolution;\n conflictId?: string | null;\n};\n\nexport type NotificationItem = {\n id: string;\n type: string;\n status?: 'unread' | 'read' | 'actioned' | 'dismissed';\n actions?: Array<{\n id: string;\n label?: string;\n labelKey?: string;\n }>;\n sourceEntityId?: string | null;\n bodyVariables?: Record<string, string>;\n};\n\ntype ApiCallResult<TBody> = {\n response: APIResponse;\n status: number;\n body: TBody | null;\n};\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nasync function readJsonSafe(response: APIResponse): Promise<unknown> {\n const raw = await response.text();\n if (!raw) return null;\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return null;\n }\n}\n\nasync function requestJson<TBody>(\n request: APIRequestContext,\n method: string,\n path: string,\n token: string,\n data?: unknown,\n extraHeaders?: Record<string, string>,\n): Promise<ApiCallResult<TBody>> {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n ...(extraHeaders ?? {}),\n };\n\n const response = await request.fetch(`${BASE_URL}${path}`, {\n method,\n headers,\n data: data === undefined ? undefined : data,\n });\n\n const body = (await readJsonSafe(response)) as TBody | null;\n\n return {\n response,\n status: response.status(),\n body,\n };\n}\n\nexport async function getRecordLockSettings(\n request: APIRequestContext,\n token: string,\n): Promise<RecordLockSettings> {\n const result = await requestJson<{ settings?: RecordLockSettings }>(\n request,\n 'GET',\n '/api/record_locks/settings',\n token,\n );\n\n expect(result.status).toBe(200);\n expect(result.body?.settings).toBeTruthy();\n\n return result.body?.settings as RecordLockSettings;\n}\n\nexport async function saveRecordLockSettings(\n request: APIRequestContext,\n token: string,\n settings: RecordLockSettings,\n): Promise<RecordLockSettings> {\n const result = await requestJson<{ settings?: RecordLockSettings }>(\n request,\n 'POST',\n '/api/record_locks/settings',\n token,\n settings,\n );\n\n expect(result.status).toBe(200);\n expect(result.body?.settings).toBeTruthy();\n\n return result.body?.settings as RecordLockSettings;\n}\n\nexport async function acquireRecordLock(\n request: APIRequestContext,\n token: string,\n resourceKind: string,\n resourceId: string,\n extraHeaders?: Record<string, string>,\n): Promise<ApiCallResult<Record<string, unknown>>> {\n return requestJson<Record<string, unknown>>(\n request,\n 'POST',\n '/api/record_locks/acquire',\n token,\n { resourceKind, resourceId },\n extraHeaders,\n );\n}\n\nexport async function releaseRecordLock(\n request: APIRequestContext,\n token: string,\n resourceKind: string,\n resourceId: string,\n lockToken: string,\n reason: 'saved' | 'cancelled' | 'unmount' | 'conflict_resolved' = 'cancelled',\n options?: {\n conflictId?: string | null;\n resolution?: 'accept_incoming';\n },\n extraHeaders?: Record<string, string>,\n): Promise<ApiCallResult<Record<string, unknown>>> {\n return requestJson<Record<string, unknown>>(\n request,\n 'POST',\n '/api/record_locks/release',\n token,\n {\n resourceKind,\n resourceId,\n token: lockToken,\n reason,\n ...(options?.conflictId ? { conflictId: options.conflictId } : {}),\n ...(options?.resolution ? { resolution: options.resolution } : {}),\n },\n extraHeaders,\n );\n}\n\nexport async function forceReleaseRecordLock(\n request: APIRequestContext,\n token: string,\n resourceKind: string,\n resourceId: string,\n reason?: string,\n extraHeaders?: Record<string, string>,\n): Promise<ApiCallResult<Record<string, unknown>>> {\n return requestJson<Record<string, unknown>>(\n request,\n 'POST',\n '/api/record_locks/force-release',\n token,\n {\n resourceKind,\n resourceId,\n ...(reason ? { reason } : {}),\n },\n extraHeaders,\n );\n}\n\nexport async function updateCompany(\n request: APIRequestContext,\n token: string,\n companyId: string,\n displayName: string,\n lockHeaders?: RecordLockMutationHeaders,\n extraHeaders?: Record<string, string>,\n): Promise<ApiCallResult<Record<string, unknown>>> {\n const requestHeaders: Record<string, string> = { ...(extraHeaders ?? {}) };\n\n if (lockHeaders) {\n requestHeaders['x-om-record-lock-kind'] = 'customers.company';\n requestHeaders['x-om-record-lock-resource-id'] = companyId;\n\n if (lockHeaders.token) {\n requestHeaders['x-om-record-lock-token'] = lockHeaders.token;\n }\n\n if (lockHeaders.baseLogId) {\n requestHeaders['x-om-record-lock-base-log-id'] = lockHeaders.baseLogId;\n }\n\n if (lockHeaders.resolution) {\n requestHeaders['x-om-record-lock-resolution'] = lockHeaders.resolution;\n }\n\n if (lockHeaders.conflictId) {\n requestHeaders['x-om-record-lock-conflict-id'] = lockHeaders.conflictId;\n }\n }\n\n return requestJson<Record<string, unknown>>(\n request,\n 'PUT',\n '/api/customers/companies',\n token,\n {\n id: companyId,\n displayName,\n },\n requestHeaders,\n );\n}\n\ntype JwtScopePayload = {\n tenantId?: string | null;\n orgId?: string | null;\n};\n\nexport function buildScopeCookieFromToken(token: string): string | null {\n const parts = token.split('.');\n if (parts.length < 2) return null;\n\n try {\n const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8')) as JwtScopePayload;\n const tenantId = typeof payload.tenantId === 'string' && payload.tenantId.trim().length > 0\n ? payload.tenantId.trim()\n : null;\n const orgId = typeof payload.orgId === 'string' && payload.orgId.trim().length > 0\n ? payload.orgId.trim()\n : null;\n\n const cookies: string[] = [];\n if (tenantId) cookies.push(`om_selected_tenant=${encodeURIComponent(tenantId)}`);\n if (orgId) cookies.push(`om_selected_org=${encodeURIComponent(orgId)}`);\n\n return cookies.length ? cookies.join('; ') : null;\n } catch {\n return null;\n }\n}\n\nexport async function getCompanyDisplayName(\n request: APIRequestContext,\n token: string,\n companyId: string,\n): Promise<string | null> {\n const response = await apiRequest(\n request,\n 'GET',\n `/api/customers/companies?id=${encodeURIComponent(companyId)}&pageSize=5`,\n { token },\n );\n\n expect(response.ok(), `Failed to read company ${companyId}: ${response.status()}`).toBeTruthy();\n\n const payload = (await readJsonSafe(response)) as { items?: Array<Record<string, unknown>> } | null;\n const rows = Array.isArray(payload?.items) ? payload.items : [];\n const row = rows.find((item) => typeof item.id === 'string' && item.id === companyId) ?? rows[0] ?? null;\n\n if (!row) return null;\n\n const snake = row.display_name;\n if (typeof snake === 'string') return snake;\n\n const camel = row.displayName;\n if (typeof camel === 'string') return camel;\n\n return null;\n}\n\nexport async function listNotificationsByType(\n request: APIRequestContext,\n token: string,\n type: string,\n): Promise<NotificationItem[]> {\n const response = await apiRequest(\n request,\n 'GET',\n `/api/notifications?type=${encodeURIComponent(type)}&pageSize=100`,\n { token },\n );\n\n expect(response.ok(), `Failed to list notifications: ${response.status()}`).toBeTruthy();\n\n const payload = (await readJsonSafe(response)) as { items?: unknown[] } | null;\n const items = Array.isArray(payload?.items) ? payload.items : [];\n\n return items.filter((entry): entry is NotificationItem => {\n if (!entry || typeof entry !== 'object') return false;\n const candidate = entry as Record<string, unknown>;\n return typeof candidate.id === 'string' && typeof candidate.type === 'string';\n });\n}\n\nexport async function waitForNotification(\n request: APIRequestContext,\n token: string,\n type: string,\n predicate: (item: NotificationItem) => boolean,\n timeoutMs = 15_000,\n pollMs = 250,\n): Promise<NotificationItem> {\n const startedAt = Date.now();\n\n while (Date.now() - startedAt <= timeoutMs) {\n const items = await listNotificationsByType(request, token, type);\n const found = items.find(predicate);\n if (found) return found;\n await sleep(pollMs);\n }\n\n throw new Error(`Notification ${type} not found within ${timeoutMs}ms`);\n}\n\nexport async function executeNotificationAction(\n request: APIRequestContext,\n token: string,\n notificationId: string,\n actionId: string,\n payload?: Record<string, unknown>,\n): Promise<ApiCallResult<{ ok?: boolean; result?: unknown; href?: string }>> {\n return requestJson<{ ok?: boolean; result?: unknown; href?: string }>(\n request,\n 'POST',\n `/api/notifications/${encodeURIComponent(notificationId)}/action`,\n token,\n {\n actionId,\n ...(payload ? { payload } : {}),\n },\n );\n}\n\nexport async function cleanupCompany(\n request: APIRequestContext,\n token: string | null,\n companyId: string | null,\n): Promise<void> {\n await deleteEntityIfExists(request, token, '/api/customers/companies', companyId);\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|