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.
- package/.cabloy-version +1 -1
- package/.github/workflows/vona-tsc.yml +2 -2
- package/CHANGELOG.md +22 -0
- package/oxlint.config.ts +5 -1
- package/package.json +5 -11
- package/repo-agent-governance/managed-assets.json +9 -9
- package/repo-agent-governance/policies/repository.md +2 -0
- package/repo-agent-governance/skills/cabloy-spec-execution/references/execution-protocol.md +6 -2
- package/repo-agent-governance/skills/cabloy-spec-generation/SKILL.md +1 -1
- package/repo-docs/ai/ai-spec-driven-development.md +6 -2
- package/repo-docs/reference/repo-scripts.md +18 -19
- package/scripts/oxlintPlugins.ts +25 -0
- package/scripts/typecheck.test.ts +107 -94
- package/scripts/typecheck.ts +1 -1
- package/vona/oxlint.config.ts +5 -1
- package/vona/package.original.json +5 -6
- package/vona/packages-cli/cabloy-cli/package.json +1 -1
- 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/beans.ts +0 -2
- package/vona/packages-cli/cli-set-api/src/lib/commands.ts +0 -2
- package/vona/packages-utils/lint/package.json +3 -3
- package/vona/packages-utils/module-glob/package.json +2 -1
- package/vona/packages-utils/module-glob/src/index.ts +2 -26
- package/vona/packages-utils/module-glob/src/suites.ts +31 -0
- package/vona/packages-utils/module-glob/test/suites.test.ts +108 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +254 -182
- package/vona/pnpm-workspace.yaml +0 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/product.test.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/sku.test.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/skuUniqueness.test.ts +9 -10
- package/vona/src/suite/a-commerce/modules/commerce-member/test/addressOwnership.test.ts +43 -16
- package/vona/src/suite/a-commerce/modules/commerce-member/test/addressPresentation.test.ts +7 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentIndexes.test.ts +2 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponReservation.test.ts +18 -9
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/cartOwnership.test.ts +28 -11
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutReservation.test.ts +33 -21
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutTransaction.test.ts +6 -5
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +33 -25
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +31 -20
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +30 -30
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +24 -26
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/shipment.test.ts +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/shipmentRefundRace.test.ts +15 -5
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockBalance.test.ts +7 -5
- package/vona/src/suite/a-home/modules/home-user/test/account.test.ts +2 -2
- package/vona/src/suite/a-home/modules/home-user/test/accountActivation.test.ts +2 -1
- package/vona/src/suite/a-home/modules/home-user/test/accountOpenapi.test.ts +53 -42
- package/vona/src/suite/a-home/modules/home-user/test/accountPasswordReset.test.ts +1 -0
- package/vona/src/suite/a-home/modules/home-user/test/accountPasswordSet.test.ts +2 -1
- package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +12 -7
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +23 -11
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/test/rbacCatalogGuard.test.ts +5 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/test/ssrMenuVisibility.test.ts +3 -3
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/test/middlewareSystem.health.test.ts +33 -12
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/permissions.ts +3 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_crud_inner.ts +6 -2
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_crud_table.ts +4 -4
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/dto/dtoMutate.ts +38 -9
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsGroup.ts +12 -10
- package/vona/src/suite-vendor/a-vona/modules/a-orm/test/dtoMutate.types.test.ts +129 -0
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-security/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-security/test/origin.test.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-web/cli/controller/metadata/generate.ts +72 -28
- package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/test/controllerMetadataParser.test.ts +156 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/vona/tsconfig.json +19 -35
- package/zova/oxlint.config.ts +5 -1
- package/zova/package.original.json +6 -6
- package/zova/packages-cli/cli/package.json +3 -3
- package/zova/packages-cli/cli-set-front/package.json +4 -4
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +195 -65
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.tsc.ts +0 -89
- package/vona/packages-cli/cli-set-api/src/lib/command/bin.tsc.ts +0 -24
- /package/{vona/packages-cli/cli-set-api/src/lib → scripts}/typescriptRunner.ts +0 -0
package/vona/pnpm-workspace.yaml
CHANGED
|
@@ -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(
|
|
@@ -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
|
|
122
|
+
const skuIds: TableIdentity[] = [];
|
|
123
123
|
for (let index = 0; index < 2; index++) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
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
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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(
|
|
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?:
|
|
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:
|
|
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
|
-
[
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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 +
|
|
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
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
app.bean.executor.performAction(
|
|
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?:
|
|
9
|
-
productId?:
|
|
10
|
-
skuId?:
|
|
11
|
-
balanceId?:
|
|
12
|
-
cartId?:
|
|
13
|
-
cartItemId?:
|
|
14
|
-
addressId?:
|
|
15
|
-
orderId?:
|
|
16
|
-
orderLineId?:
|
|
17
|
-
paymentAttemptId?:
|
|
18
|
-
orderAuditId?:
|
|
19
|
-
reservationId?:
|
|
20
|
-
stockAuditIds?:
|
|
21
|
-
couponTemplateId?:
|
|
22
|
-
couponGrantId?:
|
|
23
|
-
userId?:
|
|
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(
|
|
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?:
|
|
695
|
-
productId?:
|
|
696
|
-
skuId?:
|
|
697
|
-
balanceId?:
|
|
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
|
|
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
|
-
|
|
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:
|
|
187
|
-
couponGrantId:
|
|
188
|
-
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?:
|
|
9
|
-
productId?:
|
|
10
|
-
skuId?:
|
|
11
|
-
addressId?:
|
|
12
|
-
cartId?:
|
|
13
|
-
orderId?:
|
|
14
|
-
paymentAttemptId?:
|
|
15
|
-
couponTemplateId?:
|
|
16
|
-
couponGrantId?:
|
|
17
|
-
userId?:
|
|
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
|
|
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
|
|
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
|
|
199
|
+
addressId,
|
|
192
200
|
correlationId: `snapshot-order-${suffix}`,
|
|
193
|
-
lines: [{ skuId
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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);
|