cabloy 5.1.225 → 5.1.227

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 (89) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/vona-tsc.yml +2 -2
  3. package/CHANGELOG.md +22 -0
  4. package/oxlint.config.ts +5 -1
  5. package/package.json +5 -11
  6. package/repo-agent-governance/managed-assets.json +9 -9
  7. package/repo-agent-governance/policies/repository.md +2 -0
  8. package/repo-agent-governance/skills/cabloy-spec-execution/references/execution-protocol.md +6 -2
  9. package/repo-agent-governance/skills/cabloy-spec-generation/SKILL.md +1 -1
  10. package/repo-docs/ai/ai-spec-driven-development.md +6 -2
  11. package/repo-docs/reference/repo-scripts.md +18 -19
  12. package/scripts/oxlintPlugins.ts +25 -0
  13. package/scripts/typecheck.test.ts +107 -94
  14. package/scripts/typecheck.ts +1 -1
  15. package/vona/oxlint.config.ts +5 -1
  16. package/vona/package.original.json +5 -6
  17. package/vona/packages-cli/cabloy-cli/package.json +1 -1
  18. package/vona/packages-cli/cli/package.json +1 -1
  19. package/vona/packages-cli/cli-set-api/package.json +1 -1
  20. package/vona/packages-cli/cli-set-api/src/lib/beans.ts +0 -2
  21. package/vona/packages-cli/cli-set-api/src/lib/commands.ts +0 -2
  22. package/vona/packages-utils/lint/package.json +3 -3
  23. package/vona/packages-utils/module-glob/package.json +2 -1
  24. package/vona/packages-utils/module-glob/src/index.ts +2 -26
  25. package/vona/packages-utils/module-glob/src/suites.ts +31 -0
  26. package/vona/packages-utils/module-glob/test/suites.test.ts +108 -0
  27. package/vona/packages-vona/vona/package.json +1 -1
  28. package/vona/pnpm-lock.yaml +254 -182
  29. package/vona/pnpm-workspace.yaml +0 -1
  30. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/product.test.ts +2 -0
  31. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/sku.test.ts +1 -0
  32. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/skuUniqueness.test.ts +9 -10
  33. package/vona/src/suite/a-commerce/modules/commerce-member/test/addressOwnership.test.ts +43 -16
  34. package/vona/src/suite/a-commerce/modules/commerce-member/test/addressPresentation.test.ts +7 -1
  35. package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentIndexes.test.ts +2 -1
  36. package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponReservation.test.ts +18 -9
  37. package/vona/src/suite/a-commerce/modules/commerce-trade/test/cartOwnership.test.ts +28 -11
  38. package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutReservation.test.ts +33 -21
  39. package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutTransaction.test.ts +6 -5
  40. package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +33 -25
  41. package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +31 -20
  42. package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +30 -30
  43. package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +24 -26
  44. package/vona/src/suite/a-commerce/modules/commerce-trade/test/shipment.test.ts +7 -2
  45. package/vona/src/suite/a-commerce/modules/commerce-trade/test/shipmentRefundRace.test.ts +15 -5
  46. package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockBalance.test.ts +7 -5
  47. package/vona/src/suite/a-home/modules/home-user/test/account.test.ts +2 -2
  48. package/vona/src/suite/a-home/modules/home-user/test/accountActivation.test.ts +2 -1
  49. package/vona/src/suite/a-home/modules/home-user/test/accountOpenapi.test.ts +53 -42
  50. package/vona/src/suite/a-home/modules/home-user/test/accountPasswordReset.test.ts +1 -0
  51. package/vona/src/suite/a-home/modules/home-user/test/accountPasswordSet.test.ts +2 -1
  52. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +12 -7
  53. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +23 -11
  54. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  55. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/test/rbacCatalogGuard.test.ts +5 -1
  56. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  57. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/test/ssrMenuVisibility.test.ts +3 -3
  58. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  59. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  60. package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +1 -1
  61. package/vona/src/suite-vendor/a-vona/modules/a-metrics/test/middlewareSystem.health.test.ts +33 -12
  62. package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
  63. package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/permissions.ts +3 -1
  64. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  65. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_crud_inner.ts +6 -2
  66. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_crud_table.ts +4 -4
  67. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/dto/dtoMutate.ts +38 -9
  68. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsGroup.ts +12 -10
  69. package/vona/src/suite-vendor/a-vona/modules/a-orm/test/dtoMutate.types.test.ts +129 -0
  70. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  71. package/vona/src/suite-vendor/a-vona/modules/a-security/package.json +1 -1
  72. package/vona/src/suite-vendor/a-vona/modules/a-security/test/origin.test.ts +1 -0
  73. package/vona/src/suite-vendor/a-vona/modules/a-web/cli/controller/metadata/generate.ts +72 -28
  74. package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
  75. package/vona/src/suite-vendor/a-vona/modules/a-web/test/controllerMetadataParser.test.ts +156 -0
  76. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  77. package/vona/tsconfig.json +19 -35
  78. package/zova/oxlint.config.ts +5 -1
  79. package/zova/package.original.json +6 -6
  80. package/zova/packages-cli/cli/package.json +3 -3
  81. package/zova/packages-cli/cli-set-front/package.json +4 -4
  82. package/zova/packages-utils/zova-vite/package.json +2 -2
  83. package/zova/packages-zova/zova/package.json +2 -2
  84. package/zova/pnpm-lock.yaml +195 -65
  85. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  86. package/zova/src/suite-vendor/a-zova/package.json +2 -2
  87. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.tsc.ts +0 -89
  88. package/vona/packages-cli/cli-set-api/src/lib/command/bin.tsc.ts +0 -24
  89. /package/{vona/packages-cli/cli-set-api/src/lib → scripts}/typescriptRunner.ts +0 -0
