cabloy 5.1.202 → 5.1.203
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/.cabloy-version +1 -1
- package/CHANGELOG.md +11 -0
- package/CONTRIBUTING.md +18 -17
- package/README.md +2 -2
- package/package.json +2 -3
- package/repo-agent-governance/managed-assets.json +12 -12
- package/repo-agent-governance/policies/repository.md +1 -0
- package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +2 -2
- package/repo-agent-governance/skills/cabloy-workflow/evals/evals.json +1 -1
- package/repo-agent-governance/skills/cabloy-worktree-environment/SKILL.md +5 -5
- package/repo-docs/assets/img/cabloy-admin-ssr-cover-en.png +0 -0
- package/repo-docs/assets/img/cabloy-admin-tabs-cover-en.png +0 -0
- package/repo-docs/assets/img/cabloy-ai-crud-cover-en.png +0 -0
- package/repo-docs/demo.md +14 -0
- package/repo-docs/frontend/scripts.md +10 -18
- package/repo-docs/fullstack/parallel-worktree-environment.md +2 -2
- package/repo-docs/index.md +2 -2
- package/repo-docs/reference/repo-scripts.md +15 -31
- package/repo-e2e/config/playwright.config.ts +14 -19
- package/repo-e2e/scripts/e2e.test.ts +17 -0
- package/repo-e2e/scripts/e2e.ts +5 -1
- package/repo-e2e/scripts/runE2e.ts +3 -15
- package/repo-e2e/scripts/runE2eArgs.test.ts +21 -17
- package/repo-e2e/scripts/runE2eArgs.ts +16 -29
- package/repo-e2e/scripts/startE2eVona.ts +20 -9
- package/repo-e2e/specs/a-commerce.spec.ts +232 -31
- package/repo-e2e/specs/cabloy-basic.spec.ts +39 -12
- package/repo-e2e/specs/home-user-account.spec.ts +9 -2
- package/scripts/upgrade.test.ts +147 -0
- package/scripts/upgrade.ts +49 -27
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +51 -5
- package/vona/packages-cli/cli-set-api/src/lib/command/bin.dev.ts +6 -1
- package/vona/packages-cli/cli-set-api/test/binDevMode.test.ts +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +41 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/e2eFixture.ts +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/lib/e2eFixture.ts +224 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/e2eFixture.ts +694 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/e2eFixture.test.ts +434 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import type { EntityOutboxEvent, EntityWebhookInbox } from 'vona-module-a-pay';
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { describe, it } from 'node:test';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createOwnedFixtureName,
|
|
8
|
+
fixtureAdvancedPaymentAuditMatches,
|
|
9
|
+
fixtureEventNeedsDispatch,
|
|
10
|
+
fixtureReservationMatchesLine,
|
|
11
|
+
isFixtureCustomer,
|
|
12
|
+
isOwnedFixtureName,
|
|
13
|
+
selectPaymentFixtureEvent,
|
|
14
|
+
selectRefundFixtureEvent,
|
|
15
|
+
} from '../src/lib/e2eFixture.ts';
|
|
16
|
+
|
|
17
|
+
const eventId = 'mock-payment-owned';
|
|
18
|
+
const payment = {
|
|
19
|
+
eventId,
|
|
20
|
+
paymentSessionId: 101,
|
|
21
|
+
businessReference: '201', // Basic points to payment attempt, not the order.
|
|
22
|
+
providerName: 'pay-mock:mock',
|
|
23
|
+
state: 'succeeded',
|
|
24
|
+
providerCaptureId: 'mock-capture-owned',
|
|
25
|
+
amountMinor: 4599,
|
|
26
|
+
currency: 'USD',
|
|
27
|
+
} as const;
|
|
28
|
+
const expected = {
|
|
29
|
+
paymentSessionId: 101,
|
|
30
|
+
businessReference: '201',
|
|
31
|
+
amountMinor: 4599,
|
|
32
|
+
currency: 'USD',
|
|
33
|
+
clientName: 'default',
|
|
34
|
+
environment: 'sandbox',
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
function event(overrides: Record<string, unknown> = {}): EntityOutboxEvent {
|
|
38
|
+
return {
|
|
39
|
+
id: 301,
|
|
40
|
+
paymentSessionId: 101,
|
|
41
|
+
eventType: 'payment.outcome.v1',
|
|
42
|
+
payload: payment,
|
|
43
|
+
state: 'pending',
|
|
44
|
+
attemptCount: 0,
|
|
45
|
+
nextAttemptAt: new Date(0),
|
|
46
|
+
...overrides,
|
|
47
|
+
} as EntityOutboxEvent;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function inbox(overrides: Record<string, unknown> = {}): EntityWebhookInbox {
|
|
51
|
+
return {
|
|
52
|
+
id: 401,
|
|
53
|
+
paymentSessionId: 101,
|
|
54
|
+
providerEventId: eventId,
|
|
55
|
+
providerName: 'pay-mock:mock',
|
|
56
|
+
clientName: 'default',
|
|
57
|
+
environment: 'sandbox',
|
|
58
|
+
eventType: 'payment.succeeded',
|
|
59
|
+
paymentState: 'succeeded',
|
|
60
|
+
amountMinor: 4599,
|
|
61
|
+
currency: 'USD',
|
|
62
|
+
providerCaptureId: 'mock-capture-owned',
|
|
63
|
+
payloadHash: 'a'.repeat(64),
|
|
64
|
+
state: 'processed',
|
|
65
|
+
retryCount: 0,
|
|
66
|
+
processedAt: new Date(),
|
|
67
|
+
...overrides,
|
|
68
|
+
} as EntityWebhookInbox;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function selectPayment(events = [event()], inboxes = [inbox()]) {
|
|
72
|
+
return selectPaymentFixtureEvent(events, inboxes, expected);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('owned Commerce E2E webhook fixture selection', { concurrency: false }, () => {
|
|
76
|
+
it('requires a generated Admin-owned fixture name and the exact payment attempt relationship', () => {
|
|
77
|
+
const generatedName = createOwnedFixtureName(123);
|
|
78
|
+
assert.match(generatedName, /^e2e-fixture-123-[0-9a-f]{12}$/);
|
|
79
|
+
assert.equal(isOwnedFixtureName(generatedName, 123), true);
|
|
80
|
+
assert.equal(isOwnedFixtureName(generatedName, 12), false);
|
|
81
|
+
assert.equal(isOwnedFixtureName('e2e-fixture-123-a1b2c3d4e5f6', 123), true);
|
|
82
|
+
assert.equal(isOwnedFixtureName('e2e-fixture-123-a1b2c3d4e5f6', 12), false);
|
|
83
|
+
assert.equal(isOwnedFixtureName('e2e-fixture-123-invalid', 123), false);
|
|
84
|
+
assert.equal(isFixtureCustomer(undefined), false);
|
|
85
|
+
assert.equal(
|
|
86
|
+
isFixtureCustomer({ id: 123, name: 'e2e-fixture-customer', anonymous: true }),
|
|
87
|
+
false,
|
|
88
|
+
);
|
|
89
|
+
assert.equal(isFixtureCustomer({ id: 123, name: 'ordinary', anonymous: false }), false);
|
|
90
|
+
assert.equal(
|
|
91
|
+
isFixtureCustomer({ id: 123, name: 'e2e-fixture-customer', anonymous: false }),
|
|
92
|
+
true,
|
|
93
|
+
);
|
|
94
|
+
assert.equal(selectPayment().payload.businessReference, '201');
|
|
95
|
+
assert.throws(
|
|
96
|
+
() =>
|
|
97
|
+
selectPaymentFixtureEvent([event()], [inbox()], {
|
|
98
|
+
...expected,
|
|
99
|
+
businessReference: '202',
|
|
100
|
+
}),
|
|
101
|
+
/payload conflicts/,
|
|
102
|
+
);
|
|
103
|
+
assert.throws(
|
|
104
|
+
() => selectPayment([event({ payload: { ...payment, providerName: 'pay-paypal:paypal' } })]),
|
|
105
|
+
/payload conflicts/,
|
|
106
|
+
);
|
|
107
|
+
assert.throws(() => selectPayment([event({ paymentSessionId: 102 })]), /missing or ambiguous/);
|
|
108
|
+
assert.throws(
|
|
109
|
+
() => selectPayment([event({ payload: { ...payment, paymentSessionId: 102 } })]),
|
|
110
|
+
/payload conflicts/,
|
|
111
|
+
);
|
|
112
|
+
assert.throws(() => selectPayment([event({ refundOperationId: 501 })]), /missing or ambiguous/);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('rejects missing and ambiguous outbox events or processed inboxes', () => {
|
|
116
|
+
assert.throws(() => selectPayment([]), /missing or ambiguous/);
|
|
117
|
+
assert.throws(() => selectPayment([event(), event({ id: 302 })]), /missing or ambiguous/);
|
|
118
|
+
assert.throws(() => selectPayment([event()], []), /missing or ambiguous/);
|
|
119
|
+
assert.throws(
|
|
120
|
+
() => selectPayment([event()], [inbox(), inbox({ id: 402 })]),
|
|
121
|
+
/missing or ambiguous/,
|
|
122
|
+
);
|
|
123
|
+
assert.throws(
|
|
124
|
+
() => selectPayment([event()], [inbox({ state: 'received' })]),
|
|
125
|
+
/processed matching/,
|
|
126
|
+
);
|
|
127
|
+
assert.throws(
|
|
128
|
+
() => selectPayment([event()], [inbox({ errorSummary: 'ignored' })]),
|
|
129
|
+
/processed matching/,
|
|
130
|
+
);
|
|
131
|
+
assert.throws(
|
|
132
|
+
() => selectPayment([event()], [inbox({ providerCaptureId: 'other' })]),
|
|
133
|
+
/processed matching/,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('accepts a missing capture for cancelled and failed payments without accepting a conflicting capture', () => {
|
|
138
|
+
for (const state of ['cancelled', 'failed'] as const) {
|
|
139
|
+
const outcome = { ...payment, state, providerCaptureId: undefined };
|
|
140
|
+
const outcomeEvent = event({ payload: outcome });
|
|
141
|
+
const outcomeInbox = inbox({
|
|
142
|
+
eventType: `payment.${state}`,
|
|
143
|
+
paymentState: state,
|
|
144
|
+
providerCaptureId: null,
|
|
145
|
+
});
|
|
146
|
+
assert.equal(selectPayment([outcomeEvent], [outcomeInbox]).payload.state, state);
|
|
147
|
+
assert.throws(
|
|
148
|
+
() =>
|
|
149
|
+
selectPayment([outcomeEvent], [inbox({ ...outcomeInbox, providerCaptureId: 'other' })]),
|
|
150
|
+
/processed matching/,
|
|
151
|
+
);
|
|
152
|
+
assert.throws(
|
|
153
|
+
() => selectPayment([event({ payload: { ...payment, state } })], [outcomeInbox]),
|
|
154
|
+
/processed matching/,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('requires the refund attempt, mock operation, inbox and outbox to agree', () => {
|
|
160
|
+
const refund = {
|
|
161
|
+
eventId: 'mock-refund-owned',
|
|
162
|
+
paymentSessionId: 101,
|
|
163
|
+
refundOperationId: 501,
|
|
164
|
+
businessReference: '601',
|
|
165
|
+
providerName: 'pay-mock:mock',
|
|
166
|
+
state: 'cancelled',
|
|
167
|
+
providerRefundId: 'mock-refund-owned',
|
|
168
|
+
amountMinor: 4599,
|
|
169
|
+
currency: 'USD',
|
|
170
|
+
} as const;
|
|
171
|
+
const refundEvent = event({
|
|
172
|
+
eventType: 'refund.outcome.v1',
|
|
173
|
+
refundOperationId: 501,
|
|
174
|
+
payload: refund,
|
|
175
|
+
});
|
|
176
|
+
const refundInbox = inbox({
|
|
177
|
+
providerEventId: refund.eventId,
|
|
178
|
+
eventType: 'refund.cancelled',
|
|
179
|
+
refundOperationId: 501,
|
|
180
|
+
refundState: 'cancelled',
|
|
181
|
+
paymentState: undefined,
|
|
182
|
+
providerRefundId: refund.providerRefundId,
|
|
183
|
+
});
|
|
184
|
+
const select = (events = [refundEvent], inboxes = [refundInbox]) =>
|
|
185
|
+
selectRefundFixtureEvent(events, inboxes, {
|
|
186
|
+
...expected,
|
|
187
|
+
refundOperationId: 501,
|
|
188
|
+
businessReference: '601',
|
|
189
|
+
});
|
|
190
|
+
assert.equal(select().payload.state, 'cancelled');
|
|
191
|
+
assert.throws(
|
|
192
|
+
() => select([event({ ...refundEvent, refundOperationId: 502 })]),
|
|
193
|
+
/missing or ambiguous/,
|
|
194
|
+
);
|
|
195
|
+
assert.throws(
|
|
196
|
+
() => select([event({ ...refundEvent, payload: { ...refund, refundOperationId: 502 } })]),
|
|
197
|
+
/payload conflicts/,
|
|
198
|
+
);
|
|
199
|
+
assert.throws(
|
|
200
|
+
() => select([refundEvent], [inbox({ ...refundInbox, refundOperationId: 502 })]),
|
|
201
|
+
/processed matching/,
|
|
202
|
+
);
|
|
203
|
+
assert.throws(
|
|
204
|
+
() => select([event({ ...refundEvent, payload: { ...refund, businessReference: '602' } })]),
|
|
205
|
+
/payload conflicts/,
|
|
206
|
+
);
|
|
207
|
+
assert.throws(
|
|
208
|
+
() => select([refundEvent, event({ ...refundEvent, id: 302 })]),
|
|
209
|
+
/missing or ambiguous/,
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('requires reservation and balance identities before deleting owned stock', () => {
|
|
214
|
+
const line = { id: 10, skuId: 20, quantity: 1 };
|
|
215
|
+
const balance = { id: 30, skuId: 20 };
|
|
216
|
+
const reservation = { orderLineId: 10, skuId: 20, stockBalanceId: 30, quantity: 1 };
|
|
217
|
+
assert.equal(fixtureReservationMatchesLine(reservation, line, balance), true);
|
|
218
|
+
for (const mismatch of [
|
|
219
|
+
{ orderLineId: 11 },
|
|
220
|
+
{ skuId: 21 },
|
|
221
|
+
{ stockBalanceId: 31 },
|
|
222
|
+
{ quantity: 2 },
|
|
223
|
+
]) {
|
|
224
|
+
assert.equal(
|
|
225
|
+
fixtureReservationMatchesLine({ ...reservation, ...mismatch }, line, balance),
|
|
226
|
+
false,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
assert.equal(fixtureReservationMatchesLine(reservation, line, { id: 30, skuId: 21 }), false);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('accepts only the exact persisted payment-to-shipment or payment-to-refund audit chain', () => {
|
|
233
|
+
const paid = {
|
|
234
|
+
id: 10,
|
|
235
|
+
orderId: 1,
|
|
236
|
+
operation: 'paid' as const,
|
|
237
|
+
fromState: 'awaiting_payment' as const,
|
|
238
|
+
toState: 'paid' as const,
|
|
239
|
+
correlationId: 'payment-owned',
|
|
240
|
+
};
|
|
241
|
+
const shipped = {
|
|
242
|
+
id: 11,
|
|
243
|
+
orderId: 1,
|
|
244
|
+
operation: 'shipped' as const,
|
|
245
|
+
fromState: 'paid' as const,
|
|
246
|
+
toState: 'shipped' as const,
|
|
247
|
+
correlationId: 'shipment-owned',
|
|
248
|
+
};
|
|
249
|
+
const requested = {
|
|
250
|
+
id: 11,
|
|
251
|
+
orderId: 1,
|
|
252
|
+
operation: 'refund_requested' as const,
|
|
253
|
+
fromState: 'paid' as const,
|
|
254
|
+
toState: 'refund_requested' as const,
|
|
255
|
+
correlationId: 'request-owned',
|
|
256
|
+
};
|
|
257
|
+
const approved = {
|
|
258
|
+
id: 12,
|
|
259
|
+
orderId: 1,
|
|
260
|
+
operation: 'refund_approved' as const,
|
|
261
|
+
fromState: 'refund_requested' as const,
|
|
262
|
+
toState: 'refund_approved' as const,
|
|
263
|
+
correlationId: 'approval-owned',
|
|
264
|
+
};
|
|
265
|
+
const refunded = {
|
|
266
|
+
id: 13,
|
|
267
|
+
orderId: 1,
|
|
268
|
+
operation: 'refunded' as const,
|
|
269
|
+
fromState: 'refund_approved' as const,
|
|
270
|
+
toState: 'refunded' as const,
|
|
271
|
+
correlationId: 'refund-owned',
|
|
272
|
+
};
|
|
273
|
+
const shippedAudits = [paid, shipped];
|
|
274
|
+
const refundedAudits = [paid, requested, approved, refunded];
|
|
275
|
+
assert.equal(
|
|
276
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
277
|
+
shippedAudits,
|
|
278
|
+
1,
|
|
279
|
+
'shipped',
|
|
280
|
+
'payment-owned',
|
|
281
|
+
'shipment-owned',
|
|
282
|
+
),
|
|
283
|
+
true,
|
|
284
|
+
);
|
|
285
|
+
assert.equal(
|
|
286
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
287
|
+
refundedAudits,
|
|
288
|
+
1,
|
|
289
|
+
'refunded',
|
|
290
|
+
'payment-owned',
|
|
291
|
+
undefined,
|
|
292
|
+
'refund-owned',
|
|
293
|
+
),
|
|
294
|
+
true,
|
|
295
|
+
);
|
|
296
|
+
assert.equal(
|
|
297
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
298
|
+
shippedAudits,
|
|
299
|
+
2,
|
|
300
|
+
'shipped',
|
|
301
|
+
'payment-owned',
|
|
302
|
+
'shipment-owned',
|
|
303
|
+
),
|
|
304
|
+
false,
|
|
305
|
+
);
|
|
306
|
+
assert.equal(
|
|
307
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
308
|
+
shippedAudits,
|
|
309
|
+
1,
|
|
310
|
+
'shipped',
|
|
311
|
+
'other-payment',
|
|
312
|
+
'shipment-owned',
|
|
313
|
+
),
|
|
314
|
+
false,
|
|
315
|
+
);
|
|
316
|
+
assert.equal(
|
|
317
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
318
|
+
shippedAudits,
|
|
319
|
+
1,
|
|
320
|
+
'shipped',
|
|
321
|
+
'payment-owned',
|
|
322
|
+
'other-shipment',
|
|
323
|
+
),
|
|
324
|
+
false,
|
|
325
|
+
);
|
|
326
|
+
assert.equal(
|
|
327
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
328
|
+
refundedAudits,
|
|
329
|
+
1,
|
|
330
|
+
'refunded',
|
|
331
|
+
'payment-owned',
|
|
332
|
+
undefined,
|
|
333
|
+
'other-refund',
|
|
334
|
+
),
|
|
335
|
+
false,
|
|
336
|
+
);
|
|
337
|
+
assert.equal(
|
|
338
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
339
|
+
[paid, { ...requested, fromState: 'awaiting_payment' }, approved, refunded],
|
|
340
|
+
1,
|
|
341
|
+
'refunded',
|
|
342
|
+
'payment-owned',
|
|
343
|
+
undefined,
|
|
344
|
+
'refund-owned',
|
|
345
|
+
),
|
|
346
|
+
false,
|
|
347
|
+
);
|
|
348
|
+
assert.equal(
|
|
349
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
350
|
+
[paid, requested, approved, { ...refunded, orderId: 2 }],
|
|
351
|
+
1,
|
|
352
|
+
'refunded',
|
|
353
|
+
'payment-owned',
|
|
354
|
+
undefined,
|
|
355
|
+
'refund-owned',
|
|
356
|
+
),
|
|
357
|
+
false,
|
|
358
|
+
);
|
|
359
|
+
assert.equal(
|
|
360
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
361
|
+
[paid, requested, approved],
|
|
362
|
+
1,
|
|
363
|
+
'refunded',
|
|
364
|
+
'payment-owned',
|
|
365
|
+
undefined,
|
|
366
|
+
'refund-owned',
|
|
367
|
+
),
|
|
368
|
+
false,
|
|
369
|
+
);
|
|
370
|
+
assert.equal(
|
|
371
|
+
fixtureAdvancedPaymentAuditMatches(
|
|
372
|
+
[paid, shipped, requested],
|
|
373
|
+
1,
|
|
374
|
+
'shipped',
|
|
375
|
+
'payment-owned',
|
|
376
|
+
'shipment-owned',
|
|
377
|
+
),
|
|
378
|
+
false,
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('allows only first-time eligible dispatch and consistent settled replays', () => {
|
|
383
|
+
assert.equal(fixtureEventNeedsDispatch(event()), true);
|
|
384
|
+
assert.equal(
|
|
385
|
+
fixtureEventNeedsDispatch(
|
|
386
|
+
event({ state: 'dispatched', dispatchedAt: new Date(), attemptCount: 1 }),
|
|
387
|
+
),
|
|
388
|
+
false,
|
|
389
|
+
);
|
|
390
|
+
for (const state of ['claimed', 'failed'] as const) {
|
|
391
|
+
assert.throws(() => fixtureEventNeedsDispatch(event({ state })), /not eligible/);
|
|
392
|
+
}
|
|
393
|
+
assert.throws(
|
|
394
|
+
() => fixtureEventNeedsDispatch(event({ state: 'pending', attemptCount: 1 })),
|
|
395
|
+
/not eligible/,
|
|
396
|
+
);
|
|
397
|
+
assert.throws(
|
|
398
|
+
() =>
|
|
399
|
+
fixtureEventNeedsDispatch(event({ state: 'pending', errorSummary: 'settlement failed' })),
|
|
400
|
+
/not eligible/,
|
|
401
|
+
);
|
|
402
|
+
assert.throws(
|
|
403
|
+
() => fixtureEventNeedsDispatch(event({ nextAttemptAt: new Date(Date.now() + 60_000) })),
|
|
404
|
+
/not eligible/,
|
|
405
|
+
);
|
|
406
|
+
assert.throws(
|
|
407
|
+
() => fixtureEventNeedsDispatch(event({ state: 'dispatched' })),
|
|
408
|
+
/inconsistent dispatched/,
|
|
409
|
+
);
|
|
410
|
+
for (const stale of [
|
|
411
|
+
{ claimToken: 'busy' },
|
|
412
|
+
{ claimedAt: new Date() },
|
|
413
|
+
{ claimExpiresAt: new Date() },
|
|
414
|
+
{ errorSummary: 'failure' },
|
|
415
|
+
{ attemptCount: 0 },
|
|
416
|
+
]) {
|
|
417
|
+
assert.throws(
|
|
418
|
+
() =>
|
|
419
|
+
fixtureEventNeedsDispatch(
|
|
420
|
+
event({ state: 'dispatched', dispatchedAt: new Date(), attemptCount: 1, ...stale }),
|
|
421
|
+
),
|
|
422
|
+
/inconsistent dispatched/,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
assert.throws(
|
|
426
|
+
() => fixtureEventNeedsDispatch(event({ state: 'pending', claimExpiresAt: new Date() })),
|
|
427
|
+
/not eligible/,
|
|
428
|
+
);
|
|
429
|
+
assert.throws(
|
|
430
|
+
() => fixtureEventNeedsDispatch(event({ state: 'pending', dispatchedAt: new Date() })),
|
|
431
|
+
/not eligible/,
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
});
|