cabloy 5.1.131 → 5.1.133
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-worktree-environment/SKILL.md +6 -6
- package/.claude/skills/cabloy-worktree-environment/evals/evals.json +2 -2
- package/.github/workflows/vona-test-pg.yml +1 -1
- package/.github/workflows/vona-test-sqlite3.yml +1 -1
- package/CHANGELOG.md +28 -0
- package/cabloy-docs/backend/dto-guide.md +39 -4
- package/cabloy-docs/backend/dto-infer-generation.md +29 -0
- package/cabloy-docs/backend/openapi-guide.md +16 -0
- package/cabloy-docs/backend/validation-guide.md +45 -0
- package/cabloy-docs/frontend/environment-config-guide.md +8 -15
- package/cabloy-docs/frontend/navigation-guards-guide.md +3 -3
- package/cabloy-docs/frontend/page-route-guide.md +4 -4
- package/cabloy-docs/frontend/ssr-env.md +22 -29
- package/cabloy-docs/frontend/theme-guide.md +4 -4
- package/cabloy-docs/frontend/zova-router-under-the-hood.md +1 -1
- package/cabloy-docs/fullstack/deploy-cloudflare-docker.md +6 -6
- package/cabloy-docs/fullstack/parallel-worktree-environment.md +2 -2
- package/e2e/specs/a-commerce/commerce.spec.ts +479 -17
- package/e2e/specs/cabloy-basic/basic.spec.ts +30 -5
- package/package.json +7 -7
- package/vona/.claude/skills/detect-ssr-leak/SKILL.md +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +120 -10
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuCreate.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectResItem.tsx +13 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuUpdate.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuView.tsx +13 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/sku.tsx +6 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/skuPresentation.test.ts +227 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +46 -36
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressSelectResItem.tsx +5 -13
- package/vona/src/suite/a-commerce/modules/commerce-member/src/service/address.ts +16 -1
- package/vona/src/suite/a-commerce/modules/commerce-member/test/addressPresentation.test.ts +210 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/commercePayScene.ts +13 -2
- package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +12 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/service/coupon.ts +7 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +45 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +23 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +23 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/order.ts +31 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderAdminLineBase.tsx +56 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderAdminLineResItem.tsx +17 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderAdminLineView.tsx +13 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSelectResItem.tsx +26 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderView.tsx +197 -3
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundRecoveryAction.tsx +19 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundRecoveryView.tsx +70 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAuditSelectResItem.tsx +7 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAuditView.tsx +21 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockBalanceSelectResItem.tsx +7 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockBalanceView.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/order.tsx +12 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +15 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +215 -3
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/stockAudit.ts +25 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/stockBalance.ts +14 -3
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutReservation.test.ts +4 -4
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderPresentation.test.ts +358 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +8 -8
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +5 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +50 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockPresentation.test.ts +324 -0
- package/vona/src/suite/a-home/modules/home-base/package.json +3 -2
- package/vona/src/suite/a-home/modules/home-base/src/.metadata/index.ts +93 -16
- package/vona/src/suite/a-home/modules/home-base/src/controller/siteCatalog.ts +21 -0
- package/vona/src/suite/a-home/modules/home-base/src/dto/siteCatalogSelectRes.ts +11 -0
- package/vona/src/suite/a-home/modules/home-base/src/dto/siteCatalogSelectResItem.ts +15 -0
- package/vona/src/suite/a-home/modules/home-base/src/service/siteCatalog.ts +29 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrSite.admin.ts +3 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/bean/ssrSite.web.ts +3 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrSite/boilerplate/{{sceneName}}.{{beanName}}.ts_ +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/env.ts +1 -3
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrSite.ts +2 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
- 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 +67 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payProvider.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payScene.ts +5 -4
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.index.ts +8 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.version.ts +22 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/schedule.providerOperationDispatch.ts +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/paymentSession.tsx +6 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperation.tsx +3 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperationRecoveryAudit.tsx +53 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/refundOperation.tsx +6 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/providerOperationRecoveryAudit.ts +12 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts +6 -2
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +236 -30
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +8 -5
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/webhook.ts +34 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payProvider.ts +4 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +6 -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 +15 -7
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/service/payMock.ts +9 -5
- 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 +289 -86
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/paypalGateway.ts +9 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- 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/rest.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/utils.ts +12 -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/dto/dtoCreate.ts +5 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoUpdate.ts +5 -1
- package/vona/src/suite-vendor/a-vona/modules/a-validation/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-validation/src/bean/bean.validator.ts +60 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/decorator/bean.ts +7 -4
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/types/dto.ts +2 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/env/.env +3 -5
- package/zova/env/.env.cabloyBasicAdmin +1 -4
- package/zova/env/.env.cabloyBasicWeb +1 -5
- package/zova/env/.env.cabloyCommerce +1 -5
- package/zova/env/.env.cabloyCommerceAdmin +1 -4
- package/zova/env/.env.ssr.admin +1 -4
- package/zova/env/.env.ssr.web +1 -5
- package/zova/package.original.json +1 -1
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +2 -1
- package/zova/packages-zova/zova-core/src/core/component/locale.ts +10 -4
- package/zova/packages-zova/zova-core/src/core/sys/config.ts +14 -6
- package/zova/packages-zova/zova-core/src/types/utils/env.ts +4 -2
- package/zova/pnpm-lock.yaml +24 -21
- package/zova/src/front/config/config/config.ts +22 -7
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/routes.ts +2 -2
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +16 -8
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +150 -216
- package/zova/src/suite/a-commerce/modules/commerce-member/src/model/addressMine.ts +1 -2
- package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +5 -8
- package/zova/src/suite/a-commerce/modules/commerce-member/src/routes.ts +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/model/coupon.ts +1 -2
- package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/routes.ts +3 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +3 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeOrder.ts +72 -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 +226 -16
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeOrder.ts +15 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionRefund/controller.tsx +155 -36
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +7 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +7 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +3 -4
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +42 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/orderMine.ts +2 -4
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +5 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +14 -10
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +5 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/orders/controller.tsx +5 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +151 -24
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +5 -5
- package/zova/src/suite/a-demo/modules/demo-basic/src/routes.ts +26 -7
- package/zova/src/suite/a-demo/modules/demo-todo/src/routes.ts +2 -2
- package/zova/src/suite/a-devui/modules/devui-adapter/src/bean/meta.themeHandler.ts +1 -1
- package/zova/src/suite/a-home/modules/home-base/package.json +1 -0
- package/zova/src/suite/a-home/modules/home-base/src/routes.ts +5 -0
- package/zova/src/suite/a-home/modules/home-base/src/service/routerGuards.ts +1 -1
- package/zova/src/suite/a-home/modules/home-base/src/service/ssrLayout.ts +18 -14
- package/zova/src/suite/a-home/modules/home-indexadmin/src/routes.ts +1 -1
- package/zova/src/suite/a-home/modules/home-indexweb/src/routes.ts +1 -0
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +12 -4
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/config/config.ts +8 -0
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/model/layout.ts +5 -8
- package/zova/src/suite/a-home/modules/home-layoutempty/src/.metadata/index.ts +10 -3
- package/zova/src/suite/a-home/modules/home-layoutempty/src/component/layoutEmpty/controller.tsx +9 -3
- package/zova/src/suite/a-home/modules/home-layoutempty/src/config/config.ts +12 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +13 -8
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.header.tsx +1 -1
- package/zova/src/suite/a-home/modules/home-layoutweb/src/config/config.ts +4 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/model/layout.ts +5 -8
- package/zova/src/suite/a-home/modules/home-login/src/routes.ts +1 -0
- package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +2 -3
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/routes.ts +3 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/routes.ts +3 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- 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/model/paymentSession.ts +2 -2
- package/zova/src/suite-vendor/a-pay/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-interceptor/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-interceptor/src/bean/interceptor.jwt.ts +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.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/config/config.ts +1 -17
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/bodyReadyObserver.ts +27 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/index.ts +2 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +33 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssrMetaStore.ts +3 -35
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssrProfile.ts +38 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +10 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/types/config.ts +3 -10
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/types/ssr.ts +17 -8
- package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/src/service/ssrHandler.ts +33 -18
- package/zova/src/suite-vendor/a-zova/modules/a-style/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-style/src/bean/bean.theme.ts +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +7 -7
|
@@ -11,8 +11,13 @@ const passportTestActivateCurrentPath = '/api/home/user/passportTest/activateCur
|
|
|
11
11
|
|
|
12
12
|
interface IAddressFixture {
|
|
13
13
|
addressLine1: string;
|
|
14
|
+
addressLine2: string;
|
|
14
15
|
city: string;
|
|
16
|
+
countryCode: string;
|
|
17
|
+
phone: string;
|
|
18
|
+
postalCode: string;
|
|
15
19
|
recipientName: string;
|
|
20
|
+
region: string;
|
|
16
21
|
updatedCity: string;
|
|
17
22
|
}
|
|
18
23
|
|
|
@@ -48,7 +53,7 @@ function waitForAddressMine(page: Page) {
|
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
async function getAdminOrderRow(page: Page, orderId: number): Promise<Locator> {
|
|
51
|
-
const row = page.locator(`tr:has(td:
|
|
56
|
+
const row = page.locator(`tr:has(td a:text-is("${orderId}"))`);
|
|
52
57
|
await expect(row).toHaveCount(1);
|
|
53
58
|
return row;
|
|
54
59
|
}
|
|
@@ -123,21 +128,26 @@ function createAddressFixture(testInfo: TestInfo): IAddressFixture {
|
|
|
123
128
|
const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
124
129
|
return {
|
|
125
130
|
recipientName: `E2E Address ${id}`,
|
|
126
|
-
|
|
131
|
+
phone: '15555550123',
|
|
132
|
+
countryCode: 'US',
|
|
133
|
+
region: 'California',
|
|
127
134
|
city: `Original City ${id}`,
|
|
135
|
+
postalCode: '94105',
|
|
136
|
+
addressLine1: `1 Test Street ${id}`,
|
|
137
|
+
addressLine2: 'Suite E2E',
|
|
128
138
|
updatedCity: `Updated City ${id}`,
|
|
129
139
|
};
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
async function fillAddressForm(page: Page, fixture: IAddressFixture) {
|
|
133
143
|
await page.getByPlaceholder('Recipient name').fill(fixture.recipientName);
|
|
134
|
-
await page.getByPlaceholder('Phone').fill(
|
|
135
|
-
await page.getByPlaceholder('Country code').fill(
|
|
136
|
-
await page.getByPlaceholder('Region').fill(
|
|
144
|
+
await page.getByPlaceholder('Phone').fill(fixture.phone);
|
|
145
|
+
await page.getByPlaceholder('Country code').fill(fixture.countryCode);
|
|
146
|
+
await page.getByPlaceholder('Region').fill(fixture.region);
|
|
137
147
|
await page.getByPlaceholder('City').fill(fixture.city);
|
|
138
|
-
await page.getByPlaceholder('Postal code').fill(
|
|
148
|
+
await page.getByPlaceholder('Postal code').fill(fixture.postalCode);
|
|
139
149
|
await page.getByPlaceholder('Address line 1').fill(fixture.addressLine1);
|
|
140
|
-
await page.getByPlaceholder('Address line 2').fill(
|
|
150
|
+
await page.getByPlaceholder('Address line 2').fill(fixture.addressLine2);
|
|
141
151
|
}
|
|
142
152
|
|
|
143
153
|
async function createAddressThroughCustomerPage(
|
|
@@ -186,6 +196,7 @@ test(
|
|
|
186
196
|
async ({ page, request }) => {
|
|
187
197
|
const response = await request.get('/commerce');
|
|
188
198
|
expect(response.ok()).toBeTruthy();
|
|
199
|
+
expect(response.headers()['cache-control']).toBe('public, max-age=600');
|
|
189
200
|
const html = await response.text();
|
|
190
201
|
const normalizedHtml = html.toLowerCase();
|
|
191
202
|
expect(normalizedHtml).not.toContain('data-zova-hydrated');
|
|
@@ -292,15 +303,18 @@ test(
|
|
|
292
303
|
);
|
|
293
304
|
|
|
294
305
|
test(
|
|
295
|
-
'Commerce Address:
|
|
306
|
+
'Commerce Address: session SSR protects an anonymous document request',
|
|
296
307
|
{ tag: ['@web', '@cart'] },
|
|
297
308
|
async ({ page, request }) => {
|
|
298
309
|
const path = '/commerce/address';
|
|
299
310
|
const routePath = '/address';
|
|
300
311
|
const response = await request.get(path, { maxRedirects: 0 });
|
|
301
|
-
expect(response.status()).toBe(
|
|
302
|
-
expect(response.headers()
|
|
303
|
-
|
|
312
|
+
expect(response.status()).toBe(302);
|
|
313
|
+
expect(response.headers()['cache-control']).toBe('private, no-store');
|
|
314
|
+
const html = await response.text();
|
|
315
|
+
expect(html).not.toContain('ssr-body-ready-observer');
|
|
316
|
+
expect(html).not.toContain('__leftDrawerOpenJS');
|
|
317
|
+
expect(response.headers().location).toMatch(/^\/commerce\/login\?(?:.*&)?returnTo=/);
|
|
304
318
|
|
|
305
319
|
const pageErrors = collectPageErrors(page);
|
|
306
320
|
const documentResponse = await page.goto(path, { waitUntil: 'load' });
|
|
@@ -324,6 +338,33 @@ test(
|
|
|
324
338
|
testInfo,
|
|
325
339
|
);
|
|
326
340
|
try {
|
|
341
|
+
const addressResponse = await context.request.get('/commerce/address');
|
|
342
|
+
expect(addressResponse.ok()).toBeTruthy();
|
|
343
|
+
expect(addressResponse.headers()['cache-control']).toBe('private, no-store');
|
|
344
|
+
const addressHtml = await addressResponse.text();
|
|
345
|
+
expect(addressHtml.toLowerCase()).not.toContain('data-zova-hydrated');
|
|
346
|
+
expect(addressHtml).not.toContain('ssr-body-ready-observer');
|
|
347
|
+
expect(addressHtml).not.toContain('__leftDrawerOpenJS');
|
|
348
|
+
expect(addressHtml).toContain(fixture.recipientName);
|
|
349
|
+
expect(addressHtml).toContain(fixture.addressLine1);
|
|
350
|
+
|
|
351
|
+
const coldPage = await context.newPage();
|
|
352
|
+
try {
|
|
353
|
+
const coldPageErrors = collectPageErrors(coldPage);
|
|
354
|
+
const coldResponse = await coldPage.goto('/commerce/address', { waitUntil: 'load' });
|
|
355
|
+
expect(coldResponse?.ok()).toBeTruthy();
|
|
356
|
+
await expect(coldPage.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
357
|
+
await expect(coldPage.getByRole('heading', { name: 'Addresses' })).toBeVisible();
|
|
358
|
+
const coldCard = coldPage
|
|
359
|
+
.locator('article')
|
|
360
|
+
.filter({ has: coldPage.getByRole('heading', { name: fixture.recipientName }) });
|
|
361
|
+
await expect(coldCard).toContainText(fixture.addressLine1);
|
|
362
|
+
await expect(coldPage.getByRole('alert')).toHaveCount(0);
|
|
363
|
+
expect(coldPageErrors).toEqual([]);
|
|
364
|
+
} finally {
|
|
365
|
+
await coldPage.close();
|
|
366
|
+
}
|
|
367
|
+
|
|
327
368
|
const card = page
|
|
328
369
|
.locator('article')
|
|
329
370
|
.filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
@@ -483,6 +524,27 @@ test(
|
|
|
483
524
|
);
|
|
484
525
|
const orderRow = await getAdminOrderRow(adminPage, checkout.orderId);
|
|
485
526
|
await expect(orderRow).toBeVisible();
|
|
527
|
+
await expect(orderRow.locator('td')).toHaveCount(6);
|
|
528
|
+
await expect(orderRow).toContainText('Paid');
|
|
529
|
+
await expect(orderRow).toContainText('45.99');
|
|
530
|
+
await expect(orderRow).not.toContainText(fixture.recipientName);
|
|
531
|
+
const orderViewResponse = waitForApiResponse(
|
|
532
|
+
adminPage,
|
|
533
|
+
'GET',
|
|
534
|
+
`/api/commerce/trade/order/${checkout.orderId}`,
|
|
535
|
+
);
|
|
536
|
+
await orderRow.getByRole('link', { name: String(checkout.orderId), exact: true }).click();
|
|
537
|
+
expect((await orderViewResponse).ok()).toBeTruthy();
|
|
538
|
+
await expect(adminPage.getByText('Money summary').first()).toBeVisible();
|
|
539
|
+
await expect(adminPage.getByText('Delivery address').first()).toBeVisible();
|
|
540
|
+
await expect(adminPage.getByText('Purchased lines').first()).toBeVisible();
|
|
541
|
+
await expect(adminPage.getByText('addressSnapshot').first()).toBeVisible();
|
|
542
|
+
await expect(adminPage.getByText('couponSnapshot').first()).toBeVisible();
|
|
543
|
+
await expect(adminPage.getByText('Pour-Over Coffee Set')).toBeVisible();
|
|
544
|
+
await expect(adminPage.getByText('COF-SET-01')).toBeVisible();
|
|
545
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
546
|
+
await adminPage.getByRole('button', { name: 'Back', exact: true }).click();
|
|
547
|
+
await expect(orderRow).toBeVisible();
|
|
486
548
|
const carrierInput = orderRow.getByPlaceholder('Carrier');
|
|
487
549
|
const trackingNumberInput = orderRow.getByPlaceholder('Tracking number');
|
|
488
550
|
const confirmationCheckbox = orderRow.getByRole('checkbox');
|
|
@@ -499,7 +561,7 @@ test(
|
|
|
499
561
|
);
|
|
500
562
|
await orderRow.getByRole('button', { name: 'Ship order', exact: true }).click();
|
|
501
563
|
expect((await shipmentResponse).ok()).toBeTruthy();
|
|
502
|
-
await expect(orderRow).toContainText('
|
|
564
|
+
await expect(orderRow).toContainText('Shipped');
|
|
503
565
|
expect(adminPageErrors).toEqual([]);
|
|
504
566
|
} finally {
|
|
505
567
|
await adminContext.close().catch(() => {});
|
|
@@ -545,6 +607,233 @@ test(
|
|
|
545
607
|
},
|
|
546
608
|
);
|
|
547
609
|
|
|
610
|
+
test(
|
|
611
|
+
'Payment callback: authenticated direct route reconciles after hydration',
|
|
612
|
+
{ tag: ['@web', '@flow', '@payment'] },
|
|
613
|
+
async ({ browser, request }, testInfo) => {
|
|
614
|
+
test.setTimeout(60_000);
|
|
615
|
+
const { context, fixture, page, pageErrors } = await createAddressThroughCustomerPage(
|
|
616
|
+
browser,
|
|
617
|
+
request,
|
|
618
|
+
testInfo,
|
|
619
|
+
);
|
|
620
|
+
try {
|
|
621
|
+
await page.goto('/commerce', { waitUntil: 'load' });
|
|
622
|
+
const productLink = page.getByRole('link', { name: 'Pour-Over Coffee Set', exact: true });
|
|
623
|
+
await expect(productLink).toBeVisible();
|
|
624
|
+
await productLink.click();
|
|
625
|
+
await expect(page).toHaveURL(/\/commerce\/product\/\d+(?:\/|$)/);
|
|
626
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
627
|
+
await expect(page.getByRole('heading', { name: 'Pour-Over Coffee Set' })).toBeVisible();
|
|
628
|
+
await expect(page.getByText('COF-SET-01')).toBeVisible();
|
|
629
|
+
const addToCartButton = page.getByRole('button', { name: 'Add to cart', exact: true });
|
|
630
|
+
await expect(addToCartButton).toBeEnabled();
|
|
631
|
+
const addResponse = waitForApiResponse(page, 'POST', '/api/commerce/trade/cart/items');
|
|
632
|
+
await addToCartButton.click();
|
|
633
|
+
expect((await addResponse).ok()).toBeTruthy();
|
|
634
|
+
await page.getByRole('link', { name: /^Cart/ }).click();
|
|
635
|
+
await page.getByRole('link', { name: 'Checkout', exact: true }).click();
|
|
636
|
+
const addressChoice = page.getByRole('radio', { name: new RegExp(fixture.recipientName) });
|
|
637
|
+
await expect(addressChoice).toBeVisible();
|
|
638
|
+
await addressChoice.check();
|
|
639
|
+
await page.getByRole('radio', { name: 'No coupon', exact: true }).check();
|
|
640
|
+
const checkoutResponse = waitForApiResponse(page, 'POST', '/api/commerce/trade/checkout');
|
|
641
|
+
await page.getByRole('button', { name: 'Create order', exact: true }).click();
|
|
642
|
+
const checkoutResponseValue = await checkoutResponse;
|
|
643
|
+
expect(checkoutResponseValue.ok()).toBeTruthy();
|
|
644
|
+
const checkout = (await checkoutResponseValue.json()).data;
|
|
645
|
+
expectTableIdentity(checkout.orderId);
|
|
646
|
+
expectTableIdentity(checkout.paymentSessionId);
|
|
647
|
+
const startResponse = waitForApiResponse(
|
|
648
|
+
page,
|
|
649
|
+
'POST',
|
|
650
|
+
new RegExp(`/api/pay/payment-session/${checkout.paymentSessionId}/start$`),
|
|
651
|
+
);
|
|
652
|
+
await page.getByRole('button', { name: 'Start payment', exact: true }).click();
|
|
653
|
+
expect((await startResponse).ok()).toBeTruthy();
|
|
654
|
+
await expect(page.getByRole('heading', { name: 'Mock payment simulator' })).toBeVisible();
|
|
655
|
+
const completeResponse = waitForApiResponse(
|
|
656
|
+
page,
|
|
657
|
+
'POST',
|
|
658
|
+
new RegExp(`/api/pay/mock/payment-session/${checkout.paymentSessionId}/complete$`),
|
|
659
|
+
);
|
|
660
|
+
await page.getByRole('button', { name: 'Payment succeeded', exact: true }).click();
|
|
661
|
+
expect((await completeResponse).ok()).toBeTruthy();
|
|
662
|
+
await expect(page).toHaveURL(new RegExp(`/commerce/order/${checkout.orderId}(?:/|$)`), {
|
|
663
|
+
timeout: 40_000,
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
const callbackPath = `/commerce/payment/${checkout.paymentSessionId}/${checkout.orderId}?providerResult=return`;
|
|
667
|
+
await page.evaluate(() => {
|
|
668
|
+
localStorage.clear();
|
|
669
|
+
sessionStorage.clear();
|
|
670
|
+
});
|
|
671
|
+
const callbackResponse = await context.request.get(callbackPath);
|
|
672
|
+
expect(callbackResponse.ok()).toBeTruthy();
|
|
673
|
+
const callbackHtml = (await callbackResponse.text()).toLowerCase();
|
|
674
|
+
expect(callbackHtml).not.toContain('data-zova-hydrated');
|
|
675
|
+
expect(callbackHtml).not.toContain('mock payment simulator');
|
|
676
|
+
|
|
677
|
+
const reconcilePath = new RegExp(
|
|
678
|
+
`/api/pay/payment-session/${checkout.paymentSessionId}/reconcile$`,
|
|
679
|
+
);
|
|
680
|
+
const paymentViewPath = new RegExp(`/api/pay/payment-session/${checkout.paymentSessionId}$`);
|
|
681
|
+
const orderViewPath = new RegExp(`/api/commerce/trade/order/viewMine/${checkout.orderId}$`);
|
|
682
|
+
let reconcileCount = 0;
|
|
683
|
+
let paymentViewCount = 0;
|
|
684
|
+
let orderViewCount = 0;
|
|
685
|
+
page.on('request', request => {
|
|
686
|
+
const url = new URL(request.url());
|
|
687
|
+
if (request.method() !== 'GET') {
|
|
688
|
+
if (request.method() === 'POST' && reconcilePath.test(url.pathname)) reconcileCount++;
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (paymentViewPath.test(url.pathname)) paymentViewCount++;
|
|
692
|
+
if (orderViewPath.test(url.pathname)) orderViewCount++;
|
|
693
|
+
});
|
|
694
|
+
const reconcileResponse = waitForApiResponse(page, 'POST', reconcilePath);
|
|
695
|
+
const documentResponse = await page.goto(callbackPath, { waitUntil: 'load' });
|
|
696
|
+
expect(documentResponse?.ok()).toBeTruthy();
|
|
697
|
+
expect((await reconcileResponse).ok()).toBeTruthy();
|
|
698
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
699
|
+
await expect(page).toHaveURL(new RegExp(`/commerce/order/${checkout.orderId}(?:/|$)`));
|
|
700
|
+
expect(reconcileCount).toBe(1);
|
|
701
|
+
expect(paymentViewCount).toBeLessThanOrEqual(10);
|
|
702
|
+
expect(orderViewCount).toBeLessThanOrEqual(5);
|
|
703
|
+
await expect(page.getByText('paid · $45.99')).toBeVisible();
|
|
704
|
+
await expect(page.getByRole('alert')).toHaveCount(0);
|
|
705
|
+
expect(pageErrors).toEqual([]);
|
|
706
|
+
} finally {
|
|
707
|
+
await context.close().catch(() => {});
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
test(
|
|
713
|
+
'PayPal browser cancel: customer can continue payment or open an awaiting order',
|
|
714
|
+
{ tag: ['@web', '@flow', '@payment'] },
|
|
715
|
+
async ({ browser, request }, testInfo) => {
|
|
716
|
+
test.setTimeout(60_000);
|
|
717
|
+
const { context, fixture, page, pageErrors } = await createAddressThroughCustomerPage(
|
|
718
|
+
browser,
|
|
719
|
+
request,
|
|
720
|
+
testInfo,
|
|
721
|
+
);
|
|
722
|
+
try {
|
|
723
|
+
await page.goto('/commerce', { waitUntil: 'load' });
|
|
724
|
+
const productLink = page.getByRole('link', { name: 'Pour-Over Coffee Set', exact: true });
|
|
725
|
+
await expect(productLink).toBeVisible();
|
|
726
|
+
await productLink.click();
|
|
727
|
+
await expect(page).toHaveURL(/\/commerce\/product\/\d+(?:\/|$)/);
|
|
728
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
729
|
+
await expect(page.getByRole('heading', { name: 'Pour-Over Coffee Set' })).toBeVisible();
|
|
730
|
+
await expect(page.getByText('COF-SET-01')).toBeVisible();
|
|
731
|
+
const addToCartButton = page.getByRole('button', { name: 'Add to cart', exact: true });
|
|
732
|
+
await expect(addToCartButton).toBeEnabled();
|
|
733
|
+
const addResponse = waitForApiResponse(page, 'POST', '/api/commerce/trade/cart/items');
|
|
734
|
+
await addToCartButton.click();
|
|
735
|
+
expect((await addResponse).ok()).toBeTruthy();
|
|
736
|
+
await page.getByRole('link', { name: /^Cart/ }).click();
|
|
737
|
+
await page.getByRole('link', { name: 'Checkout', exact: true }).click();
|
|
738
|
+
const addressChoice = page.getByRole('radio', { name: new RegExp(fixture.recipientName) });
|
|
739
|
+
await expect(addressChoice).toBeVisible();
|
|
740
|
+
await addressChoice.check();
|
|
741
|
+
await page.getByRole('radio', { name: 'No coupon', exact: true }).check();
|
|
742
|
+
const checkoutResponse = waitForApiResponse(page, 'POST', '/api/commerce/trade/checkout');
|
|
743
|
+
await page.getByRole('button', { name: 'Create order', exact: true }).click();
|
|
744
|
+
const checkoutResponseValue = await checkoutResponse;
|
|
745
|
+
expect(checkoutResponseValue.ok()).toBeTruthy();
|
|
746
|
+
const checkout = (await checkoutResponseValue.json()).data;
|
|
747
|
+
expectTableIdentity(checkout.orderId);
|
|
748
|
+
expectTableIdentity(checkout.paymentSessionId);
|
|
749
|
+
expect(checkout.state).toBe('awaiting_payment');
|
|
750
|
+
|
|
751
|
+
const callbackPath = `/commerce/payment/${checkout.paymentSessionId}/${checkout.orderId}?providerResult=cancel`;
|
|
752
|
+
const paymentSessionPath = new RegExp(
|
|
753
|
+
`/api/pay/payment-session/${checkout.paymentSessionId}$`,
|
|
754
|
+
);
|
|
755
|
+
const reconcilePath = new RegExp(
|
|
756
|
+
`/api/pay/payment-session/${checkout.paymentSessionId}/reconcile$`,
|
|
757
|
+
);
|
|
758
|
+
const requiresActionSession = {
|
|
759
|
+
id: checkout.paymentSessionId,
|
|
760
|
+
state: 'requires_action',
|
|
761
|
+
providerName: 'pay-paypal:paypal',
|
|
762
|
+
nextAction: { kind: 'redirect', url: 'https://example.test/paypal-approval' },
|
|
763
|
+
amountMinor: 4599,
|
|
764
|
+
currency: 'USD',
|
|
765
|
+
};
|
|
766
|
+
const callbackResponse = await context.request.get(callbackPath);
|
|
767
|
+
expect(callbackResponse.ok()).toBeTruthy();
|
|
768
|
+
const callbackHtml = await callbackResponse.text();
|
|
769
|
+
expect(callbackHtml).toContain('Start payment');
|
|
770
|
+
expect(callbackHtml).not.toContain(
|
|
771
|
+
'You returned from the payment provider without completing payment.',
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
let reconcileCount = 0;
|
|
775
|
+
let reconcileSeen = false;
|
|
776
|
+
let resolvePaymentSessionRefresh: () => void;
|
|
777
|
+
const paymentSessionRefresh = new Promise<void>(resolve => {
|
|
778
|
+
resolvePaymentSessionRefresh = resolve;
|
|
779
|
+
});
|
|
780
|
+
await page.route(
|
|
781
|
+
url => paymentSessionPath.test(url.pathname) || reconcilePath.test(url.pathname),
|
|
782
|
+
async route => {
|
|
783
|
+
const request = route.request();
|
|
784
|
+
const pathname = new URL(request.url()).pathname;
|
|
785
|
+
if (request.method() === 'POST' && reconcilePath.test(pathname)) {
|
|
786
|
+
reconcileCount++;
|
|
787
|
+
reconcileSeen = true;
|
|
788
|
+
} else if (
|
|
789
|
+
reconcileSeen &&
|
|
790
|
+
request.method() === 'GET' &&
|
|
791
|
+
paymentSessionPath.test(pathname)
|
|
792
|
+
) {
|
|
793
|
+
resolvePaymentSessionRefresh();
|
|
794
|
+
}
|
|
795
|
+
await route.fulfill({
|
|
796
|
+
contentType: 'application/json',
|
|
797
|
+
body: JSON.stringify({
|
|
798
|
+
code: 0,
|
|
799
|
+
message: '',
|
|
800
|
+
data: requiresActionSession,
|
|
801
|
+
}),
|
|
802
|
+
});
|
|
803
|
+
},
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
const reconcileResponse = waitForApiResponse(page, 'POST', reconcilePath);
|
|
807
|
+
await page.goto(callbackPath, { waitUntil: 'load' });
|
|
808
|
+
expect((await reconcileResponse).ok()).toBeTruthy();
|
|
809
|
+
await paymentSessionRefresh;
|
|
810
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
811
|
+
await expect(
|
|
812
|
+
page.getByText(
|
|
813
|
+
'You returned from the payment provider without completing payment. Your payment status has been refreshed. You can continue to payment or open your order.',
|
|
814
|
+
),
|
|
815
|
+
).toBeVisible();
|
|
816
|
+
await expect(page.getByRole('button', { name: 'Start payment', exact: true })).toHaveCount(0);
|
|
817
|
+
await expect(
|
|
818
|
+
page.getByRole('button', { name: 'Continue to payment', exact: true }),
|
|
819
|
+
).toBeVisible();
|
|
820
|
+
await expect(page.getByRole('button', { name: 'Open order', exact: true })).toBeVisible();
|
|
821
|
+
await expect(
|
|
822
|
+
page.getByText('Waiting for verified provider confirmation and order settlement…'),
|
|
823
|
+
).toHaveCount(0);
|
|
824
|
+
await expect(page.getByRole('alert')).toHaveCount(0);
|
|
825
|
+
expect(reconcileCount).toBe(1);
|
|
826
|
+
|
|
827
|
+
await page.getByRole('button', { name: 'Open order', exact: true }).click();
|
|
828
|
+
await expect(page).toHaveURL(new RegExp(`/commerce/order/${checkout.orderId}(?:/|$)`));
|
|
829
|
+
await expect(page.getByText('awaiting_payment · $45.99')).toBeVisible();
|
|
830
|
+
expect(pageErrors).toEqual([]);
|
|
831
|
+
} finally {
|
|
832
|
+
await context.close().catch(() => {});
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
);
|
|
836
|
+
|
|
548
837
|
test(
|
|
549
838
|
'Payment cancellation: customer observes the cancelled order after verified provider confirmation',
|
|
550
839
|
{ tag: ['@web', '@flow', '@payment'] },
|
|
@@ -701,7 +990,7 @@ test(
|
|
|
701
990
|
);
|
|
702
991
|
await orderRow.getByRole('button', { name: 'Approve refund', exact: true }).click();
|
|
703
992
|
expect((await approveResponse).ok()).toBeTruthy();
|
|
704
|
-
await expect(orderRow).toContainText('
|
|
993
|
+
await expect(orderRow).toContainText('Refund approved');
|
|
705
994
|
const executeResponse = waitForApiResponse(
|
|
706
995
|
adminPage,
|
|
707
996
|
'POST',
|
|
@@ -735,8 +1024,8 @@ test(
|
|
|
735
1024
|
},
|
|
736
1025
|
{ timeout: 40_000 },
|
|
737
1026
|
)
|
|
738
|
-
.toContain('
|
|
739
|
-
await expect(await getAdminOrderRow(adminPage, checkout.orderId)).toContainText('
|
|
1027
|
+
.toContain('Refunded');
|
|
1028
|
+
await expect(await getAdminOrderRow(adminPage, checkout.orderId)).toContainText('Refunded');
|
|
740
1029
|
expect(adminPageErrors).toEqual([]);
|
|
741
1030
|
} finally {
|
|
742
1031
|
await adminContext.close().catch(() => {});
|
|
@@ -761,7 +1050,7 @@ test(
|
|
|
761
1050
|
);
|
|
762
1051
|
|
|
763
1052
|
test(
|
|
764
|
-
'ATP-
|
|
1053
|
+
'ATP-SPC-05: systemAdmin inspects readonly Address Resource without mutation controls',
|
|
765
1054
|
{ tag: ['@admin', '@flow', '@address'] },
|
|
766
1055
|
async ({ browser, request }, testInfo) => {
|
|
767
1056
|
test.setTimeout(60_000);
|
|
@@ -774,7 +1063,7 @@ test(
|
|
|
774
1063
|
const url = new URL(request.url());
|
|
775
1064
|
if (
|
|
776
1065
|
url.pathname.startsWith(addressActionPath) &&
|
|
777
|
-
['POST', 'PATCH', 'DELETE'].includes(request.method())
|
|
1066
|
+
['POST', 'PATCH', 'PUT', 'DELETE'].includes(request.method())
|
|
778
1067
|
) {
|
|
779
1068
|
addressMutationMethods.push(request.method());
|
|
780
1069
|
}
|
|
@@ -789,6 +1078,14 @@ test(
|
|
|
789
1078
|
await expect(adminPage).toHaveURL(addressResourceUrl);
|
|
790
1079
|
const fixtureRow = adminPage.getByRole('row', { name: customer.fixture.recipientName });
|
|
791
1080
|
await expect(fixtureRow).toBeVisible();
|
|
1081
|
+
await expect(fixtureRow).toContainText(customer.fixture.recipientName);
|
|
1082
|
+
await expect(fixtureRow).toContainText(customer.fixture.phone);
|
|
1083
|
+
await expect(fixtureRow).toContainText(customer.fixture.countryCode);
|
|
1084
|
+
await expect(fixtureRow).toContainText(customer.fixture.city);
|
|
1085
|
+
await expect(fixtureRow).not.toContainText(customer.fixture.region);
|
|
1086
|
+
await expect(fixtureRow).not.toContainText(customer.fixture.postalCode);
|
|
1087
|
+
await expect(fixtureRow).not.toContainText(customer.fixture.addressLine1);
|
|
1088
|
+
await expect(fixtureRow).not.toContainText(customer.fixture.addressLine2);
|
|
792
1089
|
await expect(adminPage.getByRole('button', { name: 'Create', exact: true })).toHaveCount(0);
|
|
793
1090
|
await expect(
|
|
794
1091
|
adminPage.locator('a[href*="/rest/resource/"]').filter({ hasText: 'Create' }),
|
|
@@ -796,6 +1093,7 @@ test(
|
|
|
796
1093
|
|
|
797
1094
|
const addressId = await fixtureRow.getByRole('cell').first().textContent();
|
|
798
1095
|
expect(addressId).toMatch(/^\d+$/);
|
|
1096
|
+
await expect(fixtureRow.getByRole('link', { name: addressId! })).toHaveCount(1);
|
|
799
1097
|
await adminPage.goto(`/commerce-admin/rest/resource/commerce-member%3Aaddress/${addressId}`, {
|
|
800
1098
|
waitUntil: 'load',
|
|
801
1099
|
});
|
|
@@ -1101,6 +1399,170 @@ test(
|
|
|
1101
1399
|
},
|
|
1102
1400
|
);
|
|
1103
1401
|
|
|
1402
|
+
test(
|
|
1403
|
+
'ATP-SPC-02: SKU renders semantic Admin currency and lifecycle controls',
|
|
1404
|
+
{ tag: ['@admin', '@flow', '@sku'] },
|
|
1405
|
+
async ({ browser }, testInfo) => {
|
|
1406
|
+
test.setTimeout(60_000);
|
|
1407
|
+
const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
1408
|
+
const categoryName = `E2E SKU Category ${suffix}`;
|
|
1409
|
+
const productTitle = `E2E SKU Product ${suffix}`;
|
|
1410
|
+
const skuCode = `E2E-SKU-${suffix}`;
|
|
1411
|
+
const adminContext = await browser.newContext();
|
|
1412
|
+
const adminPage = await adminContext.newPage();
|
|
1413
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
1414
|
+
const skuCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Asku/create';
|
|
1415
|
+
const skuListPath = '/commerce-admin/rest/resource/commerce-catalog%3Asku';
|
|
1416
|
+
const categoryActionPath = '/api/commerce/catalog/category';
|
|
1417
|
+
const productActionPath = '/api/commerce/catalog/product';
|
|
1418
|
+
const skuActionPath = '/api/commerce/catalog/sku';
|
|
1419
|
+
let categoryId: number | string | undefined;
|
|
1420
|
+
let productId: number | string | undefined;
|
|
1421
|
+
let skuId: number | string | undefined;
|
|
1422
|
+
let headers: { Authorization: string } | undefined;
|
|
1423
|
+
try {
|
|
1424
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
1425
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
1426
|
+
const accessToken = (await adminContext.cookies()).find(
|
|
1427
|
+
cookie => cookie.name === 'token',
|
|
1428
|
+
)?.value;
|
|
1429
|
+
expect(accessToken).toBeTruthy();
|
|
1430
|
+
headers = { Authorization: `Bearer ${accessToken}` };
|
|
1431
|
+
|
|
1432
|
+
const categoryResponse = await adminPage.request.post(categoryActionPath, {
|
|
1433
|
+
data: { name: categoryName, published: true },
|
|
1434
|
+
headers,
|
|
1435
|
+
});
|
|
1436
|
+
expect(categoryResponse.ok()).toBeTruthy();
|
|
1437
|
+
categoryId = (await categoryResponse.json()).data;
|
|
1438
|
+
expectTableIdentity(categoryId);
|
|
1439
|
+
|
|
1440
|
+
const productResponse = await adminPage.request.post(productActionPath, {
|
|
1441
|
+
data: { categoryId, title: productTitle, published: true },
|
|
1442
|
+
headers,
|
|
1443
|
+
});
|
|
1444
|
+
expect(productResponse.ok()).toBeTruthy();
|
|
1445
|
+
productId = (await productResponse.json()).data;
|
|
1446
|
+
expectTableIdentity(productId);
|
|
1447
|
+
|
|
1448
|
+
await adminPage.goto(skuCreatePath, { waitUntil: 'load' });
|
|
1449
|
+
const productField = adminPage.getByRole('group', { name: 'Product' });
|
|
1450
|
+
await expect(productField).toBeVisible();
|
|
1451
|
+
await expect(productField.getByRole('combobox')).toHaveCount(0);
|
|
1452
|
+
await expect(productField.getByRole('textbox')).toBeVisible();
|
|
1453
|
+
const lifecycle = adminPage
|
|
1454
|
+
.getByRole('group', { name: 'SKU lifecycle' })
|
|
1455
|
+
.getByRole('combobox');
|
|
1456
|
+
await expect(lifecycle).toBeVisible();
|
|
1457
|
+
await expect(lifecycle.locator('option')).toHaveText([
|
|
1458
|
+
'',
|
|
1459
|
+
'Draft',
|
|
1460
|
+
'Active',
|
|
1461
|
+
'Inactive',
|
|
1462
|
+
'Archived',
|
|
1463
|
+
]);
|
|
1464
|
+
await expect(adminPage.getByRole('group', { name: 'Price (cents)' })).toBeVisible();
|
|
1465
|
+
|
|
1466
|
+
const skuResponse = await adminPage.request.post(skuActionPath, {
|
|
1467
|
+
data: { code: skuCode, productId, priceCents: 1234, lifecycle: 'draft' },
|
|
1468
|
+
headers,
|
|
1469
|
+
});
|
|
1470
|
+
expect(skuResponse.ok()).toBeTruthy();
|
|
1471
|
+
skuId = (await skuResponse.json()).data;
|
|
1472
|
+
expectTableIdentity(skuId);
|
|
1473
|
+
|
|
1474
|
+
await adminPage.goto(skuListPath, { waitUntil: 'load' });
|
|
1475
|
+
const skuRow = adminPage.getByRole('row', { name: new RegExp(skuCode) });
|
|
1476
|
+
await expect(skuRow).toBeVisible();
|
|
1477
|
+
await expect(skuRow.getByText('12.34', { exact: true })).toBeVisible();
|
|
1478
|
+
await expect(skuRow.getByText('Draft', { exact: true })).toBeVisible();
|
|
1479
|
+
await expect(skuRow.getByRole('link', { name: skuCode, exact: true })).toBeVisible();
|
|
1480
|
+
await expect(
|
|
1481
|
+
adminPage.locator('section').getByText('SKU code', { exact: true }),
|
|
1482
|
+
).toBeVisible();
|
|
1483
|
+
await expect(adminPage.getByText('Created At', { exact: true })).toHaveCount(1);
|
|
1484
|
+
await expect(adminPage.getByRole('group', { name: 'Attributes' })).toHaveCount(0);
|
|
1485
|
+
|
|
1486
|
+
await adminPage.goto(`${skuListPath}/${skuId}`, { waitUntil: 'load' });
|
|
1487
|
+
await expect(
|
|
1488
|
+
adminPage.getByRole('group', { name: 'SKU lifecycle' }).getByRole('textbox'),
|
|
1489
|
+
).toHaveValue('Draft');
|
|
1490
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
1491
|
+
await expect(adminPage.getByRole('button', { name: 'Back', exact: true })).toBeVisible();
|
|
1492
|
+
expect(adminPageErrors).toEqual([]);
|
|
1493
|
+
} finally {
|
|
1494
|
+
if (skuId && headers) {
|
|
1495
|
+
const response = await adminPage.request.delete(`${skuActionPath}/${skuId}`, { headers });
|
|
1496
|
+
expect(response.ok()).toBeTruthy();
|
|
1497
|
+
}
|
|
1498
|
+
if (productId && headers) {
|
|
1499
|
+
const response = await adminPage.request.delete(`${productActionPath}/${productId}`, {
|
|
1500
|
+
headers,
|
|
1501
|
+
});
|
|
1502
|
+
expect(response.ok()).toBeTruthy();
|
|
1503
|
+
}
|
|
1504
|
+
if (categoryId && headers) {
|
|
1505
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${categoryId}`, {
|
|
1506
|
+
headers,
|
|
1507
|
+
});
|
|
1508
|
+
expect(response.ok()).toBeTruthy();
|
|
1509
|
+
}
|
|
1510
|
+
await adminContext.close().catch(() => {});
|
|
1511
|
+
}
|
|
1512
|
+
},
|
|
1513
|
+
);
|
|
1514
|
+
|
|
1515
|
+
test(
|
|
1516
|
+
'ATP-SPC-04: Stock Admin resources remain readonly and mutation-free',
|
|
1517
|
+
{ tag: ['@admin', '@flow', '@stock'] },
|
|
1518
|
+
async ({ browser }) => {
|
|
1519
|
+
test.setTimeout(60_000);
|
|
1520
|
+
const adminContext = await browser.newContext();
|
|
1521
|
+
const adminPage = await adminContext.newPage();
|
|
1522
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
1523
|
+
const mutationMethods: string[] = [];
|
|
1524
|
+
adminPage.on('request', request => {
|
|
1525
|
+
const url = new URL(request.url());
|
|
1526
|
+
if (
|
|
1527
|
+
url.pathname.includes('/api/commerce/trade/stock') &&
|
|
1528
|
+
['POST', 'PATCH', 'PUT', 'DELETE'].includes(request.method())
|
|
1529
|
+
) {
|
|
1530
|
+
mutationMethods.push(request.method());
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
try {
|
|
1535
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
1536
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
1537
|
+
|
|
1538
|
+
for (const [resourcePath, title] of [
|
|
1539
|
+
['commerce-trade%3AstockBalance', 'Stock Balance'],
|
|
1540
|
+
['commerce-trade%3AstockAudit', 'Stock Audit'],
|
|
1541
|
+
] as const) {
|
|
1542
|
+
await adminPage.goto(`/commerce-admin/rest/resource/${resourcePath}`, {
|
|
1543
|
+
waitUntil: 'load',
|
|
1544
|
+
});
|
|
1545
|
+
await expect(adminPage).toHaveURL(
|
|
1546
|
+
new RegExp(
|
|
1547
|
+
`/commerce-admin/rest/resource/commerce-trade(?:%3A|:|%253A)${title === 'Stock Balance' ? 'stockBalance' : 'stockAudit'}(?:[/?#]|$)`,
|
|
1548
|
+
),
|
|
1549
|
+
);
|
|
1550
|
+
await expect(adminPage.getByText(title, { exact: true }).first()).toBeVisible();
|
|
1551
|
+
await expect(adminPage.getByRole('button', { name: 'Create', exact: true })).toHaveCount(0);
|
|
1552
|
+
await expect(
|
|
1553
|
+
adminPage.locator('a[href*="/rest/resource/"]').filter({ hasText: 'Create' }),
|
|
1554
|
+
).toHaveCount(0);
|
|
1555
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
expect(mutationMethods).toEqual([]);
|
|
1559
|
+
expect(adminPageErrors).toEqual([]);
|
|
1560
|
+
} finally {
|
|
1561
|
+
await adminContext.close().catch(() => {});
|
|
1562
|
+
}
|
|
1563
|
+
},
|
|
1564
|
+
);
|
|
1565
|
+
|
|
1104
1566
|
test(
|
|
1105
1567
|
'Commerce Cart: anonymous browser is redirected to login',
|
|
1106
1568
|
{ tag: ['@web', '@cart'] },
|