@@ -1,23 +1,25 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+
1
3
  import assert from 'node:assert';
2
4
  import { createHmac, randomUUID } from 'node:crypto';
3
5
  import { after, before, describe, it } from 'node:test';
4
6
  import { acquireTestLock, app } from 'vona-mock';
5
7
 
6
8
  interface IFixture {
7
- categoryId?: number;
8
- productId?: number;
9
- skuId?: number;
10
- balanceId?: number;
11
- templateId?: number;
12
- grantId?: number;
13
- addressId?: number;
14
- cartId?: number;
15
- userId?: number;
9
+ categoryId?: TableIdentity;
10
+ productId?: TableIdentity;
11
+ skuId?: TableIdentity;
12
+ balanceId?: TableIdentity;
13
+ templateId?: TableIdentity;
14
+ grantId?: TableIdentity;
15
+ addressId?: TableIdentity;
16
+ cartId?: TableIdentity;
17
+ userId?: TableIdentity;
16
18
  customerName?: string;
17
19
  checkoutCorrelationId?: string;
18
- orderId?: number;
19
- paymentAttemptId?: number;
20
- paymentSessionId?: number;
20
+ orderId?: TableIdentity;
21
+ paymentAttemptId?: TableIdentity;
22
+ paymentSessionId?: TableIdentity;
21
23
  }
22
24
 
23
25
  async function cleanup(fixture: IFixture) {
@@ -112,6 +114,7 @@ async function createCheckoutFixture(suffix: string, withCoupon = true): Promise
112
114
  lifecycle: 'active',
113
115
  },
114
116
  });
