cabloy 5.1.115 → 5.1.117
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/.claude/skills/cabloy-contract-loop/SKILL.md +8 -2
- package/.claude/skills/cabloy-contract-loop/evals/evals.json +6 -0
- package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +13 -0
- package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +16 -3
- package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +12 -0
- package/.github/workflows/vona-test-mysql.yml +6 -10
- package/.github/workflows/vona-test-pg.yml +9 -10
- package/.github/workflows/vona-test-sqlite3.yml +3 -0
- package/CHANGELOG.md +35 -0
- package/CLAUDE.md +2 -0
- package/cabloy-docs/.vitepress/config.mjs +6 -0
- package/cabloy-docs/ai/docs-skills-rules-mapping.md +7 -0
- package/cabloy-docs/backend/aop-overview.md +16 -24
- package/cabloy-docs/backend/backend-source-reading-roadmap.md +26 -2
- package/cabloy-docs/backend/controller-aop-guide.md +38 -14
- package/cabloy-docs/backend/logger-guide.md +1 -0
- package/cabloy-docs/backend/menu-guide.md +1 -1
- package/cabloy-docs/backend/orm-mutation-guide.md +36 -10
- package/cabloy-docs/backend/orm-select-guide.md +15 -10
- package/cabloy-docs/backend/rate-limit-guide.md +97 -0
- package/cabloy-docs/backend/telemetry-guide.md +93 -0
- package/cabloy-docs/backend/vona-source-reading-map.md +44 -1
- package/cabloy-docs/frontend/model-resource-owner-pattern.md +2 -0
- package/cabloy-docs/frontend/ssr-overview.md +2 -0
- package/cabloy-docs/fullstack/admin-resource-and-web-self-service.md +329 -0
- package/cabloy-docs/fullstack/contract-loop-playbook.md +1 -1
- package/cabloy-docs/fullstack/introduction.md +1 -0
- package/e2e/specs/a-commerce/commerce.spec.ts +479 -1
- package/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoBaseName%>.tsx_ +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoMutateName%>.tsx_ +2 -2
- package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoResItemName%>.tsx_ +10 -10
- package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoViewName%>.tsx_ +2 -2
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.masterDetail.ts +13 -4
- 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/core/logger/utils.ts +12 -3
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +237 -164
- package/vona/src/backend/config/config/config.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/sku.test.ts +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +81 -25
- package/vona/src/suite/a-commerce/modules/commerce-member/src/bean/ssrMenu.address.ts +30 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/controller/address.ts +42 -20
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineCreate.tsx +35 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineItem.tsx +39 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineReq.tsx +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineRes.tsx +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineUpdate.tsx +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineView.tsx +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressSelectResItem.tsx +1 -7
- package/vona/src/suite/a-commerce/modules/commerce-member/src/service/address.ts +43 -42
- package/vona/src/suite/a-commerce/modules/commerce-member/test/addressOwnership.test.ts +168 -127
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +76 -3
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.index.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.version.ts +37 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/dto/paymentAttemptView.tsx +2 -2
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAttempt.tsx +5 -2
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAudit.tsx +54 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/model/paymentAudit.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/mockPaymentAdapter.ts +18 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +22 -7
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponIssue.tsx +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/service/coupon.ts +6 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponReservation.test.ts +480 -8
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/test/ssrMenu.test.ts +14 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +309 -3
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.index.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.version.ts +16 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/ssrMenu.order.ts +30 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/order.ts +69 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/payment.ts +23 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutResult.tsx +4 -4
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderDetail.tsx +72 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderMineReq.tsx +21 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderMineRes.tsx +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSelectReq.tsx +25 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSelectRes.tsx +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSelectResItem.tsx +38 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderShip.tsx +15 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSummary.tsx +26 -0
- package/vona/src/suite/a-commerce/modules/{commerce-member/src/dto/addressCreate.tsx → commerce-trade/src/dto/orderView.tsx} +5 -17
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/paymentOutcomeCreate.tsx +16 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/paymentOutcomeResult.tsx +29 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/shipmentView.tsx +22 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/orderAudit.tsx +13 -7
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/shipment.tsx +28 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/shipment.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/cart.ts +14 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +436 -34
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/stockBalance.ts +4 -3
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/cartOwnership.test.ts +160 -80
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutReservation.test.ts +773 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutTransaction.test.ts +212 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/order.test.ts +37 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +29 -5
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +714 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +282 -9
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/shipment.test.ts +260 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockBalance.test.ts +15 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockReservation.test.ts +20 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/testLock.ts +11 -0
- package/vona/src/suite/a-home/modules/home-user/src/.metadata/index.ts +21 -0
- package/vona/src/suite/a-home/modules/home-user/src/controller/passportTest.ts +21 -0
- package/vona/src/suite/a-home/modules/home-user/test/passportTest.test.ts +77 -0
- package/vona/src/suite-vendor/a-vona/modules/a-broadcast/package.json +2 -1
- package/vona/src/suite-vendor/a-vona/modules/a-broadcast/src/service/broadcast.ts +84 -23
- package/vona/src/suite-vendor/a-vona/modules/a-broadcast/src/types/broadcast.ts +7 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/src/lib/core.ts +8 -0
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +4 -2
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +68 -53
- package/vona/src/suite-vendor/a-vona/modules/a-orm/cli/model/metadata/generate.ts +10 -0
- 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_cache.ts +12 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_crud_inner.ts +3 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_utils.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/package.json +4 -2
- package/vona/src/suite-vendor/a-vona/modules/a-queue/src/service/queue.ts +103 -54
- package/vona/src/suite-vendor/a-vona/modules/a-queue/src/types/queue.ts +5 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/package.json +48 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/.metadata/index.ts +100 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/bean/interceptor.rateLimit.ts +72 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/index.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/main.ts +28 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/service/rateLimit.ts +75 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/types/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/types/rateLimit.ts +19 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-vona/modules/a-redis/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-redis/src/service/redis.ts +1 -2
- package/vona/src/suite-vendor/a-vona/modules/a-redis/src/types/redis.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +63 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/.metadata/index.ts +132 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +71 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +73 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/config/config.ts +83 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/index.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/ingress.ts +34 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/monkey.ts +22 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +201 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/types/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/types/telemetry.ts +22 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/test/telemetry.test.ts +339 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-vona/package.json +3 -1
- package/vona/src/suite-vendor/a-vona/tsconfig.json +3 -0
- package/zova/package.original.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +3 -2
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +87 -127
- package/zova/src/front/config/config/config.cabloyCommerce.ts +4 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/cli/openapi.config.ts +9 -1
- package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +16 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/commerceMemberAddress.ts +94 -50
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +162 -52
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +2656 -1298
- package/zova/src/suite/a-commerce/modules/commerce-member/src/apiSchema/commerceMemberAddress.ts +23 -13
- package/zova/src/suite/a-commerce/modules/commerce-member/src/model/addressMine.ts +67 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +21 -11
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/schemas.ts +40 -32
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +638 -633
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +6 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/component/tableCellActionShip.ts +31 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +248 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/checkout.ts +19 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/order.ts +19 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/orders.ts +19 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/payment.ts +19 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeOrder.ts +114 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradePayment.ts +36 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +75 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +762 -46
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeOrder.ts +35 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradePayment.ts +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/bean/tableCell.actionShip.tsx +41 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionShip/controller.tsx +66 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +38 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/orderMine.ts +31 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/payment.ts +25 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +14 -3
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +124 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +105 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/orders/controller.tsx +95 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +88 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +28 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/command.delete.tsx +3 -2
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionUpdate.tsx +2 -1
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionView.tsx +1 -1
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/config/locale/en-us.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/config/locale/zh-cn.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +4 -0
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressUpdate.tsx +0 -39
|
@@ -45,6 +45,7 @@ Use this path when the task is about how backend and frontend stay aligned insid
|
|
|
45
45
|
- [Framework Performance](/fullstack/framework-performance)
|
|
46
46
|
- [Vona + Zova Integration](/fullstack/vona-zova-integration)
|
|
47
47
|
- [Contract Loop Playbook](/fullstack/contract-loop-playbook)
|
|
48
|
+
- [Admin Resource and Web Self-Service](/fullstack/admin-resource-and-web-self-service)
|
|
48
49
|
- [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
|
|
49
50
|
- [Fullstack Image Workflow](/fullstack/image-workflow)
|
|
50
51
|
- [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
import type { APIRequestContext, Browser, Page, TestInfo } from '@playwright/test';
|
|
2
|
+
|
|
1
3
|
import { expect, test } from '@playwright/test';
|
|
2
4
|
|
|
3
5
|
const privateMarkers = ['cart', 'address', 'order', 'coupon', 'payment'];
|
|
6
|
+
const addressResourceUrl =
|
|
7
|
+
/\/commerce-admin\/rest\/resource\/commerce-member(?:%3A|:|%253A)address(?:[/?#]|$)/;
|
|
8
|
+
const addressMinePath = '/api/commerce/member/address/mine';
|
|
9
|
+
const addressActionPath = '/api/commerce/member/address';
|
|
10
|
+
const passportTestActivateCurrentPath = '/api/home/user/passportTest/activateCurrent';
|
|
11
|
+
|
|
12
|
+
interface IAddressFixture {
|
|
13
|
+
addressLine1: string;
|
|
14
|
+
city: string;
|
|
15
|
+
recipientName: string;
|
|
16
|
+
updatedCity: string;
|
|
17
|
+
}
|
|
4
18
|
|
|
5
|
-
function collectPageErrors(page) {
|
|
19
|
+
function collectPageErrors(page: Page) {
|
|
6
20
|
const errors: Error[] = [];
|
|
7
21
|
page.on('pageerror', error => {
|
|
8
22
|
errors.push(error);
|
|
@@ -10,6 +24,145 @@ function collectPageErrors(page) {
|
|
|
10
24
|
return errors;
|
|
11
25
|
}
|
|
12
26
|
|
|
27
|
+
function waitForApiResponse(page: Page, method: string, path: string | RegExp) {
|
|
28
|
+
return page.waitForResponse(response => {
|
|
29
|
+
const url = new URL(response.url());
|
|
30
|
+
return (
|
|
31
|
+
response.request().method() === method &&
|
|
32
|
+
(typeof path === 'string' ? url.pathname === path : path.test(url.pathname)) &&
|
|
33
|
+
!response.request().headers()['x-vona-openapi-schema']
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function waitForAddressResponse(page: Page, method: string, path: string | RegExp) {
|
|
39
|
+
return waitForApiResponse(page, method, path);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function waitForAddressMine(page: Page) {
|
|
43
|
+
return waitForAddressResponse(page, 'GET', addressMinePath);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function login(
|
|
47
|
+
page: Page,
|
|
48
|
+
path: string,
|
|
49
|
+
username: string,
|
|
50
|
+
password: string,
|
|
51
|
+
hydratedSite: string,
|
|
52
|
+
) {
|
|
53
|
+
await page.goto(path, { waitUntil: 'load' });
|
|
54
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', hydratedSite);
|
|
55
|
+
if (!page.url().includes('/login')) {
|
|
56
|
+
await page.waitForURL(/\/login(?:\?|$)/);
|
|
57
|
+
}
|
|
58
|
+
if (page.url().includes('/login')) {
|
|
59
|
+
const usernameInput = page.getByPlaceholder('Your Username');
|
|
60
|
+
const passwordInput = page.getByPlaceholder('Your Password');
|
|
61
|
+
await usernameInput.fill(username);
|
|
62
|
+
await passwordInput.fill(password);
|
|
63
|
+
await expect(usernameInput).toHaveValue(username);
|
|
64
|
+
await expect(passwordInput).toHaveValue(password);
|
|
65
|
+
await expect(page.getByPlaceholder('Please input captcha')).not.toHaveValue('');
|
|
66
|
+
await page.getByRole('button', { name: 'Login', exact: true }).click();
|
|
67
|
+
await expect(page).not.toHaveURL(/\/login(?:\?|$)/);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function registerCustomer(
|
|
72
|
+
request: APIRequestContext,
|
|
73
|
+
testInfo: TestInfo,
|
|
74
|
+
): Promise<{ password: string; username: string }> {
|
|
75
|
+
const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
76
|
+
const username = `e2e-address-${id}`;
|
|
77
|
+
const password = 'address-e2e-password';
|
|
78
|
+
const captchaResponse = await request.post('/api/captcha/create', {
|
|
79
|
+
data: { scene: 'captcha-simple:simple' },
|
|
80
|
+
});
|
|
81
|
+
expect(captchaResponse.ok()).toBeTruthy();
|
|
82
|
+
const captcha = (await captchaResponse.json()).data;
|
|
83
|
+
expect(captcha?.id).toEqual(expect.any(String));
|
|
84
|
+
expect(captcha?.token).toEqual(expect.any(String));
|
|
85
|
+
|
|
86
|
+
const registerResponse = await request.post('/api/home/user/passport/register', {
|
|
87
|
+
data: {
|
|
88
|
+
username,
|
|
89
|
+
email: `${username}@example.test`,
|
|
90
|
+
password,
|
|
91
|
+
passwordConfirm: password,
|
|
92
|
+
captcha: { id: captcha.id, token: captcha.token },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
expect(registerResponse.ok()).toBeTruthy();
|
|
96
|
+
const registration = (await registerResponse.json()).data;
|
|
97
|
+
expect(registration?.jwt?.accessToken).toEqual(expect.any(String));
|
|
98
|
+
const activateResponse = await request.post(passportTestActivateCurrentPath, {
|
|
99
|
+
headers: { Authorization: `Bearer ${registration.jwt.accessToken}` },
|
|
100
|
+
});
|
|
101
|
+
expect(activateResponse.ok()).toBeTruthy();
|
|
102
|
+
return { username, password };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createAddressFixture(testInfo: TestInfo): IAddressFixture {
|
|
106
|
+
const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
107
|
+
return {
|
|
108
|
+
recipientName: `E2E Address ${id}`,
|
|
109
|
+
addressLine1: `1 Test Street ${id}`,
|
|
110
|
+
city: `Original City ${id}`,
|
|
111
|
+
updatedCity: `Updated City ${id}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function fillAddressForm(page: Page, fixture: IAddressFixture) {
|
|
116
|
+
await page.getByPlaceholder('Recipient name').fill(fixture.recipientName);
|
|
117
|
+
await page.getByPlaceholder('Phone').fill('15555550123');
|
|
118
|
+
await page.getByPlaceholder('Country code').fill('US');
|
|
119
|
+
await page.getByPlaceholder('Region').fill('California');
|
|
120
|
+
await page.getByPlaceholder('City').fill(fixture.city);
|
|
121
|
+
await page.getByPlaceholder('Postal code').fill('94105');
|
|
122
|
+
await page.getByPlaceholder('Address line 1').fill(fixture.addressLine1);
|
|
123
|
+
await page.getByPlaceholder('Address line 2').fill('Suite E2E');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function createAddressThroughCustomerPage(
|
|
127
|
+
browser: Browser,
|
|
128
|
+
request: APIRequestContext,
|
|
129
|
+
testInfo: TestInfo,
|
|
130
|
+
) {
|
|
131
|
+
const customer = await registerCustomer(request, testInfo);
|
|
132
|
+
const fixture = createAddressFixture(testInfo);
|
|
133
|
+
const context = await browser.newContext();
|
|
134
|
+
const page = await context.newPage();
|
|
135
|
+
const pageErrors = collectPageErrors(page);
|
|
136
|
+
|
|
137
|
+
await login(page, '/commerce/address', customer.username, customer.password, 'commerce');
|
|
138
|
+
await expect(page).toHaveURL(/\/commerce\/address(?:\?|$)/);
|
|
139
|
+
await expect(page.getByRole('heading', { name: 'Addresses' })).toBeVisible();
|
|
140
|
+
await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
141
|
+
|
|
142
|
+
await fillAddressForm(page, fixture);
|
|
143
|
+
const createResponse = waitForAddressResponse(page, 'POST', `${addressActionPath}/createMine`);
|
|
144
|
+
const mineResponse = waitForAddressMine(page);
|
|
145
|
+
await page.getByRole('button', { name: 'Save address', exact: true }).click();
|
|
146
|
+
await createResponse;
|
|
147
|
+
await mineResponse;
|
|
148
|
+
await expect(page.getByRole('heading', { name: fixture.recipientName })).toBeVisible();
|
|
149
|
+
|
|
150
|
+
return { context, fixture, page, pageErrors };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function deleteAddressThroughCustomerPage(page: Page, fixture: IAddressFixture) {
|
|
154
|
+
const card = page
|
|
155
|
+
.locator('article')
|
|
156
|
+
.filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
157
|
+
const deleteResponse = waitForAddressResponse(page, 'DELETE', /\/deleteMine\/[^/]+$/);
|
|
158
|
+
const mineResponse = waitForAddressMine(page);
|
|
159
|
+
await card.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
160
|
+
await deleteResponse;
|
|
161
|
+
await mineResponse;
|
|
162
|
+
await expect(page.getByRole('heading', { name: fixture.recipientName })).toHaveCount(0);
|
|
163
|
+
await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
164
|
+
}
|
|
165
|
+
|
|
13
166
|
test(
|
|
14
167
|
'ATP-SSR-01: anonymous Commerce HTML is public and hydration completes',
|
|
15
168
|
{ tag: ['@web', '@smoke'] },
|
|
@@ -144,6 +297,297 @@ test(
|
|
|
144
297
|
},
|
|
145
298
|
);
|
|
146
299
|
|
|
300
|
+
test(
|
|
301
|
+
'ATP-ADDR-01: authenticated customer manages Address through Web self-service',
|
|
302
|
+
{ tag: ['@web', '@flow', '@address'] },
|
|
303
|
+
async ({ browser, request }, testInfo) => {
|
|
304
|
+
const { context, fixture, page, pageErrors } = await createAddressThroughCustomerPage(
|
|
305
|
+
browser,
|
|
306
|
+
request,
|
|
307
|
+
testInfo,
|
|
308
|
+
);
|
|
309
|
+
try {
|
|
310
|
+
const card = page
|
|
311
|
+
.locator('article')
|
|
312
|
+
.filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
313
|
+
await card.getByRole('button', { name: 'Edit', exact: true }).click();
|
|
314
|
+
await expect(page.getByRole('heading', { name: 'Add address' })).toHaveCount(0);
|
|
315
|
+
await expect(page.getByPlaceholder('City')).toHaveValue(fixture.city);
|
|
316
|
+
await page.getByPlaceholder('City').fill(fixture.updatedCity);
|
|
317
|
+
|
|
318
|
+
const updateResponse = waitForAddressResponse(page, 'PATCH', /\/updateMine\/[^/]+$/);
|
|
319
|
+
const mineResponse = waitForAddressMine(page);
|
|
320
|
+
await page.getByRole('button', { name: 'Save address', exact: true }).click();
|
|
321
|
+
await updateResponse;
|
|
322
|
+
await mineResponse;
|
|
323
|
+
await expect(card).toContainText(fixture.updatedCity);
|
|
324
|
+
await expect(card).not.toContainText(fixture.city);
|
|
325
|
+
|
|
326
|
+
await deleteAddressThroughCustomerPage(page, fixture);
|
|
327
|
+
await expect(page.getByRole('alert')).toHaveCount(0);
|
|
328
|
+
expect(pageErrors).toEqual([]);
|
|
329
|
+
} finally {
|
|
330
|
+
await context.close();
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
test(
|
|
336
|
+
'Phase 50/60: authenticated customer completes payment and observes operator shipment',
|
|
337
|
+
{ tag: ['@web', '@admin', '@flow', '@payment', '@shipment'] },
|
|
338
|
+
async ({ browser, request }, testInfo) => {
|
|
339
|
+
test.setTimeout(60_000);
|
|
340
|
+
const { context, fixture, page, pageErrors } = await createAddressThroughCustomerPage(
|
|
341
|
+
browser,
|
|
342
|
+
request,
|
|
343
|
+
testInfo,
|
|
344
|
+
);
|
|
345
|
+
try {
|
|
346
|
+
await page.goto('/commerce', { waitUntil: 'load' });
|
|
347
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
348
|
+
await expect(page.getByRole('heading', { name: 'Commerce catalogue' })).toBeVisible();
|
|
349
|
+
|
|
350
|
+
const productLink = page.getByRole('link', { name: 'Pour-Over Coffee Set', exact: true });
|
|
351
|
+
await expect(productLink).toBeVisible();
|
|
352
|
+
await productLink.click();
|
|
353
|
+
await expect(page).toHaveURL(/\/commerce\/product\/\d+(?:\/|$)/);
|
|
354
|
+
await expect(page.getByRole('heading', { name: 'Pour-Over Coffee Set' })).toBeVisible();
|
|
355
|
+
await expect(page.getByText('COF-SET-01')).toBeVisible();
|
|
356
|
+
await expect(page.getByText('$45.99')).toBeVisible();
|
|
357
|
+
|
|
358
|
+
const addResponse = waitForApiResponse(page, 'POST', '/api/commerce/trade/cart/items');
|
|
359
|
+
await page.getByRole('button', { name: 'Add to cart', exact: true }).click();
|
|
360
|
+
await addResponse;
|
|
361
|
+
|
|
362
|
+
await page.getByRole('link', { name: /^Cart/ }).click();
|
|
363
|
+
await expect(page).toHaveURL(/\/commerce\/cart(?:\/|$)/);
|
|
364
|
+
await expect(page.getByRole('heading', { name: 'Cart' })).toBeVisible();
|
|
365
|
+
const cartItem = page.locator('article').filter({ has: page.getByRole('spinbutton') });
|
|
366
|
+
await expect(cartItem).toContainText('COF-SET-01');
|
|
367
|
+
await expect(cartItem).toContainText('$45.99');
|
|
368
|
+
await expect(cartItem.getByRole('spinbutton')).toHaveValue('1');
|
|
369
|
+
|
|
370
|
+
await page.getByRole('link', { name: 'Checkout', exact: true }).click();
|
|
371
|
+
await expect(page).toHaveURL(/\/commerce\/checkout(?:\/|$)/);
|
|
372
|
+
await expect(page.getByRole('heading', { name: 'Checkout' })).toBeVisible();
|
|
373
|
+
const addressChoice = page.getByRole('radio', { name: new RegExp(fixture.recipientName) });
|
|
374
|
+
await expect(addressChoice).toBeVisible();
|
|
375
|
+
await addressChoice.check();
|
|
376
|
+
const noCouponChoice = page.getByRole('radio', { name: 'No coupon', exact: true });
|
|
377
|
+
await noCouponChoice.check();
|
|
378
|
+
await expect(noCouponChoice).toBeChecked();
|
|
379
|
+
await expect(page.getByRole('button', { name: 'Create order', exact: true })).toBeEnabled();
|
|
380
|
+
|
|
381
|
+
const checkoutResponse = waitForApiResponse(page, 'POST', '/api/commerce/trade/checkout');
|
|
382
|
+
await page.getByRole('button', { name: 'Create order', exact: true }).click();
|
|
383
|
+
const checkoutResponseValue = await checkoutResponse;
|
|
384
|
+
expect(checkoutResponseValue.ok()).toBeTruthy();
|
|
385
|
+
const checkout = (await checkoutResponseValue.json()).data;
|
|
386
|
+
expect(checkout.orderId).toEqual(expect.any(Number));
|
|
387
|
+
expect(checkout.paymentAttemptId).toEqual(expect.any(Number));
|
|
388
|
+
expect(checkout.state).toBe('awaiting_payment');
|
|
389
|
+
expect(checkout.paymentAttemptState).toBe('created');
|
|
390
|
+
expect(checkout.currency).toBe('USD');
|
|
391
|
+
expect(checkout.payableTotalCents).toBe(4599);
|
|
392
|
+
await expect(page).toHaveURL(
|
|
393
|
+
new RegExp(`/commerce/payment/${checkout.paymentAttemptId}(?:/|$)`),
|
|
394
|
+
);
|
|
395
|
+
await expect(page.getByRole('heading', { name: 'Mock payment' })).toBeVisible();
|
|
396
|
+
|
|
397
|
+
const paymentResponse = waitForApiResponse(
|
|
398
|
+
page,
|
|
399
|
+
'POST',
|
|
400
|
+
new RegExp(`/api/commerce/trade/payment/${checkout.paymentAttemptId}/outcome$`),
|
|
401
|
+
);
|
|
402
|
+
const orderDetailResponse = waitForApiResponse(
|
|
403
|
+
page,
|
|
404
|
+
'GET',
|
|
405
|
+
new RegExp('/api/commerce/trade/order/viewMine/\\d+$'),
|
|
406
|
+
);
|
|
407
|
+
await page.getByRole('button', { name: 'Payment succeeded', exact: true }).click();
|
|
408
|
+
const paymentResponseValue = await paymentResponse;
|
|
409
|
+
expect(paymentResponseValue.ok()).toBeTruthy();
|
|
410
|
+
const payment = (await paymentResponseValue.json()).data;
|
|
411
|
+
expect(payment.orderId).toBe(checkout.orderId);
|
|
412
|
+
expect(payment.paymentAttemptId).toBe(checkout.paymentAttemptId);
|
|
413
|
+
expect(payment.orderState).toBe('paid');
|
|
414
|
+
expect(payment.paymentAttemptState).toBe('succeeded');
|
|
415
|
+
expect(payment.currency).toBe('USD');
|
|
416
|
+
expect(payment.payableTotalCents).toBe(4599);
|
|
417
|
+
await expect(page).toHaveURL(new RegExp(`/commerce/order/${checkout.orderId}(?:/|$)`));
|
|
418
|
+
const orderDetailResponseValue = await orderDetailResponse;
|
|
419
|
+
expect(orderDetailResponseValue.ok()).toBeTruthy();
|
|
420
|
+
const order = (await orderDetailResponseValue.json()).data;
|
|
421
|
+
expect(order.id).toBe(checkout.orderId);
|
|
422
|
+
expect(order.state).toBe('paid');
|
|
423
|
+
expect(order.currency).toBe('USD');
|
|
424
|
+
expect(order.discountCents).toBe(0);
|
|
425
|
+
expect(order.payableTotalCents).toBe(4599);
|
|
426
|
+
expect(order.addressSnapshot.recipientName).toBe(fixture.recipientName);
|
|
427
|
+
expect(order.addressSnapshot.addressLine1).toBe(fixture.addressLine1);
|
|
428
|
+
expect(order.lines).toEqual([
|
|
429
|
+
expect.objectContaining({
|
|
430
|
+
titleSnapshot: 'Pour-Over Coffee Set',
|
|
431
|
+
skuCodeSnapshot: 'COF-SET-01',
|
|
432
|
+
unitPriceCents: 4599,
|
|
433
|
+
quantity: 1,
|
|
434
|
+
lineTotalCents: 4599,
|
|
435
|
+
}),
|
|
436
|
+
]);
|
|
437
|
+
|
|
438
|
+
await expect(page.getByRole('heading', { name: `Order #${checkout.orderId}` })).toBeVisible();
|
|
439
|
+
await expect(page.getByText('paid · $45.99')).toBeVisible();
|
|
440
|
+
await expect(page.getByText(fixture.recipientName)).toBeVisible();
|
|
441
|
+
await expect(page.getByText(fixture.addressLine1)).toBeVisible();
|
|
442
|
+
await expect(page.getByText('Discount: $0.00')).toBeVisible();
|
|
443
|
+
await expect(page.getByRole('heading', { name: 'Pour-Over Coffee Set' })).toBeVisible();
|
|
444
|
+
await expect(page.getByText('COF-SET-01')).toBeVisible();
|
|
445
|
+
await expect(page.getByText('1 × $45.99 = $45.99')).toBeVisible();
|
|
446
|
+
|
|
447
|
+
const shipmentCarrier = 'Cabloy Express';
|
|
448
|
+
const shipmentTrackingNumber = `E2E-${checkout.orderId}`;
|
|
449
|
+
const adminContext = await browser.newContext();
|
|
450
|
+
const adminPage = await adminContext.newPage();
|
|
451
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
452
|
+
try {
|
|
453
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
454
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
455
|
+
await adminPage.goto('/commerce-admin/rest/resource/commerce-trade%3Aorder', {
|
|
456
|
+
waitUntil: 'load',
|
|
457
|
+
});
|
|
458
|
+
await expect(adminPage.locator('html')).toHaveAttribute(
|
|
459
|
+
'data-zova-hydrated',
|
|
460
|
+
'commerceAdmin',
|
|
461
|
+
);
|
|
462
|
+
const orderRow = adminPage
|
|
463
|
+
.getByRole('cell', { name: String(checkout.orderId), exact: true })
|
|
464
|
+
.locator('..');
|
|
465
|
+
await expect(orderRow).toBeVisible();
|
|
466
|
+
const carrierInput = orderRow.getByPlaceholder('Carrier');
|
|
467
|
+
const trackingNumberInput = orderRow.getByPlaceholder('Tracking number');
|
|
468
|
+
const confirmationCheckbox = orderRow.getByRole('checkbox');
|
|
469
|
+
await carrierInput.fill(shipmentCarrier);
|
|
470
|
+
await trackingNumberInput.fill(shipmentTrackingNumber);
|
|
471
|
+
await confirmationCheckbox.check();
|
|
472
|
+
await expect(carrierInput).toHaveValue(shipmentCarrier);
|
|
473
|
+
await expect(trackingNumberInput).toHaveValue(shipmentTrackingNumber);
|
|
474
|
+
await expect(confirmationCheckbox).toBeChecked();
|
|
475
|
+
const shipmentResponse = waitForApiResponse(
|
|
476
|
+
adminPage,
|
|
477
|
+
'POST',
|
|
478
|
+
`/api/commerce/trade/order/${checkout.orderId}/ship`,
|
|
479
|
+
);
|
|
480
|
+
await orderRow.getByRole('button', { name: 'Ship order', exact: true }).click();
|
|
481
|
+
expect((await shipmentResponse).ok()).toBeTruthy();
|
|
482
|
+
await expect(orderRow).toContainText('shipped');
|
|
483
|
+
expect(adminPageErrors).toEqual([]);
|
|
484
|
+
} finally {
|
|
485
|
+
await adminContext.close().catch(() => {});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const shippedDetailResponse = waitForApiResponse(
|
|
489
|
+
page,
|
|
490
|
+
'GET',
|
|
491
|
+
new RegExp(`/api/commerce/trade/order/viewMine/${checkout.orderId}$`),
|
|
492
|
+
);
|
|
493
|
+
await page.reload({ waitUntil: 'load' });
|
|
494
|
+
await shippedDetailResponse;
|
|
495
|
+
await expect(page.getByText('shipped · $45.99')).toBeVisible();
|
|
496
|
+
await expect(page.getByRole('heading', { name: 'Shipment' })).toBeVisible();
|
|
497
|
+
await expect(page.getByText(shipmentCarrier)).toBeVisible();
|
|
498
|
+
await expect(page.getByText(shipmentTrackingNumber)).toBeVisible();
|
|
499
|
+
|
|
500
|
+
const ordersResponse = waitForApiResponse(page, 'GET', '/api/commerce/trade/order/mine');
|
|
501
|
+
await page.goto('/commerce/orders', { waitUntil: 'load' });
|
|
502
|
+
await expect(page).toHaveURL(/\/commerce\/orders(?:\/|$)/);
|
|
503
|
+
await ordersResponse;
|
|
504
|
+
await expect(page.getByRole('heading', { name: 'My orders' })).toBeVisible();
|
|
505
|
+
const orderCard = page
|
|
506
|
+
.locator('article')
|
|
507
|
+
.filter({ has: page.getByRole('heading', { name: `Order #${checkout.orderId}` }) });
|
|
508
|
+
await expect(orderCard).toContainText('shipped · $45.99');
|
|
509
|
+
|
|
510
|
+
const historyDetailResponse = waitForApiResponse(
|
|
511
|
+
page,
|
|
512
|
+
'GET',
|
|
513
|
+
new RegExp(`/api/commerce/trade/order/viewMine/${checkout.orderId}$`),
|
|
514
|
+
);
|
|
515
|
+
await orderCard.getByRole('button', { name: 'View', exact: true }).click();
|
|
516
|
+
await expect(page).toHaveURL(new RegExp(`/commerce/order/${checkout.orderId}(?:/|$)`));
|
|
517
|
+
await historyDetailResponse;
|
|
518
|
+
await expect(page.getByRole('heading', { name: `Order #${checkout.orderId}` })).toBeVisible();
|
|
519
|
+
await expect(page.getByText('1 × $45.99 = $45.99')).toBeVisible();
|
|
520
|
+
await expect(page.getByRole('alert')).toHaveCount(0);
|
|
521
|
+
expect(pageErrors).toEqual([]);
|
|
522
|
+
} finally {
|
|
523
|
+
await context.close().catch(() => {});
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
test(
|
|
529
|
+
'ATP-ADDR-01: systemAdmin inspects Address Resource without mutation controls',
|
|
530
|
+
{ tag: ['@admin', '@flow', '@address'] },
|
|
531
|
+
async ({ browser, request }, testInfo) => {
|
|
532
|
+
test.setTimeout(60_000);
|
|
533
|
+
const customer = await createAddressThroughCustomerPage(browser, request, testInfo);
|
|
534
|
+
const adminContext = await browser.newContext();
|
|
535
|
+
const adminPage = await adminContext.newPage();
|
|
536
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
537
|
+
const addressMutationMethods: string[] = [];
|
|
538
|
+
adminPage.on('request', request => {
|
|
539
|
+
const url = new URL(request.url());
|
|
540
|
+
if (
|
|
541
|
+
url.pathname.startsWith(addressActionPath) &&
|
|
542
|
+
['POST', 'PATCH', 'DELETE'].includes(request.method())
|
|
543
|
+
) {
|
|
544
|
+
addressMutationMethods.push(request.method());
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
550
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
551
|
+
await adminPage.goto('/commerce-admin/rest/resource/commerce-member%3Aaddress', {
|
|
552
|
+
waitUntil: 'load',
|
|
553
|
+
});
|
|
554
|
+
await expect(adminPage).toHaveURL(addressResourceUrl);
|
|
555
|
+
const fixtureRow = adminPage.getByRole('row', { name: customer.fixture.recipientName });
|
|
556
|
+
await expect(fixtureRow).toBeVisible();
|
|
557
|
+
await expect(adminPage.getByRole('button', { name: 'Create', exact: true })).toHaveCount(0);
|
|
558
|
+
await expect(
|
|
559
|
+
adminPage.locator('a[href*="/rest/resource/"]').filter({ hasText: 'Create' }),
|
|
560
|
+
).toHaveCount(0);
|
|
561
|
+
|
|
562
|
+
const addressId = await fixtureRow.getByRole('cell').first().textContent();
|
|
563
|
+
expect(addressId).toMatch(/^\d+$/);
|
|
564
|
+
await adminPage.goto(`/commerce-admin/rest/resource/commerce-member%3Aaddress/${addressId}`, {
|
|
565
|
+
waitUntil: 'load',
|
|
566
|
+
});
|
|
567
|
+
await expect(adminPage).toHaveURL(
|
|
568
|
+
/\/commerce-admin\/rest\/resource\/commerce-member(?:%3A|:|%253A)address\/\d+(?:[/?#]|$)/,
|
|
569
|
+
);
|
|
570
|
+
await expect(
|
|
571
|
+
adminPage.getByRole('group', { name: 'Address Line 1 *' }).getByRole('textbox'),
|
|
572
|
+
).toHaveValue(customer.fixture.addressLine1);
|
|
573
|
+
await expect(
|
|
574
|
+
adminPage.getByRole('group', { name: 'City *' }).getByRole('textbox'),
|
|
575
|
+
).toHaveValue(customer.fixture.city);
|
|
576
|
+
await expect(adminPage.getByRole('button', { name: 'Back', exact: true })).toBeVisible();
|
|
577
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
578
|
+
expect(addressMutationMethods).toEqual([]);
|
|
579
|
+
expect(adminPageErrors).toEqual([]);
|
|
580
|
+
|
|
581
|
+
await deleteAddressThroughCustomerPage(customer.page, customer.fixture);
|
|
582
|
+
await expect(customer.page.getByRole('alert')).toHaveCount(0);
|
|
583
|
+
expect(customer.pageErrors).toEqual([]);
|
|
584
|
+
} finally {
|
|
585
|
+
await adminContext.close().catch(() => {});
|
|
586
|
+
await customer.context.close().catch(() => {});
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
);
|
|
590
|
+
|
|
147
591
|
test(
|
|
148
592
|
'Commerce Cart: anonymous browser is redirected to login',
|
|
149
593
|
{ tag: ['@web', '@cart'] },
|
|
@@ -161,6 +605,40 @@ test(
|
|
|
161
605
|
},
|
|
162
606
|
);
|
|
163
607
|
|
|
608
|
+
test(
|
|
609
|
+
'Phase 50: anonymous payment and order routes remain SSR-neutral and protected',
|
|
610
|
+
{ tag: ['@web', '@flow'] },
|
|
611
|
+
async ({ page, request }) => {
|
|
612
|
+
const routes = [
|
|
613
|
+
['/commerce/checkout', '/checkout'],
|
|
614
|
+
['/commerce/payment/1', '/payment/1'],
|
|
615
|
+
['/commerce/orders', '/orders'],
|
|
616
|
+
['/commerce/order/1', '/order/1'],
|
|
617
|
+
] as const;
|
|
618
|
+
for (const [path, routePath] of routes) {
|
|
619
|
+
const response = await request.get(path, { maxRedirects: 0 });
|
|
620
|
+
expect(response.status(), path).toBe(200);
|
|
621
|
+
expect(response.headers().location, path).toBeUndefined();
|
|
622
|
+
const html = await response.text();
|
|
623
|
+
expect(html.toLowerCase(), path).not.toContain('data-zova-hydrated');
|
|
624
|
+
expect(html, path).not.toContain('Payment Customer');
|
|
625
|
+
expect(html, path).not.toContain('Payment succeeded');
|
|
626
|
+
expect(html, path).not.toContain('Order #');
|
|
627
|
+
expect(html, path).not.toContain('Checkout');
|
|
628
|
+
expect(html, path).not.toContain('Mock payment');
|
|
629
|
+
expect(html, path).not.toContain('My orders');
|
|
630
|
+
|
|
631
|
+
const pageErrors = collectPageErrors(page);
|
|
632
|
+
const documentResponse = await page.goto(path, { waitUntil: 'load' });
|
|
633
|
+
expect(documentResponse?.ok(), path).toBeTruthy();
|
|
634
|
+
await expect(page, path).toHaveURL(/\/commerce\/login\?(?:.*&)?returnTo=/);
|
|
635
|
+
const loginUrl = new URL(page.url());
|
|
636
|
+
expect(loginUrl.searchParams.getAll('returnTo'), path).toEqual([routePath]);
|
|
637
|
+
expect(pageErrors, path).toEqual([]);
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
);
|
|
641
|
+
|
|
164
642
|
test(
|
|
165
643
|
'ATP-SSR-02: Commerce Admin is an independent SSR site',
|
|
166
644
|
{ tag: ['@admin', '@smoke'] },
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import { $makeMetadata } from 'vona-module-a-openapiutils';
|
|
|
4
4
|
import { $Dto } from 'vona-module-a-orm';
|
|
5
5
|
import { Dto } from 'vona-module-a-web';
|
|
6
6
|
import { Model<%=argv.detailResourceNameCapitalize%> } from '<%=argv.detailPackageName%>';
|
|
7
|
-
import { ZovaRender } from '
|
|
7
|
+
import { ZovaRender } from '<%=argv.restAdminNpm%>';
|
|
8
8
|
|
|
9
9
|
export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> extends IDecoratorDtoOptions {}
|
|
10
10
|
|
|
@@ -3,14 +3,14 @@ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
|
3
3
|
import { $Dto } from 'vona-module-a-orm';
|
|
4
4
|
import { Dto } from 'vona-module-a-web';
|
|
5
5
|
import { Model<%=argv.detailResourceNameCapitalize%> } from '<%=argv.detailPackageName%>';
|
|
6
|
-
import { ZovaRender } from '
|
|
6
|
+
import { ZovaRender } from '<%=argv.restAdminNpm%>';
|
|
7
7
|
|
|
8
8
|
import { Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> } from './<%=argv.detailDtoBaseName%>.tsx';
|
|
9
9
|
|
|
10
10
|
export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoMutateName)%> extends IDecoratorDtoOptions {}
|
|
11
11
|
|
|
12
12
|
@Dto<IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoMutateName)%>>({
|
|
13
|
-
blocks: [ZovaRender.block('
|
|
13
|
+
blocks: [ZovaRender.block('<%=argv.detailsModuleName%>:blockForm')],
|
|
14
14
|
})
|
|
15
15
|
export class Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoMutateName)%> extends $Dto.mutate(() => Model<%=argv.detailResourceNameCapitalize%>, {
|
|
16
16
|
dtoClass: Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%>,
|
|
@@ -4,7 +4,7 @@ import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';
|
|
|
4
4
|
import { $Dto } from 'vona-module-a-orm';
|
|
5
5
|
import { Dto } from 'vona-module-a-web';
|
|
6
6
|
import { Model<%=argv.detailResourceNameCapitalize%> } from '<%=argv.detailPackageName%>';
|
|
7
|
-
import { ZovaRender } from '
|
|
7
|
+
import { ZovaRender } from '<%=argv.restAdminNpm%>';
|
|
8
8
|
|
|
9
9
|
import { $locale } from '../.metadata/locales.ts';
|
|
10
10
|
import { Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> } from './<%=argv.detailDtoBaseName%>.tsx';
|
|
@@ -13,23 +13,23 @@ export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoRes
|
|
|
13
13
|
|
|
14
14
|
@Dto<IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoResItemName)%>>({
|
|
15
15
|
blocks: [
|
|
16
|
-
ZovaRender.block('
|
|
16
|
+
ZovaRender.block('<%=argv.detailsModuleName%>:blockDetails', {
|
|
17
17
|
blocks: [
|
|
18
|
-
ZovaRender.block('
|
|
18
|
+
ZovaRender.block('<%=argv.detailsModuleName%>:blockToolbarBulk', {
|
|
19
19
|
actions: [
|
|
20
|
-
ZovaRender.detailsActionBulk('
|
|
20
|
+
ZovaRender.detailsActionBulk('<%=argv.detailsModuleName%>:actionCreate', {
|
|
21
21
|
dialogOptions: { title: $locale('Add<%=argv.detailDialogTitleCapitalize%>') },
|
|
22
22
|
permission: { formScene: ['create', 'edit'] },
|
|
23
23
|
}),
|
|
24
24
|
],
|
|
25
25
|
}),
|
|
26
|
-
ZovaRender.block('
|
|
26
|
+
ZovaRender.block('<%=argv.detailsModuleName%>:blockTable'),
|
|
27
27
|
],
|
|
28
28
|
}),
|
|
29
29
|
],
|
|
30
30
|
fields: {
|
|
31
31
|
name: $makeMetadata(
|
|
32
|
-
ZovaRender.cell('
|
|
32
|
+
ZovaRender.cell('<%=argv.detailsModuleName%>:actionView', {
|
|
33
33
|
dialogOptions: { title: $locale('View<%=argv.detailDialogTitleCapitalize%>') },
|
|
34
34
|
}),
|
|
35
35
|
),
|
|
@@ -38,19 +38,19 @@ export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoRes
|
|
|
38
38
|
export class Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoResItemName)%> extends $Dto.get(() => Model<%=argv.detailResourceNameCapitalize%>, {
|
|
39
39
|
dtoClass: Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%>,
|
|
40
40
|
}) {
|
|
41
|
-
@Api.field(v.title('#'), ZovaRender.order(1, 'core'), ZovaRender.cell('
|
|
41
|
+
@Api.field(v.title('#'), ZovaRender.order(1, 'core'), ZovaRender.cell('<%=argv.detailsModuleName%>:lineNumber'))
|
|
42
42
|
_lineNumber?: number;
|
|
43
43
|
|
|
44
44
|
@Api.field(
|
|
45
45
|
v.title($locale('Operations')),
|
|
46
46
|
ZovaRender.order(1, 'max'),
|
|
47
|
-
ZovaRender.cell('
|
|
47
|
+
ZovaRender.cell('<%=argv.detailsModuleName%>:actionOperationsRow', {
|
|
48
48
|
actions: [
|
|
49
|
-
ZovaRender.detailsActionRow('
|
|
49
|
+
ZovaRender.detailsActionRow('<%=argv.detailsModuleName%>:actionUpdate', {
|
|
50
50
|
dialogOptions: { title: $locale('Edit<%=argv.detailDialogTitleCapitalize%>') },
|
|
51
51
|
permission: { formScene: ['create', 'edit'] },
|
|
52
52
|
}),
|
|
53
|
-
ZovaRender.detailsActionRow('
|
|
53
|
+
ZovaRender.detailsActionRow('<%=argv.detailsModuleName%>:actionDelete', {
|
|
54
54
|
permission: { formScene: ['create', 'edit'] },
|
|
55
55
|
}),
|
|
56
56
|
],
|
|
@@ -3,14 +3,14 @@ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
|
3
3
|
import { $Dto } from 'vona-module-a-orm';
|
|
4
4
|
import { Dto } from 'vona-module-a-web';
|
|
5
5
|
import { Model<%=argv.detailResourceNameCapitalize%> } from '<%=argv.detailPackageName%>';
|
|
6
|
-
import { ZovaRender } from '
|
|
6
|
+
import { ZovaRender } from '<%=argv.restAdminNpm%>';
|
|
7
7
|
|
|
8
8
|
import { Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> } from './<%=argv.detailDtoBaseName%>.tsx';
|
|
9
9
|
|
|
10
10
|
export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoViewName)%> extends IDecoratorDtoOptions {}
|
|
11
11
|
|
|
12
12
|
@Dto<IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoViewName)%>>({
|
|
13
|
-
blocks: [ZovaRender.block('
|
|
13
|
+
blocks: [ZovaRender.block('<%=argv.detailsModuleName%>:blockForm')],
|
|
14
14
|
})
|
|
15
15
|
export class Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoViewName)%> extends $Dto.get(() => Model<%=argv.detailResourceNameCapitalize%>, {
|
|
16
16
|
dtoClass: Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%>,
|