cabloy 5.1.126 → 5.1.128
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +37 -0
- package/cabloy-docs/backend/error-guide.md +17 -0
- package/cabloy-docs/backend/i18n-guide.md +86 -7
- package/e2e/specs/a-commerce/commerce.spec.ts +222 -6
- package/package.json +1 -1
- package/vona/env/.env +2 -0
- package/vona/env/.env.prod +6 -2
- 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-utils/module-glob/package.json +1 -1
- package/vona/packages-utils/utils/package.json +1 -1
- package/vona/packages-utils/utils/src/vona.ts +8 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorClass.ts +21 -4
- package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorObject.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/bean/type.ts +3 -1
- package/vona/packages-vona/vona-core/src/types/interface/module.ts +6 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +15 -44
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +37 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectResItem.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categoryView.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productSelectResItem.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/category.tsx +21 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/product.tsx +23 -3
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/category.ts +11 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/product.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/category.ts +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/categoryPresentation.test.ts +218 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productPresentation.test.ts +214 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +39 -3
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/commercePayScene.ts +14 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +5 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +17 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/checkout.ts +18 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutCreate.tsx +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutPaymentMethod.tsx +19 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutPaymentMethods.tsx +17 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundResult.tsx +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +53 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +2 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +111 -3
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +94 -55
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payScene.ts +65 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.redlock.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentCallback.ts +32 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentSession.ts +26 -8
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCallback.ts +99 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts +26 -3
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +214 -82
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +27 -2
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payScene.ts +9 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +5 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/bean/payProvider.mock.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +395 -84
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/index.ts +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/paypalGateway.ts +129 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/types/index.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +7 -0
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- 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-jsx/package.json +3 -3
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +2 -2
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +16 -4
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +240 -19
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeCheckout.ts +16 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +8 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +184 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeCheckout.ts +12 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +11 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +25 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +27 -4
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/bean/tableCell.select.tsx +12 -6
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +6 -3
- package/zova/src/suite-vendor/a-pay/modules/a-pay/cli/openapi.config.ts +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/schemas.ts +20 -8
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/types.ts +292 -32
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/paymentSession.ts +26 -4
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/apiSchema/paymentSession.ts +5 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +2 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +12 -0
- package/zova/src/suite-vendor/a-pay/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +4 -4
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.128
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.128
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add payment session support.
|
|
8
|
+
- Combine resource name components.
|
|
9
|
+
- Add a PayPal Commerce runbook.
|
|
10
|
+
- Implement the product semantic presentation contract.
|
|
11
|
+
- Complete the PayPal Commerce payment flow.
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
- Preserve changes when resolving main branch conflicts.
|
|
16
|
+
- Enforce payment provider availability.
|
|
17
|
+
- Harden PayPal Commerce reconciliation.
|
|
18
|
+
|
|
19
|
+
## 5.1.127
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
- Separate module error codes from HTTP status codes.
|
|
24
|
+
- Complete the commerce payment refund flow.
|
|
25
|
+
- Update feature functionality.
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
- Harden the commerce order detail locator.
|
|
30
|
+
|
|
31
|
+
### Improvements
|
|
32
|
+
|
|
33
|
+
- Refactor error handling.
|
|
34
|
+
- Record the first phase of error status evolution.
|
|
35
|
+
- Define product presentation authority.
|
|
36
|
+
- Retain category semantic CI evidence.
|
|
37
|
+
- Retain semantic presentation CI evidence.
|
|
38
|
+
- Prepare the category presentation rollout.
|
|
39
|
+
|
|
3
40
|
## 5.1.126
|
|
4
41
|
|
|
5
42
|
### Features
|
|
@@ -37,6 +37,23 @@ export const errors = {
|
|
|
37
37
|
|
|
38
38
|
A useful convention is that business error codes should stay above `1000`.
|
|
39
39
|
|
|
40
|
+
### Structured declarations and HTTP status
|
|
41
|
+
|
|
42
|
+
Legacy numeric declarations remain supported. When a business error needs a stable namespaced application code and an intentional HTTP status, use a descriptor:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export const errors = {
|
|
46
|
+
ErrorTest: {
|
|
47
|
+
code: 1001,
|
|
48
|
+
status: 409,
|
|
49
|
+
},
|
|
50
|
+
} as const;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The descriptor produces an application error code such as `training-student:1001` and an HTTP response status of `409`. The `code` is the stable business identity; `status` is the initial transport decision. The standard JSON response remains `{ code, message }`, with the HTTP status carried by the response envelope.
|
|
54
|
+
|
|
55
|
+
Use statuses that describe the API meaning: `404` for an absent resource, `409` for a resource-state or uniqueness conflict, and `422` for unacceptable business content. Existing final error filters may still revise the initial status for request-context policy, such as converting unauthenticated authorization failures from `403` to `401`.
|
|
56
|
+
|
|
40
57
|
### 2. Define the localized error messages
|
|
41
58
|
|
|
42
59
|
Representative locale pattern:
|
|
@@ -127,17 +127,96 @@ declare module 'vona' {
|
|
|
127
127
|
|
|
128
128
|
In the VSCode workflow, the `recordlocale` snippet can generate the augmentation skeleton.
|
|
129
129
|
|
|
130
|
-
##
|
|
130
|
+
## Formatting locale text
|
|
131
131
|
|
|
132
|
-
Locale
|
|
132
|
+
Locale values use printf-style placeholders. Pass formatting values after the locale key:
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
|
|
134
|
+
```typescript
|
|
135
|
+
// en-us.ts
|
|
136
|
+
export default {
|
|
137
|
+
Apples_: '%d apples',
|
|
138
|
+
Greeting: '%s has %d apples',
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
this.scope.locale.Apples_.locale('en-us', 2);
|
|
144
|
+
// 2 apples
|
|
145
|
+
|
|
146
|
+
this.scope.locale.Greeting.locale('en-us', 'Mike', 2);
|
|
147
|
+
// Mike has 2 apples
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Common placeholders include:
|
|
151
|
+
|
|
152
|
+
- `%s`: string
|
|
153
|
+
- `%d`: number
|
|
154
|
+
- `%i`: integer
|
|
155
|
+
- `%f`: floating-point number
|
|
156
|
+
- `%j`: JSON
|
|
157
|
+
- `%%`: literal percent sign
|
|
158
|
+
|
|
159
|
+
## Plural and value-specific support
|
|
160
|
+
|
|
161
|
+
Locale resources can express special messages for exact numeric values through key suffixes:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
export default {
|
|
165
|
+
Apples_: '%d apples',
|
|
166
|
+
Apples_0: 'no apples',
|
|
167
|
+
Apples_1: 'one apple',
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The suffix selects a special message based on the first formatting argument:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
this.scope.locale.Apples_.locale('en-us', 0);
|
|
175
|
+
// no apples
|
|
176
|
+
|
|
177
|
+
this.scope.locale.Apples_.locale('en-us', 1);
|
|
178
|
+
// one apple
|
|
137
179
|
|
|
138
|
-
|
|
180
|
+
this.scope.locale.Apples_.locale('en-us', 2);
|
|
181
|
+
// 2 apples
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The base key is used when no value-specific key matches. Therefore, it is not necessary to define a special key for every possible number.
|
|
185
|
+
|
|
186
|
+
For a numeric value that is not the first formatting argument, use this naming convention:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
<baseKey>_<matchedValue>_<argumentIndex>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The argument index is zero-based. For example:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
export default {
|
|
196
|
+
NameApples_: '%s has %d apples',
|
|
197
|
+
NameApples_0_1: '%s has no apples',
|
|
198
|
+
NameApples_1_1: '%s has one apple',
|
|
199
|
+
};
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Here:
|
|
203
|
+
|
|
204
|
+
- `NameApples_0_1` matches `args[1] === 0`, the second formatting argument;
|
|
205
|
+
- `NameApples_1_1` matches `args[1] === 1`, the second formatting argument;
|
|
206
|
+
- `NameApples_` is used for other values.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
this.scope.locale.NameApples_.locale('en-us', 'Mike', 0);
|
|
210
|
+
// Mike has no apples
|
|
211
|
+
|
|
212
|
+
this.scope.locale.NameApples_.locale('en-us', 'Mike', 1);
|
|
213
|
+
// Mike has one apple
|
|
214
|
+
|
|
215
|
+
this.scope.locale.NameApples_.locale('en-us', 'Mike', 2);
|
|
216
|
+
// Mike has 2 apples
|
|
217
|
+
```
|
|
139
218
|
|
|
140
|
-
This makes plural handling part of the localization model
|
|
219
|
+
This is exact numeric-value selection, not automatic `Intl.PluralRules` or CLDR plural-category resolution. It makes common plural handling part of the localization model while allowing each locale to define its own wording.
|
|
141
220
|
|
|
142
221
|
## Timezone support
|
|
143
222
|
|
|
@@ -415,7 +415,7 @@ test(
|
|
|
415
415
|
const startResponse = waitForApiResponse(
|
|
416
416
|
page,
|
|
417
417
|
'POST',
|
|
418
|
-
new RegExp(`/api/payment-session/${checkout.paymentSessionId}/start$`),
|
|
418
|
+
new RegExp(`/api/pay/payment-session/${checkout.paymentSessionId}/start$`),
|
|
419
419
|
);
|
|
420
420
|
await page.getByRole('button', { name: 'Start payment', exact: true }).click();
|
|
421
421
|
expect((await startResponse).ok()).toBeTruthy();
|
|
@@ -584,7 +584,7 @@ test(
|
|
|
584
584
|
const startResponse = waitForApiResponse(
|
|
585
585
|
page,
|
|
586
586
|
'POST',
|
|
587
|
-
new RegExp(`/api/payment-session/${checkout.paymentSessionId}/start$`),
|
|
587
|
+
new RegExp(`/api/pay/payment-session/${checkout.paymentSessionId}/start$`),
|
|
588
588
|
);
|
|
589
589
|
await page.getByRole('button', { name: 'Start payment', exact: true }).click();
|
|
590
590
|
expect((await startResponse).ok()).toBeTruthy();
|
|
@@ -652,7 +652,7 @@ test(
|
|
|
652
652
|
const startResponse = waitForApiResponse(
|
|
653
653
|
page,
|
|
654
654
|
'POST',
|
|
655
|
-
new RegExp(`/api/payment-session/${checkout.paymentSessionId}/start$`),
|
|
655
|
+
new RegExp(`/api/pay/payment-session/${checkout.paymentSessionId}/start$`),
|
|
656
656
|
);
|
|
657
657
|
await page.getByRole('button', { name: 'Start payment', exact: true }).click();
|
|
658
658
|
expect((await startResponse).ok()).toBeTruthy();
|
|
@@ -705,11 +705,38 @@ test(
|
|
|
705
705
|
const executeResponse = waitForApiResponse(
|
|
706
706
|
adminPage,
|
|
707
707
|
'POST',
|
|
708
|
-
`/api/commerce/trade/order/${checkout.orderId}/
|
|
708
|
+
`/api/commerce/trade/order/${checkout.orderId}/executeRefund`,
|
|
709
709
|
);
|
|
710
710
|
await orderRow.getByRole('button', { name: 'Execute refund', exact: true }).click();
|
|
711
|
-
|
|
712
|
-
|
|
711
|
+
const executeResponseValue = await executeResponse;
|
|
712
|
+
expect(executeResponseValue.ok()).toBeTruthy();
|
|
713
|
+
const executeResult = (await executeResponseValue.json()).data;
|
|
714
|
+
const authorization = executeResponseValue.request().headers()['authorization'];
|
|
715
|
+
expect(authorization).toMatch(/^Bearer /);
|
|
716
|
+
const authHeaders = { Authorization: authorization };
|
|
717
|
+
expectTableIdentity(executeResult.refundAttemptId);
|
|
718
|
+
expectTableIdentity(executeResult.refundOperationId);
|
|
719
|
+
expect([
|
|
720
|
+
executeResult.orderState,
|
|
721
|
+
executeResult.refundState,
|
|
722
|
+
executeResult.refundAttemptState,
|
|
723
|
+
]).toEqual(['refund_approved', 'approved', 'created']);
|
|
724
|
+
const completeRefundResponse = await adminPage.request.post(
|
|
725
|
+
`/api/pay/mock/payment-session/refund-operation/${executeResult.refundOperationId}/complete`,
|
|
726
|
+
{ headers: authHeaders, data: { outcome: 'succeeded' } },
|
|
727
|
+
);
|
|
728
|
+
expect(completeRefundResponse.ok()).toBeTruthy();
|
|
729
|
+
await expect
|
|
730
|
+
.poll(
|
|
731
|
+
async () => {
|
|
732
|
+
await adminPage.reload({ waitUntil: 'load' });
|
|
733
|
+
const currentOrderRow = await getAdminOrderRow(adminPage, checkout.orderId);
|
|
734
|
+
return (await currentOrderRow.textContent()) ?? '';
|
|
735
|
+
},
|
|
736
|
+
{ timeout: 40_000 },
|
|
737
|
+
)
|
|
738
|
+
.toContain('refunded');
|
|
739
|
+
await expect(await getAdminOrderRow(adminPage, checkout.orderId)).toContainText('refunded');
|
|
713
740
|
expect(adminPageErrors).toEqual([]);
|
|
714
741
|
} finally {
|
|
715
742
|
await adminContext.close().catch(() => {});
|
|
@@ -885,6 +912,195 @@ test(
|
|
|
885
912
|
},
|
|
886
913
|
);
|
|
887
914
|
|
|
915
|
+
test(
|
|
916
|
+
'ATP-SPC-02: Category renders semantic Admin relation and publication controls',
|
|
917
|
+
{ tag: ['@admin', '@flow', '@category'] },
|
|
918
|
+
async ({ browser }, testInfo) => {
|
|
919
|
+
test.setTimeout(60_000);
|
|
920
|
+
const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
921
|
+
const parentName = `E2E Category Parent ${suffix}`;
|
|
922
|
+
const childName = `E2E Category Child ${suffix}`;
|
|
923
|
+
const adminContext = await browser.newContext();
|
|
924
|
+
const adminPage = await adminContext.newPage();
|
|
925
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
926
|
+
const categoryCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Acategory/create';
|
|
927
|
+
const categoryListPath = '/commerce-admin/rest/resource/commerce-catalog%3Acategory';
|
|
928
|
+
const categoryActionPath = '/api/commerce/catalog/category';
|
|
929
|
+
let parentId: number | string | undefined;
|
|
930
|
+
let childId: number | string | undefined;
|
|
931
|
+
let headers: { Authorization: string } | undefined;
|
|
932
|
+
try {
|
|
933
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
934
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
935
|
+
const accessToken = (await adminContext.cookies()).find(
|
|
936
|
+
cookie => cookie.name === 'token',
|
|
937
|
+
)?.value;
|
|
938
|
+
expect(accessToken).toBeTruthy();
|
|
939
|
+
headers = { Authorization: `Bearer ${accessToken}` };
|
|
940
|
+
|
|
941
|
+
const parentCreateResponse = await adminPage.request.post(categoryActionPath, {
|
|
942
|
+
data: { name: parentName, published: false },
|
|
943
|
+
headers,
|
|
944
|
+
});
|
|
945
|
+
expect(parentCreateResponse.ok()).toBeTruthy();
|
|
946
|
+
parentId = (await parentCreateResponse.json()).data;
|
|
947
|
+
expectTableIdentity(parentId);
|
|
948
|
+
|
|
949
|
+
await adminPage.goto(categoryCreatePath, { waitUntil: 'load' });
|
|
950
|
+
const parentPublication = adminPage
|
|
951
|
+
.getByRole('group', { name: 'Published' })
|
|
952
|
+
.getByRole('combobox');
|
|
953
|
+
await expect(parentPublication).toBeVisible();
|
|
954
|
+
await expect(parentPublication.locator('option')).toHaveText([
|
|
955
|
+
'',
|
|
956
|
+
'Unpublished',
|
|
957
|
+
'Published',
|
|
958
|
+
]);
|
|
959
|
+
await parentPublication.selectOption({ label: 'Unpublished' });
|
|
960
|
+
await expect(parentPublication).toHaveValue('false');
|
|
961
|
+
|
|
962
|
+
const parentPicker = adminPage
|
|
963
|
+
.getByRole('group', { name: 'Parent category' })
|
|
964
|
+
.getByRole('combobox');
|
|
965
|
+
await expect(parentPicker).toBeVisible();
|
|
966
|
+
await parentPicker.selectOption({ label: parentName });
|
|
967
|
+
const parentValue = await parentPicker.inputValue();
|
|
968
|
+
expect(parentValue).toBe(String(parentId));
|
|
969
|
+
const childPublication = adminPage
|
|
970
|
+
.getByRole('group', { name: 'Published' })
|
|
971
|
+
.getByRole('combobox');
|
|
972
|
+
await childPublication.selectOption({ label: 'Published' });
|
|
973
|
+
await expect(childPublication).toHaveValue('true');
|
|
974
|
+
|
|
975
|
+
const childCreateResponse = await adminPage.request.post(categoryActionPath, {
|
|
976
|
+
data: { name: childName, parentId: parentValue, published: true },
|
|
977
|
+
headers,
|
|
978
|
+
});
|
|
979
|
+
expect(childCreateResponse.ok()).toBeTruthy();
|
|
980
|
+
childId = (await childCreateResponse.json()).data;
|
|
981
|
+
expectTableIdentity(childId);
|
|
982
|
+
|
|
983
|
+
await adminPage.goto(categoryListPath, { waitUntil: 'load' });
|
|
984
|
+
const childRow = adminPage.getByRole('row', { name: new RegExp(childName) });
|
|
985
|
+
await expect(childRow).toBeVisible();
|
|
986
|
+
await expect(childRow.getByText(parentName, { exact: true })).toBeVisible();
|
|
987
|
+
await expect(childRow.getByText('Published', { exact: true })).toBeVisible();
|
|
988
|
+
|
|
989
|
+
await adminPage.goto(`${categoryListPath}/${childId}`, { waitUntil: 'load' });
|
|
990
|
+
await expect(
|
|
991
|
+
adminPage.getByRole('group', { name: 'Parent category' }).getByRole('textbox'),
|
|
992
|
+
).toHaveValue(parentName);
|
|
993
|
+
await expect(
|
|
994
|
+
adminPage.getByRole('group', { name: 'Published' }).getByRole('textbox'),
|
|
995
|
+
).toHaveValue('Published');
|
|
996
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
997
|
+
expect(adminPageErrors).toEqual([]);
|
|
998
|
+
} finally {
|
|
999
|
+
if (childId && headers) {
|
|
1000
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${childId}`, {
|
|
1001
|
+
headers,
|
|
1002
|
+
});
|
|
1003
|
+
expect(response.ok()).toBeTruthy();
|
|
1004
|
+
}
|
|
1005
|
+
if (parentId && headers) {
|
|
1006
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${parentId}`, {
|
|
1007
|
+
headers,
|
|
1008
|
+
});
|
|
1009
|
+
expect(response.ok()).toBeTruthy();
|
|
1010
|
+
}
|
|
1011
|
+
await adminContext.close().catch(() => {});
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
test(
|
|
1017
|
+
'ATP-SPC-02: Product renders semantic Admin relation and publication controls',
|
|
1018
|
+
{ tag: ['@admin', '@flow', '@product'] },
|
|
1019
|
+
async ({ browser }, testInfo) => {
|
|
1020
|
+
test.setTimeout(60_000);
|
|
1021
|
+
const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
1022
|
+
const categoryName = `E2E Product Category ${suffix}`;
|
|
1023
|
+
const productTitle = `E2E Product ${suffix}`;
|
|
1024
|
+
const adminContext = await browser.newContext();
|
|
1025
|
+
const adminPage = await adminContext.newPage();
|
|
1026
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
1027
|
+
const productCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Aproduct/create';
|
|
1028
|
+
const productListPath = '/commerce-admin/rest/resource/commerce-catalog%3Aproduct';
|
|
1029
|
+
const categoryActionPath = '/api/commerce/catalog/category';
|
|
1030
|
+
const productActionPath = '/api/commerce/catalog/product';
|
|
1031
|
+
let categoryId: number | string | undefined;
|
|
1032
|
+
let productId: number | string | undefined;
|
|
1033
|
+
let headers: { Authorization: string } | undefined;
|
|
1034
|
+
try {
|
|
1035
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
1036
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
1037
|
+
const accessToken = (await adminContext.cookies()).find(
|
|
1038
|
+
cookie => cookie.name === 'token',
|
|
1039
|
+
)?.value;
|
|
1040
|
+
expect(accessToken).toBeTruthy();
|
|
1041
|
+
headers = { Authorization: `Bearer ${accessToken}` };
|
|
1042
|
+
|
|
1043
|
+
const categoryResponse = await adminPage.request.post(categoryActionPath, {
|
|
1044
|
+
data: { name: categoryName, published: true },
|
|
1045
|
+
headers,
|
|
1046
|
+
});
|
|
1047
|
+
expect(categoryResponse.ok()).toBeTruthy();
|
|
1048
|
+
categoryId = (await categoryResponse.json()).data;
|
|
1049
|
+
expectTableIdentity(categoryId);
|
|
1050
|
+
|
|
1051
|
+
await adminPage.goto(productCreatePath, { waitUntil: 'load' });
|
|
1052
|
+
const categoryPicker = adminPage
|
|
1053
|
+
.getByRole('group', { name: 'Category' })
|
|
1054
|
+
.getByRole('combobox');
|
|
1055
|
+
await expect(categoryPicker).toBeVisible();
|
|
1056
|
+
await categoryPicker.selectOption({ label: categoryName });
|
|
1057
|
+
await expect(categoryPicker).toHaveValue(String(categoryId));
|
|
1058
|
+
const publication = adminPage.getByRole('group', { name: 'Published' }).getByRole('combobox');
|
|
1059
|
+
await expect(publication).toBeVisible();
|
|
1060
|
+
await publication.selectOption({ label: 'Unpublished' });
|
|
1061
|
+
await expect(publication).toHaveValue('false');
|
|
1062
|
+
|
|
1063
|
+
const productResponse = await adminPage.request.post(productActionPath, {
|
|
1064
|
+
data: { categoryId, title: productTitle, published: false },
|
|
1065
|
+
headers,
|
|
1066
|
+
});
|
|
1067
|
+
expect(productResponse.ok()).toBeTruthy();
|
|
1068
|
+
productId = (await productResponse.json()).data;
|
|
1069
|
+
expectTableIdentity(productId);
|
|
1070
|
+
|
|
1071
|
+
await adminPage.goto(productListPath, { waitUntil: 'load' });
|
|
1072
|
+
const productRow = adminPage.getByRole('row', { name: new RegExp(productTitle) });
|
|
1073
|
+
await expect(productRow).toBeVisible();
|
|
1074
|
+
await expect(productRow.getByText(categoryName, { exact: true })).toBeVisible();
|
|
1075
|
+
await expect(productRow.getByText('Unpublished', { exact: true })).toBeVisible();
|
|
1076
|
+
|
|
1077
|
+
await adminPage.goto(`${productListPath}/${productId}`, { waitUntil: 'load' });
|
|
1078
|
+
await expect(
|
|
1079
|
+
adminPage.getByRole('group', { name: 'Category' }).getByRole('textbox'),
|
|
1080
|
+
).toHaveValue(categoryName);
|
|
1081
|
+
await expect(
|
|
1082
|
+
adminPage.getByRole('group', { name: 'Published' }).getByRole('textbox'),
|
|
1083
|
+
).toHaveValue('Unpublished');
|
|
1084
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
1085
|
+
expect(adminPageErrors).toEqual([]);
|
|
1086
|
+
} finally {
|
|
1087
|
+
if (productId && headers) {
|
|
1088
|
+
const response = await adminPage.request.delete(`${productActionPath}/${productId}`, {
|
|
1089
|
+
headers,
|
|
1090
|
+
});
|
|
1091
|
+
expect(response.ok()).toBeTruthy();
|
|
1092
|
+
}
|
|
1093
|
+
if (categoryId && headers) {
|
|
1094
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${categoryId}`, {
|
|
1095
|
+
headers,
|
|
1096
|
+
});
|
|
1097
|
+
expect(response.ok()).toBeTruthy();
|
|
1098
|
+
}
|
|
1099
|
+
await adminContext.close().catch(() => {});
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
);
|
|
1103
|
+
|
|
888
1104
|
test(
|
|
889
1105
|
'Commerce Cart: anonymous browser is redirected to login',
|
|
890
1106
|
{ tag: ['@web', '@cart'] },
|
package/package.json
CHANGED
package/vona/env/.env
CHANGED
package/vona/env/.env.prod
CHANGED
|
@@ -17,7 +17,14 @@ export function combineResourceNameParts(
|
|
|
17
17
|
// path
|
|
18
18
|
const res: string[] = [];
|
|
19
19
|
if (!simplifyProviderId || parts[0] !== 'a') res.push(parts[0]);
|
|
20
|
-
if (
|
|
20
|
+
if (
|
|
21
|
+
!simplify ||
|
|
22
|
+
(resourceName !== parts[1] &&
|
|
23
|
+
!resourceName.startsWith(`${parts[1]}-`) &&
|
|
24
|
+
!resourceName.startsWith(`${parts[1]}/`))
|
|
25
|
+
) {
|
|
26
|
+
res.push(parts[1]);
|
|
27
|
+
}
|
|
21
28
|
if (resourceName) res.push(resourceName);
|
|
22
29
|
return res;
|
|
23
30
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.85",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
|
|
6
6
|
"keywords": [
|
|
@@ -21,7 +21,7 @@ export class ErrorClass extends BeanSimple {
|
|
|
21
21
|
fail(module, code, ...args) {
|
|
22
22
|
const body = this.parseFail(module, code, ...args);
|
|
23
23
|
|
|
24
|
-
this.ctx.response.status =
|
|
24
|
+
this.ctx.response.status = body.status;
|
|
25
25
|
this.ctx.response.type = 'application/json';
|
|
26
26
|
this.ctx.response.body = { code: body.code, message: body.message }; // body maybe Error
|
|
27
27
|
}
|
|
@@ -32,7 +32,7 @@ export class ErrorClass extends BeanSimple {
|
|
|
32
32
|
const err = new Error();
|
|
33
33
|
err.code = body.code;
|
|
34
34
|
err.message = body.message;
|
|
35
|
-
err.status =
|
|
35
|
+
err.status = body.status;
|
|
36
36
|
throw err;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -58,9 +58,16 @@ export class ErrorClass extends BeanSimple {
|
|
|
58
58
|
|
|
59
59
|
// convert from enum
|
|
60
60
|
let text;
|
|
61
|
+
let status;
|
|
61
62
|
if (ebError && code && typeof code === 'string') {
|
|
62
63
|
text = code;
|
|
63
|
-
|
|
64
|
+
const declaration = ebError[code];
|
|
65
|
+
if (__isErrorDescriptor(declaration)) {
|
|
66
|
+
code = declaration.code;
|
|
67
|
+
status = declaration.status;
|
|
68
|
+
} else {
|
|
69
|
+
code = declaration;
|
|
70
|
+
}
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
if (code === undefined || code === null || code === '') {
|
|
@@ -75,10 +82,20 @@ export class ErrorClass extends BeanSimple {
|
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
code = __combineErrorCode(module, code);
|
|
78
|
-
|
|
85
|
+
status ??= __calcStatus(code);
|
|
86
|
+
return { code, status, message };
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
|
|
90
|
+
function __isErrorDescriptor(value) {
|
|
91
|
+
return (
|
|
92
|
+
value &&
|
|
93
|
+
typeof value === 'object' &&
|
|
94
|
+
typeof value.code === 'number' &&
|
|
95
|
+
typeof value.status === 'number'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
function __combineErrorCode(module, code) {
|
|
83
100
|
if (typeof code !== 'number' || code <= 1000) return code;
|
|
84
101
|
return module ? `${module}:${code}` : code;
|
|
@@ -29,12 +29,14 @@ export type TypeBeanScopeLocaleKeys = keyof IBeanScopeLocale;
|
|
|
29
29
|
export interface IBeanScopeErrors {}
|
|
30
30
|
export type TypeBeanScopeErrorsKeys = keyof IBeanScopeErrors;
|
|
31
31
|
|
|
32
|
+
type TypeErrorDeclarationCode<T> = T extends { code: infer Code } ? Code : T;
|
|
33
|
+
|
|
32
34
|
export type TypeScopesErrorsHelper<
|
|
33
35
|
ModuleName extends keyof IBeanScopeErrors,
|
|
34
36
|
Errors extends IBeanScopeErrors[ModuleName],
|
|
35
37
|
> = {
|
|
36
38
|
// @ts-ignore: ignore
|
|
37
|
-
[K in keyof Errors as `${ModuleName}:${Errors[K]}`]: K;
|
|
39
|
+
[K in keyof Errors as `${ModuleName}:${TypeErrorDeclarationCode<Errors[K]>}`]: K;
|
|
38
40
|
};
|
|
39
41
|
export type TypeScopesErrorCodes = TypeRecordValues<{
|
|
40
42
|
[ModuleName in keyof IBeanScopeErrors]: keyof TypeScopesErrorsHelper<
|
|
@@ -4,7 +4,12 @@ import type { IModuleMain, IMonkeyModule, IMonkeySystem } from './monkey.ts';
|
|
|
4
4
|
|
|
5
5
|
export type TypeModuleResourceLocales = Record<string, object>;
|
|
6
6
|
export type TypeModuleResourceLocaleModules = Record<string, TypeModuleResourceLocales>;
|
|
7
|
-
export
|
|
7
|
+
export interface IModuleResourceErrorDescriptor {
|
|
8
|
+
code: number;
|
|
9
|
+
status: number;
|
|
10
|
+
}
|
|
11
|
+
export type TypeModuleResourceError = number | IModuleResourceErrorDescriptor;
|
|
12
|
+
export type TypeModuleResourceErrors = Record<string, TypeModuleResourceError>;
|
|
8
13
|
export type TypeModuleResourceErrorModules = Record<string, TypeModuleResourceErrors>;
|
|
9
14
|
export type TypeModuleResourceConfig = (app: VonaApplication) => object | Promise<object>;
|
|
10
15
|
|