117
+ assert.ok(fixture.skuId !== undefined);
115
118
  fixture.balanceId = (
116
119
  await app.scope('commerce-trade').service.stockBalance.adjustStock({
117
120
  skuId: fixture.skuId,
@@ -173,6 +176,7 @@ async function createCheckoutFixture(suffix: string, withCoupon = true): Promise
173
176
  quantity: 1,
174
177
  });
175
178
  fixture.checkoutCorrelationId = `payment-checkout-${suffix}`;
179
+ assert.ok(fixture.addressId !== undefined);
176
180
  const checkout = await app.scope('commerce-trade').service.order.checkout({
177
181
  addressId: fixture.addressId,
178
182
  couponGrantId: fixture.grantId,
@@ -184,7 +188,7 @@ async function createCheckoutFixture(suffix: string, withCoupon = true): Promise
184
188
  return fixture;
185
189
  }
186
190
 
187
- describe('paymentOutcome.test.ts', { concurrency: false, sequential: true }, () => {
191
+ describe('paymentOutcome.test.ts', { concurrency: false }, () => {
188
192
  const releaseTestLocks: Array<() => void> = [];
189
193
 
190
194
  before(async () => {
@@ -223,8 +227,9 @@ describe('paymentOutcome.test.ts', { concurrency: false, sequential: true }, ()
223
227
  providerCaptureId: `capture-${eventId}`,
224
228
  });
225
229
  const options = app.bean.payProvider.getOptions('pay-mock:mock', 'default');
226
- assert.equal(typeof options.secretWebhook, 'string');
227
- const signature = createHmac('sha256', options.secretWebhook).update(rawBody).digest('hex');
230
+ const secretWebhook = options.secretWebhook;
231
+ assert.ok(typeof secretWebhook === 'string');
232
+ const signature = createHmac('sha256', secretWebhook).update(rawBody).digest('hex');
228
233
  const webhookPath = app.util.getAbsoluteUrlByApiPath('/pay/webhook/pay-mock:mock/default');
229
234
  const webhookUrl = webhookPath.startsWith('http')
230
235
  ? webhookPath
@@ -635,17 +640,23 @@ describe('paymentOutcome.test.ts', { concurrency: false, sequential: true }, ()
635
640
  innerAccess: false,
636
641
  }),
637
642
  () =>
638
- app.bean.executor.performAction('get', '/commerce/trade/order/viewMine/1', {
643
+ app.bean.executor.performAction('get', '/commerce/trade/order/viewMine/:id', {
644
+ params: { id: 1 },
639
645
  innerAccess: false,
640
646
  }),
641
647
  () =>
642
- app.bean.executor.performAction('get', '/commerce/trade/order/1', {
648
+ app.bean.executor.performAction('get', '/commerce/trade/order/:id', {
649
+ params: { id: 1 },
643
650
  innerAccess: false,
644
651
  }),
645
652
  () =>
646
- app.bean.executor.performAction('get', '/pay/payment-session/1', {
647
- innerAccess: false,
648
- }),
653
+ app
654
+ .scope('a-executor')
655
+ .service.executor.performActionInner(
656
+ 'get',
657
+ app.util.combineApiPath('/pay/payment-session/1', '', true, true),
658
+ { innerAccess: false },
659
+ ),
649
660
  ];
650
661
  for (const request of requests) {
651
662
  await assert.rejects(request(), (error: any) => error.code === 401);
@@ -1,22 +1,24 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+
1
3
  import assert from 'node:assert';
2
4
  import { randomUUID } from 'node:crypto';
3
5
  import { after, before, describe, it } from 'node:test';
4
6
  import { acquireTestLock, app } from 'vona-mock';
5
7
 
6
8
  interface IFixture {
7
- categoryId?: number;
8
- productId?: number;
9
- skuId?: number;
10
- balanceId?: number;
11
- templateId?: number;
12
- grantId?: number;
13
- addressId?: number;
14
- cartId?: number;
15
- userId?: number;
9
+ categoryId?: TableIdentity;
10
+ productId?: TableIdentity;
11
+ skuId?: TableIdentity;
12
+ balanceId?: TableIdentity;
13
+ templateId?: TableIdentity;
14
+ grantId?: TableIdentity;
15
+ addressId?: TableIdentity;
16
+ cartId?: TableIdentity;
17
+ userId?: TableIdentity;
16
18
  customerName?: string;
17
- orderId?: number;
18
- paymentAttemptId?: number;
19
- paymentSessionId?: number;
19
+ orderId?: TableIdentity;
20
+ paymentAttemptId?: TableIdentity;
21
+ paymentSessionId?: TableIdentity;
20
22
  }
21
23
 
22
24
  async function cleanup(fixture: IFixture) {
@@ -117,6 +119,7 @@ async function createPaidOrder(suffix: string): Promise<IFixture> {
117
119
  lifecycle: 'active',
118
120
  },
119
121
  });
122
+ assert.ok(fixture.skuId !== undefined);
120
123
  fixture.balanceId = (
121
124
  await app.scope('commerce-trade').service.stockBalance.adjustStock({
122
125
  skuId: fixture.skuId,
@@ -172,6 +175,7 @@ async function createPaidOrder(suffix: string): Promise<IFixture> {
172
175
  await app
173
176
  .scope('commerce-trade')
174
177
  .model.cartItem.insert({ cartId: cart.id, skuId: fixture.skuId, quantity: 1 });
178
+ assert.ok(fixture.addressId !== undefined);
175
179
  const checkout = await app.scope('commerce-trade').service.order.checkout({
176
180
  addressId: fixture.addressId,
177
181
  couponGrantId: fixture.grantId,
@@ -264,7 +268,7 @@ async function waitForPostgresWaiter(waiterPid: number, holderPid: number): Prom
264
268
  assert.fail(`PostgreSQL backend ${waiterPid} was not blocked by backend ${holderPid}`);
265
269
  }
266
270
 
267
- describe('refundLifecycle.test.ts', { concurrency: false, sequential: true }, () => {
271
+ describe('refundLifecycle.test.ts', { concurrency: false }, () => {
268
272
  let releaseTestLock: (() => void) | undefined;
269
273
 
270
274
  before(async () => {
@@ -363,6 +367,7 @@ describe('refundLifecycle.test.ts', { concurrency: false, sequential: true }, ()
363
367
  const executed = await order.executeRefund(fixture.orderId!);
364
368
  await assert.rejects(order.executeRefund(fixture.orderId!), { status: 409 });
365
369
  const pay = app.scope('a-pay');
370
+ assert.ok(executed.refundOperationId !== undefined);
366
371
  const refundOperation = await pay.model.refundOperation.getById(executed.refundOperationId);
367
372
  const providerOperation = await pay.model.providerOperation.get({
368
373
  refundOperationId: refundOperation?.id,
@@ -427,7 +432,8 @@ describe('refundLifecycle.test.ts', { concurrency: false, sequential: true }, ()
427
432
  where: { refundRequestId: approved.refundRequestId },
428
433
  });
429
434
  const [audit] = audits.filter(item => item.toRefundState === 'failed');
430
- assert.equal(audit?.idempotencyKey.length <= 100, true);
435
+ assert.ok(audit?.idempotencyKey !== undefined);
436
+ assert.equal(audit.idempotencyKey.length <= 100, true);
431
437
  assert.equal(audit?.correlationId.length <= 100, true);
432
438
  });
433
439
  }
@@ -844,14 +850,11 @@ describe('refundLifecycle.test.ts', { concurrency: false, sequential: true }, ()
844
850
  Object.assign(fixture, await createPaidOrder(randomUUID().slice(0, 12)));
845
851
  await app.bean.passport.signout();
846
852
  await assert.rejects(
847
- app.bean.executor.performAction(
848
- 'post',
849
- `/commerce/trade/order/${fixture.orderId}/requestRefund`,
850
- {
851
- innerAccess: false,
852
- body: { reason: 'anonymous', idempotencyKey: 'anonymous-1' },
853
- },
854
- ),
853
+ app.bean.executor.performAction('post', '/commerce/trade/order/:id/requestRefund', {
854
+ params: { id: fixture.orderId },
855
+ innerAccess: false,
856
+ body: { reason: 'anonymous', idempotencyKey: 'anonymous-1' },
857
+ }),
855
858
  (error: any) => error.code === 401,
856
859
  );
857
860
  await app.bean.passport.signinMock(fixture.customerName as any);
@@ -860,14 +863,11 @@ describe('refundLifecycle.test.ts', { concurrency: false, sequential: true }, ()
860
863
  idempotencyKey: 'customer-1',
861
864
  });
862
865
  await assert.rejects(
863
- app.bean.executor.performAction(
864
- 'post',
865
- `/commerce/trade/order/${fixture.orderId}/approveRefund`,
866
- {
867
- innerAccess: false,
868
- body: { reason: 'not admin', idempotencyKey: 'not-admin-1' },
869
- },
870
- ),
866
+ app.bean.executor.performAction('post', '/commerce/trade/order/:id/approveRefund', {
867
+ params: { id: fixture.orderId },
868
+ innerAccess: false,
869
+ body: { reason: 'not admin', idempotencyKey: 'not-admin-1' },
870
+ }),
871
871
  (error: any) => error.code === 403,
872
872
  );
873
873
  } finally {
@@ -1,3 +1,5 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+
1
3
  import assert from 'node:assert';
2
4
  import { randomUUID } from 'node:crypto';
3
5
  import { after, before, describe, it } from 'node:test';
@@ -6,19 +8,19 @@ import { acquireTestLock, app } from 'vona-mock';
6
8
  import { BeanScheduleOrderExpiry } from '../src/bean/bean.scheduleOrderExpiry.ts';
7
9
 
8
10
  interface IFixture {
9
- categoryId?: number;
10
- productId?: number;
11
- skuId?: number;
12
- balanceId?: number;
13
- userId?: number;
14
- couponTemplateId?: number;
15
- couponGrantId?: number;
16
- cartId?: number;
17
- addressId?: number;
18
- orderId?: number;
19
- orderLineId?: number;
20
- reservationId?: number;
21
- paymentAttemptId?: number;
11
+ categoryId?: TableIdentity;
12
+ productId?: TableIdentity;
13
+ skuId?: TableIdentity;
14
+ balanceId?: TableIdentity;
15
+ userId?: TableIdentity;
16
+ couponTemplateId?: TableIdentity;
17
+ couponGrantId?: TableIdentity;
18
+ cartId?: TableIdentity;
19
+ addressId?: TableIdentity;
20
+ orderId?: TableIdentity;
21
+ orderLineId?: TableIdentity;
22
+ reservationId?: TableIdentity;
23
+ paymentAttemptId?: TableIdentity;
22
24
  }
23
25
 
24
26
  async function cleanup(fixture: IFixture) {
@@ -92,6 +94,7 @@ async function createExpiredCheckoutFixture(suffix: string): Promise<IFixture> {
92
94
  lifecycle: 'active',
93
95
  },
94
96
  });
97
+ assert.ok(fixture.skuId !== undefined);
95
98
  fixture.balanceId = (
96
99
  await app.scope('commerce-trade').service.stockBalance.adjustStock({
97
100
  skuId: fixture.skuId,
@@ -122,6 +125,7 @@ async function createExpiredCheckoutFixture(suffix: string): Promise<IFixture> {
122
125
  skuId: fixture.skuId,
123
126
  quantity: 1,
124
127
  });
128
+ assert.ok(fixture.addressId !== undefined);
125
129
  const created = await app.scope('commerce-trade').service.order.checkout({
126
130
  addressId: fixture.addressId,
127
131
  correlationId: `schedule-expiry-${suffix}`,
@@ -144,7 +148,7 @@ async function createExpiredCheckoutFixture(suffix: string): Promise<IFixture> {
144
148
  }
145
149
  }
146
150
 
147
- describe('reservationExpiry.test.ts', { concurrency: false, sequential: true }, () => {
151
+ describe('reservationExpiry.test.ts', { concurrency: false }, () => {
148
152
  let releaseTestLock: (() => void) | undefined;
149
153
 
150
154
  before(async () => {
@@ -192,6 +196,7 @@ describe('reservationExpiry.test.ts', { concurrency: false, sequential: true },
192
196
  lifecycle: 'active',
193
197
  },
194
198
  });
199
+ assert.ok(fixture.skuId !== undefined);
195
200
  const balance = await app.scope('commerce-trade').service.stockBalance.adjustStock({
196
201
  skuId: fixture.skuId,
197
202
  delta: 1,
@@ -242,6 +247,7 @@ describe('reservationExpiry.test.ts', { concurrency: false, sequential: true },
242
247
  skuId: fixture.skuId,
243
248
  quantity: 1,
244
249
  });
250
+ assert.ok(fixture.addressId !== undefined);
245
251
  const created = await app.scope('commerce-trade').service.order.checkout({
246
252
  addressId: fixture.addressId,
247
253
  couponGrantId: fixture.couponGrantId,
@@ -342,9 +348,7 @@ describe('reservationExpiry.test.ts', { concurrency: false, sequential: true },
342
348
  );
343
349
 
344
350
  await app.bean.executor.mockCtx(async () => {
345
- await app.bean
346
- ._getBean<BeanScheduleOrderExpiry>('commerce-trade.schedule.beanScheduleOrderExpiry')
347
- .execute();
351
+ await app.bean._getBean(BeanScheduleOrderExpiry).execute();
348
352
  const order = await app
349
353
  .scope('commerce-trade')
350
354
  .model.order.getById(defaultFixture!.orderId!);
@@ -357,9 +361,7 @@ describe('reservationExpiry.test.ts', { concurrency: false, sequential: true },
357
361
  assert.equal(order?.state, 'expired');
358
362
  assert.equal(reservation?.state, 'released');
359
363
  assert.equal(paymentAttempt?.state, 'cancelled');
360
- await app.bean
361
- ._getBean<BeanScheduleOrderExpiry>('commerce-trade.schedule.beanScheduleOrderExpiry')
362
- .execute();
364
+ await app.bean._getBean(BeanScheduleOrderExpiry).execute();
363
365
  const audits = await app.scope('commerce-trade').model.orderAudit.select({
364
366
  where: { orderId: defaultFixture!.orderId! },
365
367
  orders: [['id', 'asc']],
@@ -385,9 +387,7 @@ describe('reservationExpiry.test.ts', { concurrency: false, sequential: true },
385
387
  assert.equal(reservation?.state, 'reserved');
386
388
  assert.equal(paymentAttempt?.state, 'created');
387
389
 
388
- await app.bean
389
- ._getBean<BeanScheduleOrderExpiry>('commerce-trade.schedule.beanScheduleOrderExpiry')
390
- .execute();
390
+ await app.bean._getBean(BeanScheduleOrderExpiry).execute();
391
391
  assert.equal(
392
392
  (await app.scope('commerce-trade').model.order.getById(shareFixture!.orderId!))?.state,
393
393
  'expired',
@@ -408,9 +408,7 @@ describe('reservationExpiry.test.ts', { concurrency: false, sequential: true },
408
408
  )?.state,
409
409
  'cancelled',
410
410
  );
411
- await app.bean
412
- ._getBean<BeanScheduleOrderExpiry>('commerce-trade.schedule.beanScheduleOrderExpiry')
413
- .execute();
411
+ await app.bean._getBean(BeanScheduleOrderExpiry).execute();
414
412
  const audits = await app.scope('commerce-trade').model.orderAudit.select({
415
413
  where: { orderId: shareFixture!.orderId! },
416
414
  orders: [['id', 'asc']],
@@ -69,7 +69,7 @@ async function createPaidOrder(suffix: string): Promise<IFixture> {
69
69
  return fixture;
70
70
  }
71
71
 
72
- describe('shipment.test.ts', { concurrency: false, sequential: true }, () => {
72
+ describe('shipment.test.ts', { concurrency: false }, () => {
73
73
  let releaseTestLock: (() => void) | undefined;
74
74
 
75
75
  before(async () => {
@@ -85,9 +85,11 @@ describe('shipment.test.ts', { concurrency: false, sequential: true }, () => {
85
85
  const fixture: IFixture = {};
86
86
  try {
87
87
  Object.assign(fixture, await createPaidOrder(randomUUID().slice(0, 12)));
88
- const actionPath = `/commerce/trade/order/${fixture.orderId}/ship`;
88
+ const actionPath = '/commerce/trade/order/:id/ship';
89
+ const params = { id: fixture.orderId };
89
90
  const [_, anonymousError] = await catchError(() =>
90
91
  app.bean.executor.performAction('post', actionPath, {
92
+ params,
91
93
  body: { carrier: 'Cabloy Express', trackingNumber: 'CAB-100' },
92
94
  innerAccess: false,
93
95
  }),
@@ -96,6 +98,7 @@ describe('shipment.test.ts', { concurrency: false, sequential: true }, () => {
96
98
  await app.bean.passport.signinMock(fixture.customerName as any);
97
99
  const [__, customerError] = await catchError(() =>
98
100
  app.bean.executor.performAction('post', actionPath, {
101
+ params,
99
102
  body: { carrier: 'Cabloy Express', trackingNumber: 'CAB-100' },
100
103
  innerAccess: false,
101
104
  }),
@@ -104,10 +107,12 @@ describe('shipment.test.ts', { concurrency: false, sequential: true }, () => {
104
107
  await app.bean.passport.signout();
105
108
  await app.bean.passport.signinMock();
106
109
  const first = await app.bean.executor.performAction('post', actionPath, {
110
+ params,
107
111
  body: { carrier: ' Cabloy Express ', trackingNumber: ' CAB-100 ' },
108
112
  innerAccess: false,
109
113
  });
110
114
  const replay = await app.bean.executor.performAction('post', actionPath, {
115
+ params,
111
116
  body: { carrier: 'Cabloy Express', trackingNumber: 'CAB-100' },
112
117
  innerAccess: false,
113
118
  });
@@ -168,7 +168,7 @@ async function assertPersistedRefundOutcome(
168
168
  assert.deepEqual(refundAudits.map(item => item.toRefundState).sort(), expected.refundAuditStates);
169
169
  }
170
170
 
171
- describe('shipmentRefundRace.test.ts', { concurrency: false, sequential: true }, () => {
171
+ describe('shipmentRefundRace.test.ts', { concurrency: false }, () => {
172
172
  let releaseTestLock: (() => void) | undefined;
173
173
 
174
174
  before(async () => {
@@ -321,7 +321,12 @@ describe('shipmentRefundRace.test.ts', { concurrency: false, sequential: true },
321
321
  const releaseShipment = Promise.withResolvers<void>();
322
322
  let shipmentPid: number | undefined;
323
323
  let ship: Promise<unknown> | undefined;
324
- let results: PromiseSettledResult<unknown>[] | undefined;
324
+ let results:
325
+ | [
326
+ PromiseSettledResult<Awaited<typeof approveRefund>>,
327
+ PromiseSettledResult<Awaited<typeof ship>>,
328
+ ]
329
+ | undefined;
325
330
  try {
326
331
  await waitForHolderStage(entered.promise, approveRefund, 'refund approval');
327
332
  assert.notEqual(holderPid, undefined);
@@ -352,7 +357,7 @@ describe('shipmentRefundRace.test.ts', { concurrency: false, sequential: true },
352
357
  } finally {
353
358
  releaseShipment.resolve();
354
359
  release.resolve();
355
- results = await Promise.allSettled([approveRefund, ship].filter(Boolean));
360
+ results = await Promise.allSettled([approveRefund, ship]);
356
361
  }
357
362
  const [approvalResult, shipmentResult] = results!;
358
363
  const message = JSON.stringify(results);
@@ -431,7 +436,12 @@ describe('shipmentRefundRace.test.ts', { concurrency: false, sequential: true },
431
436
  const releaseShipment = Promise.withResolvers<void>();
432
437
  let shipmentPid: number | undefined;
433
438
  let ship: Promise<unknown> | undefined;
434
- let results: PromiseSettledResult<unknown>[] | undefined;
439
+ let results:
440
+ | [
441
+ PromiseSettledResult<Awaited<typeof executeRefund>>,
442
+ PromiseSettledResult<Awaited<typeof ship>>,
443
+ ]
444
+ | undefined;
435
445
  try {
436
446
  await waitForHolderStage(entered.promise, executeRefund, 'refund execution');
437
447
  assert.notEqual(holderPid, undefined);
@@ -462,7 +472,7 @@ describe('shipmentRefundRace.test.ts', { concurrency: false, sequential: true },
462
472
  } finally {
463
473
  releaseShipment.resolve();
464
474
  release.resolve();
465
- results = await Promise.allSettled([executeRefund, ship].filter(Boolean));
475
+ results = await Promise.allSettled([executeRefund, ship]);
466
476
  }
467
477
  const [refundResult, shipmentResult] = results!;
468
478
  const message = JSON.stringify(results);
@@ -1,3 +1,4 @@
1
+ import type { TableIdentity } from 'table-identity';
1
2
  import type { DtoStockAdjust, EntityStockBalance } from 'vona-module-commerce-trade';
2
3
 
3
4
  import { catchError } from '@cabloy/utils';
@@ -145,12 +146,13 @@ describe('stockBalance.test.ts', { concurrency: false }, () => {
145
146
  let fixture: IStockFixture | undefined;
146
147
  await app.bean.passport.signinMock();
147
148
  try {
148
- fixture = await createSku(createTestId());
149
+ const createdFixture = await createSku(createTestId());
150
+ fixture = createdFixture;
149
151
  const balance = await adjustStock(
150
- stockAdjust(fixture.skuId, 2, `${fixture.skuId}-initial`),
152
+ stockAdjust(createdFixture.skuId, 2, `${createdFixture.skuId}-initial`),
151
153
  );
152
154
  const [_, err] = await catchError(() =>
153
- adjustStock(stockAdjust(fixture.skuId, -3, `${fixture.skuId}-reject`)),
155
+ adjustStock(stockAdjust(createdFixture.skuId, -3, `${createdFixture.skuId}-reject`)),
154
156
  );
155
157
  assert.equal(err?.code, 409);
156
158
  assert.match(err?.message ?? '', /negative/);
@@ -294,8 +296,8 @@ describe('stockBalance.test.ts', { concurrency: false }, () => {
294
296
  it('hides foreign stock balances and audits from operator read APIs', async () => {
295
297
  const suffix = createTestId();
296
298
  let fixture: IStockFixture | undefined;
297
- let balanceId!: number;
298
- let auditId!: number;
299
+ let balanceId!: TableIdentity;
300
+ let auditId!: TableIdentity;
299
301
  const reason = `stock read isolation ${suffix}`;
300
302
  const correlationId = `stock-read-isolation-${suffix}`;
301
303
  try {
@@ -57,7 +57,8 @@ describe('account.test.ts', { concurrency: false }, () => {
57
57
  await app.bean.executor.mockCtx(async () => {
58
58
  const user = await app.bean.user.findOneById(fixture!.userId);
59
59
  assert.equal(user?.name, `${fixture!.name}-updated`);
60
- assert.equal((user as Record<string, unknown>)?.ignored, undefined);
60
+ assert.ok(user);
61
+ assert.equal('ignored' in user, false);
61
62
  });
62
63
 
63
64
  await app.bean.executor.mockCtx(async () => {
@@ -117,7 +118,6 @@ describe('account.test.ts', { concurrency: false }, () => {
117
118
  clientName: policy.clientName,
118
119
  imageScene: policy.imageScene,
119
120
  meta: policy.meta,
120
- public: policy.public,
121
121
  },
122
122
  );
123
123
  imageId = uploaded.id;
@@ -7,7 +7,7 @@ const activationConsumePath = '/home/user/account/activation/consume';
7
7
  const activationConsumerUrl = 'https://account.example.test/home/user/activation';
8
8
  const activationConsumerUrlAdmin = 'https://account.example.test/admin/home/user/activation';
9
9
 
10
- describe('accountActivation.test.ts', { concurrency: false, sequential: true }, () => {
10
+ describe('accountActivation.test.ts', { concurrency: false }, () => {
11
11
  const releases: Array<() => void> = [];
12
12
  let restoreConfig: (() => void) | undefined;
13
13
 
@@ -211,6 +211,7 @@ async function configureActivation() {
211
211
  return await app.bean.executor.mockCtx(async () => {
212
212
  const options =
213
213
  app.bean.onion.middlewareSystem.getOnionSlice('a-security:cors').beanOptions.options;
214
+ assert.ok(options);
214
215
  const whiteListPrevious = options.whiteList;
215
216
  options.whiteList = ['https://account.example.test'];
216
217
  return () => {
@@ -1,3 +1,11 @@
1
+ import type {
2
+ OpenAPIObject,
3
+ OperationObject,
4
+ ReferenceObject,
5
+ SchemaObject,
6
+ } from 'openapi3-ts/oas31';
7
+ import type { ISchemaObjectExtensionField } from 'vona-module-a-openapi';
8
+
1
9
  import assert from 'node:assert';
2
10
  import { describe, it } from 'node:test';
3
11
  import { app } from 'vona-mock';
@@ -59,44 +67,23 @@ describe('accountOpenapi.test.ts', () => {
59
67
  assert.deepEqual(operation.tags, ['HomeUserAccount']);
60
68
  assert.equal(!operation.security, expectation.isPublic);
61
69
  if (['issuePasswordSetLink', 'requestPasswordReset'].includes(expectation.action)) {
62
- assert.ok(operation.requestBody);
63
- const schema = (
64
- operation.requestBody as { content?: { 'application/json'?: { schema?: any } } }
65
- ).content?.['application/json']?.schema;
66
- assert.ok(schema);
67
- const schemaRef = (schema as { $ref?: string }).$ref;
68
- assert.ok(schemaRef);
69
- const schemaName = schemaRef.replace('#/components/schemas/', '');
70
- const schemaResolved = doc.components?.schemas?.[schemaName];
71
- assert.ok(schemaResolved);
70
+ const schemaResolved = requestSchema(doc, operation);
72
71
  assert.ok(schemaResolved.properties?.consumerUrl);
73
72
  assert.ok(schemaResolved.required?.includes('consumerUrl'));
74
73
  if (expectation.action === 'issuePasswordSetLink') {
75
- assert.equal(schemaResolved.properties?.email?.format, 'email');
74
+ assert.equal(inlineSchema(schemaResolved.properties?.email).format, 'email');
76
75
  assert.ok(schemaResolved.required?.includes('email'));
77
76
  }
78
77
  }
79
78
  if (expectation.action === 'consumeActivation') {
80
- const schema = (
81
- operation.requestBody as { content?: { 'application/json'?: { schema?: any } } }
82
- ).content?.['application/json']?.schema;
83
- const schemaRef = (schema as { $ref?: string })?.$ref;
84
- assert.ok(schemaRef);
85
- const schemaName = schemaRef.replace('#/components/schemas/', '');
86
- const schemaResolved = doc.components?.schemas?.[schemaName];
79
+ const schemaResolved = requestSchema(doc, operation);
87
80
  assert.deepEqual(Object.keys(schemaResolved?.properties ?? {}), ['token']);
88
81
  assert.deepEqual(schemaResolved?.required, ['token']);
89
- assert.equal(schemaResolved?.properties?.token?.minLength, 32);
90
- assert.equal(schemaResolved?.properties?.token?.maxLength, 255);
82
+ assert.equal(inlineSchema(schemaResolved.properties?.token).minLength, 32);
83
+ assert.equal(inlineSchema(schemaResolved.properties?.token).maxLength, 255);
91
84
  }
92
85
  if (expectation.action === 'changePassword') {
93
- const schema = (
94
- operation.requestBody as { content?: { 'application/json'?: { schema?: any } } }
95
- ).content?.['application/json']?.schema;
96
- const schemaRef = (schema as { $ref?: string })?.$ref;
97
- assert.ok(schemaRef);
98
- const schemaName = schemaRef.replace('#/components/schemas/', '');
99
- const schemaResolved = doc.components?.schemas?.[schemaName];
86
+ const schemaResolved = requestSchema(doc, operation);
100
87
  assert.deepEqual(Object.keys(schemaResolved?.properties ?? {}), [
101
88
  'currentPassword',
102
89
  'newPassword',
@@ -112,23 +99,19 @@ describe('accountOpenapi.test.ts', () => {
112
99
  ['newPassword', 'home-user::NewPassword'],
113
100
  ['passwordConfirm', 'home-user::PasswordConfirm'],
114
101
  ]) {
115
- const property = schemaResolved?.properties?.[field];
102
+ const property = inlineSchema(schemaResolved.properties?.[field]);
116
103
  assert.equal(property?.title?.toString(), title);
117
104
  assert.equal(property?.minLength, 6);
118
105
  assert.equal(property?.maxLength, 20);
119
106
  assert.equal(property?.rest?.form?.render, 'basic-input:formFieldInput');
120
- assert.deepEqual(property?.rest?.form?.options, { type: 'password' });
107
+ const form = property.rest?.form;
108
+ assert.ok(form && 'options' in form);
109
+ assert.deepEqual(form.options, { type: 'password' });
121
110
  assert.equal(property?.rest?.form?.required, true);
122
111
  }
123
112
  }
124
113
  if (expectation.action === 'updateProfile') {
125
- const schema = (
126
- operation.requestBody as { content?: { 'application/json'?: { schema?: any } } }
127
- ).content?.['application/json']?.schema;
128
- const schemaRef = (schema as { $ref?: string })?.$ref;
129
- assert.ok(schemaRef);
130
- const schemaName = schemaRef.replace('#/components/schemas/', '');
131
- const schemaResolved = doc.components?.schemas?.[schemaName];
114
+ const schemaResolved = requestSchema(doc, operation);
132
115
  assert.deepEqual(Object.keys(schemaResolved?.properties ?? {}), [
133
116
  'name',
134
117
  'avatar',
@@ -142,13 +125,19 @@ describe('accountOpenapi.test.ts', () => {
142
125
  ['locale', 'home-user::AccountLocale'],
143
126
  ['tz', 'home-user::AccountTimezone'],
144
127
  ]) {
145
- assert.equal(schemaResolved?.properties?.[field]?.title?.toString(), title);
128
+ assert.equal(inlineSchema(schemaResolved.properties?.[field]).title?.toString(), title);
146
129
  }
147
- assert.equal(schemaResolved?.properties?.name?.minLength, 1);
148
- assert.equal(schemaResolved?.properties?.name?.maxLength, 100);
149
- assert.deepEqual(schemaResolved?.properties?.avatar?.type, ['string', 'null']);
150
- assert.deepEqual(schemaResolved?.properties?.locale?.type, ['string', 'null']);
151
- assert.deepEqual(schemaResolved?.properties?.tz?.type, ['string', 'null']);
130
+ assert.equal(inlineSchema(schemaResolved.properties?.name).minLength, 1);
131
+ assert.equal(inlineSchema(schemaResolved.properties?.name).maxLength, 100);
132
+ assert.deepEqual(inlineSchema(schemaResolved.properties?.avatar).type, [
133
+ 'string',
134
+ 'null',
135
+ ]);
136
+ assert.deepEqual(inlineSchema(schemaResolved.properties?.locale).type, [
137
+ 'string',
138
+ 'null',
139
+ ]);
140
+ assert.deepEqual(inlineSchema(schemaResolved.properties?.tz).type, ['string', 'null']);
152
141
  assert.equal(schemaResolved?.properties?.avatarImageId, undefined);
153
142
  assert.equal(schemaResolved?.properties?.avatarFileId, undefined);
154
143
  }
@@ -156,3 +145,25 @@ describe('accountOpenapi.test.ts', () => {
156
145
  });
157
146
  });
158
147
  });
148
+
149
+ function inlineSchema(
150
+ schema: ReferenceObject | SchemaObject | undefined,
151
+ ): ISchemaObjectExtensionField {
152
+ assert.ok(schema);
153
+ assert.ok(!('$ref' in schema));
154
+ return schema;
155
+ }
156
+
157
+ function requestSchema(
158
+ doc: OpenAPIObject,
159
+ operation: OperationObject,
160
+ ): ISchemaObjectExtensionField {
161
+ const body = operation.requestBody;
162
+ assert.ok(body);
163
+ assert.ok(!('$ref' in body));
164
+ const schema = body.content['application/json']?.schema;
165
+ assert.ok(schema && '$ref' in schema);
166
+ assert.ok(typeof schema.$ref === 'string');
167
+ const schemaName = schema.$ref.replace('#/components/schemas/', '');
168
+ return inlineSchema(doc.components?.schemas?.[schemaName]);
169
+ }
@@ -521,6 +521,7 @@ async function configurePasswordReset(
521
521
  return await app.bean.executor.mockCtx(async () => {
522
522
  const options =
523
523
  app.bean.onion.middlewareSystem.getOnionSlice('a-security:cors').beanOptions.options;
524
+ assert.ok(options);
524
525
  const whiteListPrevious = options.whiteList;
525
526
  options.whiteList = Array.isArray(whiteList) ? [...whiteList] : whiteList;
526
527
  return () => {