@open-mercato/enterprise 0.4.5-develop-2e9903a57a → 0.4.5-develop-754ef4d2f0
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,127 +0,0 @@
|
|
|
1
|
-
import widget from '../widgets/injection/record-locking/widget'
|
|
2
|
-
import {
|
|
3
|
-
clearRecordLockFormState,
|
|
4
|
-
getRecordLockFormState,
|
|
5
|
-
setRecordLockFormState,
|
|
6
|
-
} from '@open-mercato/enterprise/modules/record_locks/lib/clientLockStore'
|
|
7
|
-
import { validateBeforeSave } from '../widgets/injection/record-locking/widget.client'
|
|
8
|
-
|
|
9
|
-
jest.mock('../widgets/injection/record-locking/widget.client', () => ({
|
|
10
|
-
__esModule: true,
|
|
11
|
-
default: () => null,
|
|
12
|
-
validateBeforeSave: jest.fn(),
|
|
13
|
-
}))
|
|
14
|
-
|
|
15
|
-
const mockedValidateBeforeSave = validateBeforeSave as jest.MockedFunction<typeof validateBeforeSave>
|
|
16
|
-
|
|
17
|
-
describe('record lock widget resolution headers', () => {
|
|
18
|
-
const formId = 'record-lock:test-form'
|
|
19
|
-
const conflictId = 'a0000000-0000-4000-8000-000000000001'
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
clearRecordLockFormState(formId)
|
|
23
|
-
mockedValidateBeforeSave.mockReset()
|
|
24
|
-
mockedValidateBeforeSave.mockResolvedValue({ ok: true })
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('blocks save when resolution intent is not armed', async () => {
|
|
28
|
-
setRecordLockFormState(formId, {
|
|
29
|
-
formId,
|
|
30
|
-
resourceKind: 'customers.deal',
|
|
31
|
-
resourceId: 'b0000000-0000-4000-8000-000000000001',
|
|
32
|
-
conflict: {
|
|
33
|
-
id: conflictId,
|
|
34
|
-
resourceKind: 'customers.deal',
|
|
35
|
-
resourceId: 'b0000000-0000-4000-8000-000000000001',
|
|
36
|
-
baseActionLogId: null,
|
|
37
|
-
incomingActionLogId: null,
|
|
38
|
-
allowIncomingOverride: true,
|
|
39
|
-
canOverrideIncoming: true,
|
|
40
|
-
resolutionOptions: ['accept_mine'],
|
|
41
|
-
changes: [],
|
|
42
|
-
},
|
|
43
|
-
pendingConflictId: conflictId,
|
|
44
|
-
pendingResolution: 'accept_mine',
|
|
45
|
-
pendingResolutionArmed: false,
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const result = await widget.eventHandlers.onBeforeSave({}, { formId } as any)
|
|
49
|
-
expect(result.ok).toBe(false)
|
|
50
|
-
expect(mockedValidateBeforeSave).not.toHaveBeenCalled()
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('does not call validate before save while conflict is unresolved', async () => {
|
|
54
|
-
setRecordLockFormState(formId, {
|
|
55
|
-
formId,
|
|
56
|
-
resourceKind: 'customers.deal',
|
|
57
|
-
resourceId: 'b0000000-0000-4000-8000-000000000001',
|
|
58
|
-
conflict: {
|
|
59
|
-
id: conflictId,
|
|
60
|
-
resourceKind: 'customers.deal',
|
|
61
|
-
resourceId: 'b0000000-0000-4000-8000-000000000001',
|
|
62
|
-
baseActionLogId: null,
|
|
63
|
-
incomingActionLogId: null,
|
|
64
|
-
allowIncomingOverride: true,
|
|
65
|
-
canOverrideIncoming: true,
|
|
66
|
-
resolutionOptions: ['accept_mine'],
|
|
67
|
-
changes: [],
|
|
68
|
-
},
|
|
69
|
-
pendingConflictId: conflictId,
|
|
70
|
-
pendingResolution: 'normal',
|
|
71
|
-
pendingResolutionArmed: false,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const result = await widget.eventHandlers.onBeforeSave({}, { formId } as any)
|
|
75
|
-
expect(result.ok).toBe(false)
|
|
76
|
-
expect(mockedValidateBeforeSave).not.toHaveBeenCalled()
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test('sends resolution header once and disarms it immediately', async () => {
|
|
80
|
-
setRecordLockFormState(formId, {
|
|
81
|
-
formId,
|
|
82
|
-
resourceKind: 'customers.deal',
|
|
83
|
-
resourceId: 'b0000000-0000-4000-8000-000000000001',
|
|
84
|
-
conflict: {
|
|
85
|
-
id: conflictId,
|
|
86
|
-
resourceKind: 'customers.deal',
|
|
87
|
-
resourceId: 'b0000000-0000-4000-8000-000000000001',
|
|
88
|
-
baseActionLogId: null,
|
|
89
|
-
incomingActionLogId: null,
|
|
90
|
-
allowIncomingOverride: true,
|
|
91
|
-
canOverrideIncoming: true,
|
|
92
|
-
resolutionOptions: ['accept_mine'],
|
|
93
|
-
changes: [],
|
|
94
|
-
},
|
|
95
|
-
pendingConflictId: conflictId,
|
|
96
|
-
pendingResolution: 'accept_mine',
|
|
97
|
-
pendingResolutionArmed: true,
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const first = await widget.eventHandlers.onBeforeSave({}, { formId } as any)
|
|
101
|
-
expect(first.ok).toBe(true)
|
|
102
|
-
if (!first.ok) throw new Error('Expected successful first result')
|
|
103
|
-
expect(first.requestHeaders?.['x-om-record-lock-resolution']).toBe('accept_mine')
|
|
104
|
-
|
|
105
|
-
const consumedState = getRecordLockFormState(formId)
|
|
106
|
-
expect(consumedState?.pendingResolution).toBe('normal')
|
|
107
|
-
expect(consumedState?.pendingResolutionArmed).toBe(false)
|
|
108
|
-
|
|
109
|
-
const second = await widget.eventHandlers.onBeforeSave({}, { formId } as any)
|
|
110
|
-
expect(second.ok).toBe(false)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
test('blocks save when record was deleted by another user', async () => {
|
|
114
|
-
setRecordLockFormState(formId, {
|
|
115
|
-
formId,
|
|
116
|
-
resourceKind: 'customers.deal',
|
|
117
|
-
resourceId: 'b0000000-0000-4000-8000-000000000001',
|
|
118
|
-
recordDeleted: true,
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
const result = await widget.eventHandlers.onBeforeSave({}, { formId } as any)
|
|
122
|
-
expect(result.ok).toBe(false)
|
|
123
|
-
if (result.ok) throw new Error('Expected blocked save result')
|
|
124
|
-
expect(result.message).toContain('deleted')
|
|
125
|
-
expect(mockedValidateBeforeSave).not.toHaveBeenCalled()
|
|
126
|
-
})
|
|
127
|
-
})
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { POST } from '@open-mercato/enterprise/modules/record_locks/api/acquire/route'
|
|
2
|
-
import { resolveRecordLocksApiContext, resolveRequestIp } from '@open-mercato/enterprise/modules/record_locks/api/utils'
|
|
3
|
-
|
|
4
|
-
jest.mock('@open-mercato/enterprise/modules/record_locks/api/utils', () => ({
|
|
5
|
-
resolveRecordLocksApiContext: jest.fn(),
|
|
6
|
-
resolveRequestIp: jest.fn(() => '127.0.0.1'),
|
|
7
|
-
}))
|
|
8
|
-
|
|
9
|
-
function makeContext(overrides: Record<string, unknown> = {}) {
|
|
10
|
-
const emInstance = {
|
|
11
|
-
fork: () => ({
|
|
12
|
-
findOne: async () => null,
|
|
13
|
-
}),
|
|
14
|
-
}
|
|
15
|
-
const rbacServiceInstance = {
|
|
16
|
-
userHasAllFeatures: jest.fn().mockResolvedValue(true),
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
auth: {
|
|
21
|
-
sub: '10000000-0000-4000-8000-000000000001',
|
|
22
|
-
tenantId: '20000000-0000-4000-8000-000000000001',
|
|
23
|
-
},
|
|
24
|
-
organizationId: '30000000-0000-4000-8000-000000000001',
|
|
25
|
-
container: {
|
|
26
|
-
resolve: (key: string) => {
|
|
27
|
-
if (key === 'em') return emInstance
|
|
28
|
-
if (key === 'rbacService') return rbacServiceInstance
|
|
29
|
-
return null
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
...overrides,
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function makeRequest(body: unknown) {
|
|
37
|
-
return new Request('http://localhost/api/record_locks/acquire', {
|
|
38
|
-
method: 'POST',
|
|
39
|
-
headers: { 'content-type': 'application/json' },
|
|
40
|
-
body: JSON.stringify(body),
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe('record_locks acquire route', () => {
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
jest.clearAllMocks()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('returns 400 for invalid payload', async () => {
|
|
50
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue(makeContext({
|
|
51
|
-
recordLockService: { acquire: jest.fn() },
|
|
52
|
-
}))
|
|
53
|
-
|
|
54
|
-
const response = await POST(makeRequest({}))
|
|
55
|
-
expect(response.status).toBe(400)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test('returns lock error payload when service reports lock', async () => {
|
|
59
|
-
const acquire = jest.fn().mockResolvedValue({
|
|
60
|
-
ok: false,
|
|
61
|
-
status: 423,
|
|
62
|
-
error: 'Record is currently locked by another user',
|
|
63
|
-
code: 'record_locked',
|
|
64
|
-
allowForceUnlock: false,
|
|
65
|
-
lock: null,
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue(makeContext({
|
|
69
|
-
recordLockService: { acquire },
|
|
70
|
-
}))
|
|
71
|
-
|
|
72
|
-
const response = await POST(
|
|
73
|
-
makeRequest({
|
|
74
|
-
resourceKind: 'sales.quote',
|
|
75
|
-
resourceId: '40000000-0000-4000-8000-000000000001',
|
|
76
|
-
}),
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
expect(response.status).toBe(423)
|
|
80
|
-
const body = await response.json()
|
|
81
|
-
expect(body).toMatchObject({
|
|
82
|
-
code: 'record_locked',
|
|
83
|
-
error: 'Record is currently locked by another user',
|
|
84
|
-
allowForceUnlock: false,
|
|
85
|
-
})
|
|
86
|
-
expect(resolveRequestIp).toHaveBeenCalled()
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test('returns successful acquire response', async () => {
|
|
90
|
-
const acquire = jest.fn().mockResolvedValue({
|
|
91
|
-
ok: true,
|
|
92
|
-
enabled: true,
|
|
93
|
-
resourceEnabled: true,
|
|
94
|
-
strategy: 'optimistic',
|
|
95
|
-
allowForceUnlock: true,
|
|
96
|
-
heartbeatSeconds: 30,
|
|
97
|
-
acquired: true,
|
|
98
|
-
latestActionLogId: null,
|
|
99
|
-
lock: null,
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue(makeContext({
|
|
103
|
-
recordLockService: { acquire },
|
|
104
|
-
}))
|
|
105
|
-
|
|
106
|
-
const response = await POST(
|
|
107
|
-
makeRequest({
|
|
108
|
-
resourceKind: 'sales.quote',
|
|
109
|
-
resourceId: '40000000-0000-4000-8000-000000000001',
|
|
110
|
-
}),
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
expect(response.status).toBe(200)
|
|
114
|
-
const body = await response.json()
|
|
115
|
-
expect(body).toMatchObject({
|
|
116
|
-
ok: true,
|
|
117
|
-
resourceEnabled: true,
|
|
118
|
-
strategy: 'optimistic',
|
|
119
|
-
allowForceUnlock: true,
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
test('hides force unlock when user lacks force-release feature', async () => {
|
|
124
|
-
const acquire = jest.fn().mockResolvedValue({
|
|
125
|
-
ok: true,
|
|
126
|
-
enabled: true,
|
|
127
|
-
resourceEnabled: true,
|
|
128
|
-
strategy: 'optimistic',
|
|
129
|
-
allowForceUnlock: true,
|
|
130
|
-
heartbeatSeconds: 30,
|
|
131
|
-
acquired: false,
|
|
132
|
-
latestActionLogId: null,
|
|
133
|
-
lock: null,
|
|
134
|
-
})
|
|
135
|
-
const rbacService = {
|
|
136
|
-
userHasAllFeatures: jest.fn().mockResolvedValue(false),
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue(makeContext({
|
|
140
|
-
recordLockService: { acquire },
|
|
141
|
-
container: {
|
|
142
|
-
resolve: (key: string) => {
|
|
143
|
-
if (key === 'rbacService') return rbacService
|
|
144
|
-
if (key === 'em') {
|
|
145
|
-
return {
|
|
146
|
-
fork: () => ({
|
|
147
|
-
findOne: async () => null,
|
|
148
|
-
}),
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return null
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
}))
|
|
155
|
-
|
|
156
|
-
const response = await POST(
|
|
157
|
-
makeRequest({
|
|
158
|
-
resourceKind: 'sales.quote',
|
|
159
|
-
resourceId: '40000000-0000-4000-8000-000000000001',
|
|
160
|
-
}),
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
expect(response.status).toBe(200)
|
|
164
|
-
const body = await response.json()
|
|
165
|
-
expect(body.allowForceUnlock).toBe(false)
|
|
166
|
-
expect(rbacService.userHasAllFeatures).toHaveBeenCalledWith(
|
|
167
|
-
'10000000-0000-4000-8000-000000000001',
|
|
168
|
-
['record_locks.force_release'],
|
|
169
|
-
{
|
|
170
|
-
tenantId: '20000000-0000-4000-8000-000000000001',
|
|
171
|
-
organizationId: '30000000-0000-4000-8000-000000000001',
|
|
172
|
-
},
|
|
173
|
-
)
|
|
174
|
-
})
|
|
175
|
-
})
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { POST } from '@open-mercato/enterprise/modules/record_locks/api/release/route'
|
|
2
|
-
import { resolveRecordLocksApiContext } from '@open-mercato/enterprise/modules/record_locks/api/utils'
|
|
3
|
-
|
|
4
|
-
jest.mock('@open-mercato/enterprise/modules/record_locks/api/utils', () => ({
|
|
5
|
-
resolveRecordLocksApiContext: jest.fn(),
|
|
6
|
-
}))
|
|
7
|
-
|
|
8
|
-
function makeRequest(body: unknown) {
|
|
9
|
-
return new Request('http://localhost/api/record_locks/release', {
|
|
10
|
-
method: 'POST',
|
|
11
|
-
headers: { 'content-type': 'application/json' },
|
|
12
|
-
body: JSON.stringify(body),
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('record_locks release route', () => {
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
jest.clearAllMocks()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('returns 400 when reason=conflict_resolved but conflict payload is missing', async () => {
|
|
22
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue({
|
|
23
|
-
auth: {
|
|
24
|
-
sub: '10000000-0000-4000-8000-000000000001',
|
|
25
|
-
tenantId: '20000000-0000-4000-8000-000000000001',
|
|
26
|
-
},
|
|
27
|
-
organizationId: '30000000-0000-4000-8000-000000000001',
|
|
28
|
-
recordLockService: { release: jest.fn() },
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
const response = await POST(
|
|
32
|
-
makeRequest({
|
|
33
|
-
resourceKind: 'sales.quote',
|
|
34
|
-
resourceId: '40000000-0000-4000-8000-000000000001',
|
|
35
|
-
token: '50000000-0000-4000-8000-000000000001',
|
|
36
|
-
reason: 'conflict_resolved',
|
|
37
|
-
}),
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
expect(response.status).toBe(400)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('passes explicit conflict resolution payload to service and returns result', async () => {
|
|
44
|
-
const release = jest.fn().mockResolvedValue({
|
|
45
|
-
ok: true,
|
|
46
|
-
released: true,
|
|
47
|
-
conflictResolved: true,
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue({
|
|
51
|
-
auth: {
|
|
52
|
-
sub: '10000000-0000-4000-8000-000000000001',
|
|
53
|
-
tenantId: '20000000-0000-4000-8000-000000000001',
|
|
54
|
-
},
|
|
55
|
-
organizationId: '30000000-0000-4000-8000-000000000001',
|
|
56
|
-
recordLockService: { release },
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const response = await POST(
|
|
60
|
-
makeRequest({
|
|
61
|
-
resourceKind: 'sales.quote',
|
|
62
|
-
resourceId: '40000000-0000-4000-8000-000000000001',
|
|
63
|
-
token: '50000000-0000-4000-8000-000000000001',
|
|
64
|
-
reason: 'conflict_resolved',
|
|
65
|
-
conflictId: '60000000-0000-4000-8000-000000000001',
|
|
66
|
-
resolution: 'accept_incoming',
|
|
67
|
-
}),
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
expect(response.status).toBe(200)
|
|
71
|
-
const body = await response.json()
|
|
72
|
-
expect(body).toEqual({
|
|
73
|
-
ok: true,
|
|
74
|
-
released: true,
|
|
75
|
-
conflictResolved: true,
|
|
76
|
-
})
|
|
77
|
-
expect(release).toHaveBeenCalledWith({
|
|
78
|
-
token: '50000000-0000-4000-8000-000000000001',
|
|
79
|
-
resourceKind: 'sales.quote',
|
|
80
|
-
resourceId: '40000000-0000-4000-8000-000000000001',
|
|
81
|
-
reason: 'conflict_resolved',
|
|
82
|
-
conflictId: '60000000-0000-4000-8000-000000000001',
|
|
83
|
-
resolution: 'accept_incoming',
|
|
84
|
-
tenantId: '20000000-0000-4000-8000-000000000001',
|
|
85
|
-
organizationId: '30000000-0000-4000-8000-000000000001',
|
|
86
|
-
userId: '10000000-0000-4000-8000-000000000001',
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('accepts conflict_resolved payload without token and passes it to service', async () => {
|
|
91
|
-
const release = jest.fn().mockResolvedValue({
|
|
92
|
-
ok: true,
|
|
93
|
-
released: false,
|
|
94
|
-
conflictResolved: true,
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue({
|
|
98
|
-
auth: {
|
|
99
|
-
sub: '10000000-0000-4000-8000-000000000001',
|
|
100
|
-
tenantId: '20000000-0000-4000-8000-000000000001',
|
|
101
|
-
},
|
|
102
|
-
organizationId: '30000000-0000-4000-8000-000000000001',
|
|
103
|
-
recordLockService: { release },
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const response = await POST(
|
|
107
|
-
makeRequest({
|
|
108
|
-
resourceKind: 'sales.quote',
|
|
109
|
-
resourceId: '40000000-0000-4000-8000-000000000001',
|
|
110
|
-
reason: 'conflict_resolved',
|
|
111
|
-
conflictId: '60000000-0000-4000-8000-000000000001',
|
|
112
|
-
resolution: 'accept_incoming',
|
|
113
|
-
}),
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
expect(response.status).toBe(200)
|
|
117
|
-
const body = await response.json()
|
|
118
|
-
expect(body).toEqual({
|
|
119
|
-
ok: true,
|
|
120
|
-
released: false,
|
|
121
|
-
conflictResolved: true,
|
|
122
|
-
})
|
|
123
|
-
expect(release).toHaveBeenCalledWith({
|
|
124
|
-
token: undefined,
|
|
125
|
-
resourceKind: 'sales.quote',
|
|
126
|
-
resourceId: '40000000-0000-4000-8000-000000000001',
|
|
127
|
-
reason: 'conflict_resolved',
|
|
128
|
-
conflictId: '60000000-0000-4000-8000-000000000001',
|
|
129
|
-
resolution: 'accept_incoming',
|
|
130
|
-
tenantId: '20000000-0000-4000-8000-000000000001',
|
|
131
|
-
organizationId: '30000000-0000-4000-8000-000000000001',
|
|
132
|
-
userId: '10000000-0000-4000-8000-000000000001',
|
|
133
|
-
})
|
|
134
|
-
})
|
|
135
|
-
})
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { GET, POST } from '@open-mercato/enterprise/modules/record_locks/api/settings/route'
|
|
2
|
-
import { resolveRecordLocksApiContext } from '@open-mercato/enterprise/modules/record_locks/api/utils'
|
|
3
|
-
|
|
4
|
-
jest.mock('@open-mercato/enterprise/modules/record_locks/api/utils', () => ({
|
|
5
|
-
resolveRecordLocksApiContext: jest.fn(),
|
|
6
|
-
}))
|
|
7
|
-
|
|
8
|
-
function makeRequest(body: unknown) {
|
|
9
|
-
return new Request('http://localhost/api/record_locks/settings', {
|
|
10
|
-
method: 'POST',
|
|
11
|
-
headers: { 'content-type': 'application/json' },
|
|
12
|
-
body: JSON.stringify(body),
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('record_locks settings route', () => {
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
jest.clearAllMocks()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('GET returns current settings', async () => {
|
|
22
|
-
const settings = {
|
|
23
|
-
enabled: false,
|
|
24
|
-
strategy: 'optimistic',
|
|
25
|
-
timeoutSeconds: 300,
|
|
26
|
-
heartbeatSeconds: 30,
|
|
27
|
-
enabledResources: [],
|
|
28
|
-
allowForceUnlock: true,
|
|
29
|
-
allowIncomingOverride: true,
|
|
30
|
-
notifyOnConflict: true,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue({
|
|
34
|
-
recordLockService: {
|
|
35
|
-
getSettings: jest.fn().mockResolvedValue(settings),
|
|
36
|
-
},
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const response = await GET(new Request('http://localhost/api/record_locks/settings'))
|
|
40
|
-
expect(response.status).toBe(200)
|
|
41
|
-
const body = await response.json()
|
|
42
|
-
expect(body).toEqual({ settings })
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test('POST returns 400 when payload is invalid', async () => {
|
|
46
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue({
|
|
47
|
-
recordLockService: { saveSettings: jest.fn() },
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const response = await POST(makeRequest({ enabled: 'yes' }))
|
|
51
|
-
expect(response.status).toBe(400)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('POST saves and returns settings', async () => {
|
|
55
|
-
const settings = {
|
|
56
|
-
enabled: true,
|
|
57
|
-
strategy: 'pessimistic',
|
|
58
|
-
timeoutSeconds: 600,
|
|
59
|
-
heartbeatSeconds: 30,
|
|
60
|
-
enabledResources: ['sales.quote'],
|
|
61
|
-
allowForceUnlock: true,
|
|
62
|
-
allowIncomingOverride: true,
|
|
63
|
-
notifyOnConflict: true,
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const saveSettings = jest.fn().mockResolvedValue(settings)
|
|
67
|
-
;(resolveRecordLocksApiContext as jest.Mock).mockResolvedValue({
|
|
68
|
-
recordLockService: { saveSettings },
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
const response = await POST(makeRequest(settings))
|
|
72
|
-
expect(response.status).toBe(200)
|
|
73
|
-
const body = await response.json()
|
|
74
|
-
expect(body).toEqual({ settings })
|
|
75
|
-
expect(saveSettings).toHaveBeenCalledWith(expect.objectContaining({
|
|
76
|
-
enabled: true,
|
|
77
|
-
strategy: 'pessimistic',
|
|
78
|
-
timeoutSeconds: 600,
|
|
79
|
-
heartbeatSeconds: 30,
|
|
80
|
-
enabledResources: ['sales.quote'],
|
|
81
|
-
allowForceUnlock: true,
|
|
82
|
-
notifyOnConflict: true,
|
|
83
|
-
}))
|
|
84
|
-
})
|
|
85
|
-
})
|