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
@@ -29,7 +29,6 @@ publicHoistPattern:
29
29
  - '*cabloy*'
30
30
  - '*@cabloy*'
31
31
  - '*why-is-node-running*'
32
- - '*ts-node*'
33
32
  - zod
34
33
 
35
34
  overrides:
@@ -68,6 +68,7 @@ describe('product.test.ts', { concurrency: false }, () => {
68
68
  title: `__Tom-${suffix}__`,
69
69
  published: false,
70
70
  description: 'This is a test',
71
+ productContentForm: undefined,
71
72
  };
72
73
  const productId = await app.bean.executor.performAction(
73
74
  'post',
@@ -82,6 +83,7 @@ describe('product.test.ts', { concurrency: false }, () => {
82
83
  title: `__TomNew-${suffix}__`,
83
84
  published: true,
84
85
  description: 'This is a test',
86
+ productContentForm: undefined,
85
87
  };
86
88
  assert.equal(!!productId, true);
87
89
  const selectRes: DtoProductSelectRes = await app.bean.executor.performAction(
@@ -43,6 +43,7 @@ describe('sku.test.ts', { concurrency: false }, () => {
43
43
  categoryId,
44
44
  title: '__SkuProduct__',
45
45
  published: true,
46
+ productContentForm: undefined,
46
47
  };
47
48
  const productId = await app.bean.executor.performAction(
48
49
  'post',
@@ -119,18 +119,17 @@ describe('skuUniqueness.test.ts', { concurrency: false }, () => {
119
119
  await createFixture(fixture, suffix, 2);
120
120
  const sourceIds = await runInContext(async () => {
121
121
  const catalog = app.scope('commerce-catalog');
122
- const skus = [];
122
+ const skuIds: TableIdentity[] = [];
123
123
  for (let index = 0; index < 2; index++) {
124
- skus.push(
125
- await catalog.service.sku.create({
126
- productId: fixture.productIds[index]!,
127
- code: sourceCodes[index]!,
128
- priceCents: 100,
129
- lifecycle: 'draft',
130
- }),
131
- );
124
+ const sku = await catalog.service.sku.create({
125
+ productId: fixture.productIds[index]!,
126
+ code: sourceCodes[index]!,
127
+ priceCents: 100,
128
+ lifecycle: 'draft',
129
+ });
130
+ skuIds.push(sku.id);
132
131
  }
133
- return skus.map(sku => sku.id);
132
+ return skuIds;
134
133
  });
135
134
  const results = await Promise.allSettled(
136
135
  sourceIds.map((skuId, index) =>
@@ -1,3 +1,7 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+ import type { IPerformActionOptions } from 'vona-module-a-executor';
3
+ import type { IApiPathRecordMethodMap } from 'vona-module-a-web';
4
+
1
5
  import { catchError } from '@cabloy/utils';
2
6
  import assert from 'node:assert';
3
7
  import { randomUUID } from 'node:crypto';
@@ -35,7 +39,10 @@ function createAddress(suffix: string): DtoAddressMineCreate {
35
39
  };
36
40
  }
37
41
 
38
- async function performAs(accessToken: string, method: string, path: string, options = {}) {
42
+ async function performAs<
43
+ METHOD extends keyof IApiPathRecordMethodMap,
44
+ PATH extends keyof IApiPathRecordMethodMap[METHOD],
45
+ >(accessToken: string, method: METHOD, path: PATH, options: IPerformActionOptions = {}) {
39
46
  return await app.bean.executor.newCtxIsolate(async () => {
40
47
  return await app.bean.executor.performAction(method, path, {
41
48
  ...options,
@@ -92,18 +99,37 @@ describe('addressOwnership.test.ts', () => {
92
99
 
93
100
  it('denies anonymous Admin and Web Address actions', async () => {
94
101
  await app.bean.executor.mockCtx(async () => {
95
- for (const [method, path, options] of [
96
- ['get', actionPath, {}],
97
- ['get', `${actionPath}/:id`, { params: { id: 999999 } }],
98
- ['get', minePath, {}],
99
- ['get', viewMinePath, { params: { id: 999999 } }],
100
- ['post', createMinePath, { body: createAddress('anonymous') }],
101
- ['patch', updateMinePath, { params: { id: 999999 }, body: createAddress('anonymous') }],
102
- ['delete', deleteMinePath, { params: { id: 999999 } }],
103
- ] as const) {
104
- const [_, err] = await catchError(() =>
105
- app.bean.executor.performAction(method, path, { ...options, innerAccess: false }),
106
- );
102
+ for (const action of [
103
+ () => app.bean.executor.performAction('get', actionPath, { innerAccess: false }),
104
+ () =>
105
+ app.bean.executor.performAction('get', `${actionPath}/:id`, {
106
+ params: { id: 999999 },
107
+ innerAccess: false,
108
+ }),
109
+ () => app.bean.executor.performAction('get', minePath, { innerAccess: false }),
110
+ () =>
111
+ app.bean.executor.performAction('get', viewMinePath, {
112
+ params: { id: 999999 },
113
+ innerAccess: false,
114
+ }),
115
+ () =>
116
+ app.bean.executor.performAction('post', createMinePath, {
117
+ body: createAddress('anonymous'),
118
+ innerAccess: false,
119
+ }),
120
+ () =>
121
+ app.bean.executor.performAction('patch', updateMinePath, {
122
+ params: { id: 999999 },
123
+ body: createAddress('anonymous'),
124
+ innerAccess: false,
125
+ }),
126
+ () =>
127
+ app.bean.executor.performAction('delete', deleteMinePath, {
128
+ params: { id: 999999 },
129
+ innerAccess: false,
130
+ }),
131
+ ]) {
132
+ const [_, err] = await catchError(action);
107
133
  assert.equal(err?.code, 401);
108
134
  }
109
135
  });
@@ -111,7 +137,7 @@ describe('addressOwnership.test.ts', () => {
111
137
 
112
138
  it('derives Web ownership from Passport and treats foreign rows as absent', async () => {
113
139
  await app.bean.executor.mockCtx(async () => {
114
- let addressId: number | undefined;
140
+ let addressId: TableIdentity | undefined;
115
141
  try {
116
142
  const suffix = createTestId();
117
143
  const customerA = await registerAndSignin(`address-a-${suffix}`);
@@ -121,6 +147,7 @@ describe('addressOwnership.test.ts', () => {
121
147
  body: { ...addressData, userId: customerB.user.id, iid: 999999 } as any,
122
148
  });
123
149
 
150
+ assert.ok(addressId !== undefined);
124
151
  const persisted = await app.scope('commerce-member').model.address.getById(addressId);
125
152
  assert.equal(String(persisted?.userId), String(customerA.user.id));
126
153
  assert.equal(persisted?.iid, app.ctx.instance.id);
@@ -201,7 +228,7 @@ describe('addressOwnership.test.ts', () => {
201
228
 
202
229
  it('exposes active-instance Address inspection only to systemAdmin', async () => {
203
230
  await app.bean.executor.mockCtx(async () => {
204
- let addressId: number | undefined;
231
+ let addressId: TableIdentity | undefined;
205
232
  try {
206
233
  const suffix = createTestId();
207
234
  const customer = await registerAndSignin(`address-admin-${suffix}`);
@@ -238,7 +265,7 @@ describe('addressOwnership.test.ts', () => {
238
265
  });
239
266
 
240
267
  it('treats another instance Address as absent on Web actions', async () => {
241
- let addressId: number | undefined;
268
+ let addressId: TableIdentity | undefined;
242
269
  let customerDefault!: Awaited<ReturnType<typeof registerAndSignin>>;
243
270
  const addressDefault = createAddress('default');
244
271
  try {
@@ -1,3 +1,6 @@
1
+ import type { IPerformActionOptions } from 'vona-module-a-executor';
2
+ import type { IApiPathRecordMethodMap } from 'vona-module-a-web';
3
+
1
4
  import assert from 'node:assert';
2
5
  import { randomUUID } from 'node:crypto';
3
6
  import { describe, it } from 'node:test';
@@ -51,7 +54,10 @@ function createAddress(suffix: string): DtoAddressMineCreate {
51
54
  };
52
55
  }
53
56
 
54
- async function performAs(accessToken: string, method: string, path: string, options = {}) {
57
+ async function performAs<
58
+ METHOD extends keyof IApiPathRecordMethodMap,
59
+ PATH extends keyof IApiPathRecordMethodMap[METHOD],
60
+ >(accessToken: string, method: METHOD, path: PATH, options: IPerformActionOptions = {}) {
55
61
  return await app.bean.executor.newCtxIsolate(async () => {
56
62
  return await app.bean.executor.performAction(method, path, {
57
63
  ...options,
@@ -16,6 +16,7 @@ import {
16
16
  resolveVonaLocalPaths,
17
17
  } from 'vona';
18
18
  import { app } from 'vona-mock';
19
+ import { MetaVersion } from 'vona-module-a-index';
19
20
  import { ServiceDatabaseAsyncLocalStorage, ServiceDatabaseClient } from 'vona-module-a-orm';
20
21
 
21
22
  const expectedIndexes = {
@@ -198,7 +199,7 @@ describe('paymentIndexes.test.ts', { concurrency: false }, () => {
198
199
  assert.ok(indexesBefore.some(item => item.name === retainedIndex));
199
200
 
200
201
  const update = async () => {
201
- await app.bean._getBean('a-index.meta.version').update({ version: -1 });
202
+ await app.bean._getBean(MetaVersion).update({ version: -1 });
202
203
  };
203
204
  for (let pass = 0; pass < 2; pass++) {
204
205
  await update();
@@ -1,3 +1,5 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+
1
3
  import { catchError } from '@cabloy/utils';
2
4
  import assert from 'node:assert';
3
5
  import { randomUUID } from 'node:crypto';
@@ -13,7 +15,7 @@ interface IFixture {
13
15
  userId?: number;
14
16
  templateIds?: number[];
15
17
  grantIds?: number[];
16
- userIds?: number[];
18
+ userIds?: TableIdentity[];
17
19
  }
18
20
 
19
21
  function createTestId() {
@@ -36,7 +38,7 @@ async function cleanup(fixture: IFixture) {
36
38
  }
37
39
  for (const userId of userIds) {
38
40
  await app.scope('home-user').model.roleUser.delete({ userId });
39
- await app.bean.user.removeById(userId);
41
+ if (userId !== undefined) await app.bean.user.removeById(userId);
40
42
  }
41
43
  }
42
44
 
@@ -199,10 +201,15 @@ describe('couponReservation.test.ts', { concurrency: false }, () => {
199
201
  reason: 'test reserve',
200
202
  });
201
203
  assert.equal(replay.couponGrant.id, grant.id);
204
+ const wrongCustomer = await app.bean.user.register(
205
+ { name: `coupon-wrong-customer-${suffix}` },
206
+ true,
207
+ );
208
+ (fixture.userIds ??= []).push(wrongCustomer.id);
202
209
  const [, wrongCustomerError] = await catchError(() =>
203
210
  coupon.reserve({
204
211
  couponGrantId: grant.id,
205
- userId: customer.id + 10_000,
212
+ userId: wrongCustomer.id,
206
213
  orderId: 100_001,
207
214
  eligibleSubtotalCents: 1_200,
208
215
  currency: 'USD',
@@ -434,16 +441,18 @@ describe('couponReservation.test.ts', { concurrency: false }, () => {
434
441
  customer.id as number,
435
442
  `reserve-exhausted-${suffix}`,
436
443
  );
437
- for (const [grant, label] of [
438
- [disabledGrant, 'disabled'],
439
- [expiredGrant, 'expired'],
440
- [usageExhaustedGrant, 'usage-exhausted'],
441
- ] as const) {
444
+ for (const [index, [grant, label]] of (
445
+ [
446
+ [disabledGrant, 'disabled'],
447
+ [expiredGrant, 'expired'],
448
+ [usageExhaustedGrant, 'usage-exhausted'],
449
+ ] as const
450
+ ).entries()) {
442
451
  const [_, error] = await catchError(() =>
443
452
  coupon.reserve({
444
453
  couponGrantId: grant.id,
445
454
  userId: customer.id,
446
- orderId: 200_000 + grant.id,
455
+ orderId: 200_000 + index,
447
456
  eligibleSubtotalCents: 1_000,
448
457
  currency: 'USD',
449
458
  correlationId: `reserve-${label}-${suffix}`,
@@ -1,3 +1,5 @@
1
+ import type { IPerformActionOptions } from 'vona-module-a-executor';
2
+ import type { IApiPathRecordMethodMap } from 'vona-module-a-web';
1
3
  import type { DtoCartAddItem, DtoCartUpdateItem } from 'vona-module-commerce-trade';
2
4
 
3
5
  import { catchError } from '@cabloy/utils';
@@ -22,7 +24,10 @@ function cartItem(skuId: number, quantity = 1): DtoCartAddItem {
22
24
  return { skuId, quantity };
23
25
  }
24
26
 
25
- async function performAs(accessToken: string, method: string, path: string, options = {}) {
27
+ async function performAs<
28
+ METHOD extends keyof IApiPathRecordMethodMap,
29
+ PATH extends keyof IApiPathRecordMethodMap[METHOD],
30
+ >(accessToken: string, method: METHOD, path: PATH, options: IPerformActionOptions = {}) {
26
31
  return await app.bean.executor.newCtxIsolate(async () => {
27
32
  return await app.bean.executor.performAction(method, path, {
28
33
  ...options,
@@ -106,16 +111,28 @@ describe('cartOwnership.test.ts', { concurrency: false }, () => {
106
111
 
107
112
  it('denies anonymous cart actions', async () => {
108
113
  await app.bean.executor.mockCtx(async () => {
109
- for (const [method, path, options] of [
110
- ['get', actionPath, {}],
111
- ['post', `${actionPath}/items`, { body: cartItem(1) }],
112
- ['patch', `${actionPath}/items/:id`, { params: { id: 999999 }, body: { quantity: 1 } }],
113
- ['delete', `${actionPath}/items/:id`, { params: { id: 999999 } }],
114
- ['delete', `${actionPath}/items`, {}],
115
- ] as const) {
116
- const [_, err] = await catchError(() =>
117
- app.bean.executor.performAction(method, path, { ...options, innerAccess: false }),
118
- );
114
+ for (const action of [
115
+ () => app.bean.executor.performAction('get', actionPath, { innerAccess: false }),
116
+ () =>
117
+ app.bean.executor.performAction('post', `${actionPath}/items`, {
118
+ body: cartItem(1),
119
+ innerAccess: false,
120
+ }),
121
+ () =>
122
+ app.bean.executor.performAction('patch', `${actionPath}/items/:id`, {
123
+ params: { id: 999999 },
124
+ body: { quantity: 1 },
125
+ innerAccess: false,
126
+ }),
127
+ () =>
128
+ app.bean.executor.performAction('delete', `${actionPath}/items/:id`, {
129
+ params: { id: 999999 },
130
+ innerAccess: false,
131
+ }),
132
+ () =>
133
+ app.bean.executor.performAction('delete', `${actionPath}/items`, { innerAccess: false }),
134
+ ]) {
135
+ const [_, err] = await catchError(action);
119
136
  assert.equal(err?.code, 401);
120
137
  }
121
138
  });
@@ -1,3 +1,5 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+
1
3
  import { catchError } from '@cabloy/utils';
2
4
  import assert from 'node:assert';
3
5
  import { randomUUID } from 'node:crypto';
@@ -5,22 +7,22 @@ import { after, before, describe, it } from 'node:test';
5
7
  import { acquireTestLock, app } from 'vona-mock';
6
8
 
7
9
  interface IFixture {
8
- categoryId?: number;
9
- productId?: number;
10
- skuId?: number;
11
- balanceId?: number;
12
- cartId?: number;
13
- cartItemId?: number;
14
- addressId?: number;
15
- orderId?: number;
16
- orderLineId?: number;
17
- paymentAttemptId?: number;
18
- orderAuditId?: number;
19
- reservationId?: number;
20
- stockAuditIds?: number[];
21
- couponTemplateId?: number;
22
- couponGrantId?: number;
23
- userId?: number;
10
+ categoryId?: TableIdentity;
11
+ productId?: TableIdentity;
12
+ skuId?: TableIdentity;
13
+ balanceId?: TableIdentity;
14
+ cartId?: TableIdentity;
15
+ cartItemId?: TableIdentity;
16
+ addressId?: TableIdentity;
17
+ orderId?: TableIdentity;
18
+ orderLineId?: TableIdentity;
19
+ paymentAttemptId?: TableIdentity;
20
+ orderAuditId?: TableIdentity;
21
+ reservationId?: TableIdentity;
22
+ stockAuditIds?: TableIdentity[];
23
+ couponTemplateId?: TableIdentity;
24
+ couponGrantId?: TableIdentity;
25
+ userId?: TableIdentity;
24
26
  }
25
27
 
26
28
  async function cleanup(fixture: IFixture) {
@@ -113,6 +115,7 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
113
115
  lifecycle: 'active',
114
116
  },
115
117
  });
118
+ assert.ok(fixture.skuId !== undefined);
116
119
  fixture.balanceId = (
117
120
  await app.scope('commerce-trade').service.stockBalance.adjustStock({
118
121
  skuId: fixture.skuId,
@@ -246,6 +249,7 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
246
249
  lifecycle: 'active',
247
250
  },
248
251
  });
252
+ assert.ok(local.skuId !== undefined);
249
253
  local.balanceId = (
250
254
  await app.scope('commerce-trade').service.stockBalance.adjustStock({
251
255
  skuId: local.skuId,
@@ -339,6 +343,8 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
339
343
  );
340
344
 
341
345
  await app.bean.executor.mockCtx(async () => {
346
+ const cartItemId = local.cartItemId;
347
+ assert.ok(cartItemId !== undefined);
342
348
  const assertNoPartialWrites = async (correlationId: string) => {
343
349
  assert.equal(
344
350
  await app.scope('commerce-trade').model.order.get({ correlationId }),
@@ -349,7 +355,7 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
349
355
  undefined,
350
356
  );
351
357
  assert.equal(
352
- (await app.scope('commerce-trade').model.cartItem.getById(local.cartItemId))?.quantity,
358
+ (await app.scope('commerce-trade').model.cartItem.getById(cartItemId))?.quantity,
353
359
  1,
354
360
  );
355
361
  const balance = await app
@@ -435,6 +441,7 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
435
441
  lifecycle: 'active',
436
442
  })
437
443
  ).id as number;
444
+ assert.ok(fixture.skuId !== undefined);
438
445
  fixture.balanceId = (
439
446
  await app.scope('commerce-trade').service.stockBalance.adjustStock({
440
447
  skuId: fixture.skuId,
@@ -521,6 +528,8 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
521
528
  )?.id as number;
522
529
  assert.equal(result.payableTotalCents, 799);
523
530
 
531
+ assert.ok(fixture.productId !== undefined);
532
+ assert.ok(fixture.addressId !== undefined);
524
533
  await app.scope('commerce-catalog').model.product.updateById(fixture.productId, {
525
534
  title: 'Changed checkout product',
526
535
  published: false,
@@ -609,6 +618,7 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
609
618
  lifecycle: 'active',
610
619
  },
611
620
  });
621
+ assert.ok(fixture.skuId !== undefined);
612
622
  const balance = await app.scope('commerce-trade').service.stockBalance.adjustStock({
613
623
  skuId: fixture.skuId,
614
624
  delta: 1,
@@ -644,6 +654,7 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
644
654
  });
645
655
  fixture.cartItemId = cartItem.id as number;
646
656
 
657
+ assert.ok(fixture.addressId !== undefined);
647
658
  const result = await app.scope('commerce-trade').service.order.checkout({
648
659
  addressId: fixture.addressId,
649
660
  correlationId: `checkout-${suffix}`,
@@ -691,10 +702,10 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
691
702
 
692
703
  const suffix = randomUUID().slice(0, 12);
693
704
  const fixture: {
694
- categoryId?: number;
695
- productId?: number;
696
- skuId?: number;
697
- balanceId?: number;
705
+ categoryId?: TableIdentity;
706
+ productId?: TableIdentity;
707
+ skuId?: TableIdentity;
708
+ balanceId?: TableIdentity;
698
709
  customers: Array<{
699
710
  name: string;
700
711
  userId: number;
@@ -776,6 +787,7 @@ describe('checkoutReservation.test.ts', { concurrency: false }, () => {
776
787
  lifecycle: 'active',
777
788
  },
778
789
  });
790
+ assert.ok(fixture.skuId !== undefined);
779
791
  const balance = await trade.service.stockBalance.adjustStock({
780
792
  skuId: fixture.skuId,
781
793
  delta: 1,
@@ -152,7 +152,7 @@ async function assertRollback(fixture: IFixture) {
152
152
  );
153
153
  }
154
154
 
155
- describe('checkoutTransaction.test.ts', { concurrency: false, sequential: true }, () => {
155
+ describe('checkoutTransaction.test.ts', { concurrency: false }, () => {
156
156
  let releaseTestLock: (() => void) | undefined;
157
157
 
158
158
  before(async () => {
@@ -179,13 +179,14 @@ describe('checkoutTransaction.test.ts', { concurrency: false, sequential: true }
179
179
  await app.bean.passport.signinMock(customerName as any);
180
180
  let fixture: IFixture | undefined;
181
181
  try {
182
- fixture = await createFixture(suffix);
182
+ const createdFixture = await createFixture(suffix);
183
+ fixture = createdFixture;
183
184
  const [_, error] = await catchError(() =>
184
185
  app.scope('commerce-trade').service.order.checkoutForTest(
185
186
  {
186
- addressId: fixture.addressId,
187
- couponGrantId: fixture.grantId,
188
- correlationId: fixture.correlationId,
187
+ addressId: createdFixture.addressId,
188
+ couponGrantId: createdFixture.grantId,
189
+ correlationId: createdFixture.correlationId,
189
190
  },
190
191
  async currentStage => {
191
192
  if (currentStage === stage) throw new Error(`checkout test failure: ${stage}`);
@@ -1,3 +1,5 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+
1
3
  import { catchError } from '@cabloy/utils';
2
4
  import assert from 'node:assert';
3
5
  import { randomUUID } from 'node:crypto';
@@ -5,16 +7,16 @@ import { after, before, describe, it } from 'node:test';
5
7
  import { acquireTestLock, app } from 'vona-mock';
6
8
 
7
9
  interface IFixture {
8
- categoryId?: number;
9
- productId?: number;
10
- skuId?: number;
11
- addressId?: number;
12
- cartId?: number;
13
- orderId?: number;
14
- paymentAttemptId?: number;
15
- couponTemplateId?: number;
16
- couponGrantId?: number;
17
- userId?: number;
10
+ categoryId?: TableIdentity;
11
+ productId?: TableIdentity;
12
+ skuId?: TableIdentity;
13
+ addressId?: TableIdentity;
14
+ cartId?: TableIdentity;
15
+ orderId?: TableIdentity;
16
+ paymentAttemptId?: TableIdentity;
17
+ couponTemplateId?: TableIdentity;
18
+ couponGrantId?: TableIdentity;
19
+ userId?: TableIdentity;
18
20
  }
19
21
 
20
22
  async function cleanup(fixture: IFixture) {
@@ -171,15 +173,21 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
171
173
  },
172
174
  );
173
175
 
176
+ const productId = fixture.productId;
177
+ const addressId = fixture.addressId;
178
+ const skuId = fixture.skuId;
179
+ assert.ok(productId !== undefined);
180
+ assert.ok(addressId !== undefined);
181
+ assert.ok(skuId !== undefined);
174
182
  const created = await app.scope('commerce-trade').service.order.createSnapshot({
175
- addressId: fixture.addressId,
183
+ addressId,
176
184
  couponGrantId: fixture.couponGrantId,
177
185
  correlationId: `snapshot-order-${suffix}`,
178
186
  lines: [{ skuId: fixture.skuId, quantity: 2 }],
179
187
  });
180
188
  fixture.orderId = created.order.id as number;
181
189
  const replay = await app.scope('commerce-trade').service.order.createSnapshot({
182
- addressId: fixture.addressId,
190
+ addressId,
183
191
  couponGrantId: fixture.couponGrantId,
184
192
  correlationId: `snapshot-order-${suffix}`,
185
193
  lines: [{ skuId: fixture.skuId, quantity: 2 }],
@@ -188,9 +196,9 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
188
196
  assert.equal(replay.lines[0].id, created.lines[0].id);
189
197
  const [_, conflict] = await catchError(() =>
190
198
  app.scope('commerce-trade').service.order.createSnapshot({
191
- addressId: fixture.addressId,
199
+ addressId,
192
200
  correlationId: `snapshot-order-${suffix}`,
193
- lines: [{ skuId: fixture.skuId, quantity: 1 }],
201
+ lines: [{ skuId, quantity: 1 }],
194
202
  }),
195
203
  );
196
204
  assert.equal(conflict?.code, 409);
@@ -203,7 +211,7 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
203
211
  { name: 'Color', value: 'Black' },
204
212
  ]);
205
213
 
206
- await app.scope('commerce-catalog').model.product.updateById(fixture.productId, {
214
+ await app.scope('commerce-catalog').model.product.updateById(productId, {
207
215
  title: 'Changed product title',
208
216
  published: false,
209
217
  });
@@ -213,7 +221,7 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
213
221
  attributes: [{ name: 'Color', value: 'Blue' }],
214
222
  lifecycle: 'inactive',
215
223
  });
216
- await app.scope('commerce-member').model.address.updateById(fixture.addressId, {
224
+ await app.scope('commerce-member').model.address.updateById(addressId, {
217
225
  city: 'Oakland',
218
226
  addressLine1: 'Changed address',
219
227
  });
@@ -337,8 +345,12 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
337
345
  skuId: fixture.skuId,
338
346
  quantity: 1,
339
347
  });
348
+ const productId = fixture.productId;
349
+ const addressId = fixture.addressId;
350
+ assert.ok(productId !== undefined);
351
+ assert.ok(addressId !== undefined);
340
352
  const checkout = await app.scope('commerce-trade').service.order.checkout({
341
- addressId: fixture.addressId,
353
+ addressId,
342
354
  couponGrantId: fixture.couponGrantId,
343
355
  correlationId: `refund-snapshot-checkout-${suffix}`,
344
356
  });
@@ -351,7 +363,7 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
351
363
  idempotencyKey: `refund-snapshot-payment-${suffix}`,
352
364
  });
353
365
 
354
- await app.scope('commerce-catalog').model.product.updateById(fixture.productId, {
366
+ await app.scope('commerce-catalog').model.product.updateById(productId, {
355
367
  title: 'Changed refund product title',
356
368
  published: false,
357
369
  });
@@ -361,7 +373,7 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
361
373
  attributes: [{ name: 'Color', value: 'Blue' }],
362
374
  lifecycle: 'inactive',
363
375
  });
364
- await app.scope('commerce-member').model.address.updateById(fixture.addressId, {
376
+ await app.scope('commerce-member').model.address.updateById(addressId, {
365
377
  city: 'Oakland',
366
378
  addressLine1: 'Changed refund address',
367
379
  });
@@ -417,13 +429,9 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
417
429
  lineTotalCents: 1299,
418
430
  },
419
431
  );
420
- const liveProduct = await app
421
- .scope('commerce-catalog')
422
- .model.product.getById(fixture.productId);
432
+ const liveProduct = await app.scope('commerce-catalog').model.product.getById(productId);
423
433
  const liveSku = await app.scope('commerce-catalog').model.sku.getById(fixture.skuId);
424
- const liveAddress = await app
425
- .scope('commerce-member')
426
- .model.address.getById(fixture.addressId);
434
+ const liveAddress = await app.scope('commerce-member').model.address.getById(addressId);
427
435
  const liveCoupon = await app
428
436
  .scope('commerce-promotion')
429
437
  .model.couponTemplate.getById(fixture.couponTemplateId);