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
|
@@ -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?:
|
|
8
|
-
productId?:
|
|
9
|
-
skuId?:
|
|
10
|
-
balanceId?:
|
|
11
|
-
templateId?:
|
|
12
|
-
grantId?:
|
|
13
|
-
addressId?:
|
|
14
|
-
cartId?:
|
|
15
|
-
userId?:
|
|
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?:
|
|
19
|
-
paymentAttemptId?:
|
|
20
|
-
paymentSessionId?:
|
|
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
|
|
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
|
-
|
|
227
|
-
|
|
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
|
|
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
|
|
648
|
+
app.bean.executor.performAction('get', '/commerce/trade/order/:id', {
|
|
649
|
+
params: { id: 1 },
|
|
643
650
|
innerAccess: false,
|
|
644
651
|
}),
|
|
645
652
|
() =>
|
|
646
|
-
app
|
|
647
|
-
|
|
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?:
|
|
8
|
-
productId?:
|
|
9
|
-
skuId?:
|
|
10
|
-
balanceId?:
|
|
11
|
-
templateId?:
|
|
12
|
-
grantId?:
|
|
13
|
-
addressId?:
|
|
14
|
-
cartId?:
|
|
15
|
-
userId?:
|
|
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?:
|
|
18
|
-
paymentAttemptId?:
|
|
19
|
-
paymentSessionId?:
|
|
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
|
|
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.
|
|
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
|
-
|
|
849
|
-
|
|
850
|
-
{
|
|
851
|
-
|
|
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
|
-
|
|
865
|
-
|
|
866
|
-
{
|
|
867
|
-
|
|
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?:
|
|
10
|
-
productId?:
|
|
11
|
-
skuId?:
|
|
12
|
-
balanceId?:
|
|
13
|
-
userId?:
|
|
14
|
-
couponTemplateId?:
|
|
15
|
-
couponGrantId?:
|
|
16
|
-
cartId?:
|
|
17
|
-
addressId?:
|
|
18
|
-
orderId?:
|
|
19
|
-
orderLineId?:
|
|
20
|
-
reservationId?:
|
|
21
|
-
paymentAttemptId?:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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]
|
|
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:
|
|
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]
|
|
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
|
-
|
|
149
|
+
const createdFixture = await createSku(createTestId());
|
|
150
|
+
fixture = createdFixture;
|
|
149
151
|
const balance = await adjustStock(
|
|
150
|
-
stockAdjust(
|
|
152
|
+
stockAdjust(createdFixture.skuId, 2, `${createdFixture.skuId}-initial`),
|
|
151
153
|
);
|
|
152
154
|
const [_, err] = await catchError(() =>
|
|
153
|
-
adjustStock(stockAdjust(
|
|
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!:
|
|
298
|
-
let auditId!:
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
90
|
-
assert.equal(schemaResolved
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
128
|
+
assert.equal(inlineSchema(schemaResolved.properties?.[field]).title?.toString(), title);
|
|
146
129
|
}
|
|
147
|
-
assert.equal(schemaResolved
|
|
148
|
-
assert.equal(schemaResolved
|
|
149
|
-
assert.deepEqual(schemaResolved
|
|
150
|
-
|
|
151
|
-
|
|
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 () => {
|