cabloy 5.1.116 → 5.1.118
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-pg.yml +3 -0
- package/.github/workflows/vona-test-sqlite3.yml +3 -0
- package/CHANGELOG.md +34 -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/metrics-guide.md +75 -0
- 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 +590 -1
- package/observability/README.md +18 -0
- package/observability/collector/otel-collector.yaml +34 -0
- package/observability/docker-compose.metrics.yml +25 -0
- package/observability/grafana/provisioning/datasources/prometheus.yaml +8 -0
- package/observability/prometheus/prometheus.yaml +7 -0
- package/package.json +1 -1
- package/vona/env/.env +6 -0
- 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-core/src/lib/framework/processWorker.ts +1 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/packages-vona/vona-mock/src/index.ts +2 -0
- package/vona/packages-vona/vona-mock/src/lib/testLock.ts +21 -0
- package/vona/pnpm-lock.yaml +345 -53
- package/vona/src/backend/config/config/config.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/catalog.test.ts +13 -3
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/product.test.ts +124 -70
- 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 -130
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +297 -3
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.index.ts +15 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.version.ts +128 -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/entity/refundAttempt.tsx +37 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/refundAudit.tsx +47 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/refundRequest.tsx +43 -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/model/refundAttempt.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/model/refundAudit.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/model/refundRequest.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/service/coupon.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponReservation.test.ts +395 -6
- 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 +345 -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 +112 -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 +93 -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 +41 -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 +47 -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/refundOutcomeCreate.tsx +16 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundRequestCreate.tsx +15 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundResult.tsx +35 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundReview.tsx +15 -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 +54 -6
- 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/order.ts +839 -28
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/cartOwnership.test.ts +2 -4
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutReservation.test.ts +521 -6
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutTransaction.test.ts +16 -14
- 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 +3 -5
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +712 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +337 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +194 -4
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/shipment.test.ts +258 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/shipmentRefundRace.test.ts +143 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockBalance.test.ts +2 -4
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockReservation.test.ts +2 -4
- 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/cabloy-basic/modules/basic-metrics/package.json +54 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/.metadata/index.ts +189 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/.metadata/this.ts +1 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/bean/ssrMenu.metrics.ts +23 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/controller/metrics.ts +21 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/dto/metricsQueue.ts +25 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/dto/metricsRuntime.ts +36 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/dto/metricsSnapshot.ts +21 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/index.ts +1 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/src/service/metrics.ts +11 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/tsconfig.build.json +11 -0
- package/vona/src/suite/cabloy-basic/modules/basic-metrics/tsconfig.json +5 -0
- package/vona/src/suite/cabloy-basic/package.json +1 -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-instance/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/bean/middlewareSystem.app.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/bean/middlewareSystem.instance.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +62 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/.metadata/index.ts +178 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/.metadata/this.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/bean.metrics.ts +15 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/middlewareSystem.health.ts +24 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/middlewareSystem.metrics.ts +51 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/config/config.ts +75 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/index.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/monkey.ts +27 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/service/health.ts +53 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/service/metrics.ts +222 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/service/metricsRuntime.ts +69 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/types/context.ts +9 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/types/index.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/types/metrics.ts +50 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/tsconfig.json +7 -0
- 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-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 +4 -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 +47 -72
- 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-trade/cli/openapi.config.ts +10 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/component/tableCellActionRefund.ts +31 -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 +277 -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 +218 -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 +91 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +1019 -74
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeOrder.ts +55 -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.actionRefund.tsx +40 -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/tableCellActionRefund/controller.tsx +106 -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 +9 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +9 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +82 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/orderMine.ts +46 -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 +146 -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/cabloy-basic/modules/basic-metrics/package.json +53 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/.metadata/index.ts +151 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/.metadata/page/dashboard.ts +9 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/.metadata/this.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/api/basicMetricsMetrics.ts +41 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/index.ts +2 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/model/metrics.ts +16 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/page/dashboard/controller.tsx +95 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/routes.ts +5 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/tsconfig.build.json +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/tsconfig.json +5 -0
- package/zova/src/suite/cabloy-basic/package.json +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
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/testLock.ts +0 -11
|
@@ -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,408 @@ 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
|
+
'Phase 60: customer requests and operator executes a whole-order refund',
|
|
530
|
+
{ tag: ['@web', '@admin', '@flow', '@payment', '@refund'] },
|
|
531
|
+
async ({ browser, request }, testInfo) => {
|
|
532
|
+
test.setTimeout(120_000);
|
|
533
|
+
const { context, fixture, page, pageErrors } = await createAddressThroughCustomerPage(
|
|
534
|
+
browser,
|
|
535
|
+
request,
|
|
536
|
+
testInfo,
|
|
537
|
+
);
|
|
538
|
+
try {
|
|
539
|
+
await page.goto('/commerce', { waitUntil: 'load' });
|
|
540
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
541
|
+
await expect(page.getByRole('heading', { name: 'Commerce catalogue' })).toBeVisible();
|
|
542
|
+
const productLink = page.getByRole('link', { name: 'Pour-Over Coffee Set', exact: true });
|
|
543
|
+
await expect(productLink).toBeVisible();
|
|
544
|
+
await productLink.click();
|
|
545
|
+
await expect(page).toHaveURL(/\/commerce\/product\/\d+(?:\/|$)/);
|
|
546
|
+
await expect(page.getByRole('heading', { name: 'Pour-Over Coffee Set' })).toBeVisible();
|
|
547
|
+
await expect(page.getByText('COF-SET-01')).toBeVisible();
|
|
548
|
+
await expect(page.getByText('$45.99')).toBeVisible();
|
|
549
|
+
const addResponse = waitForApiResponse(page, 'POST', '/api/commerce/trade/cart/items');
|
|
550
|
+
await page.getByRole('button', { name: 'Add to cart', exact: true }).click();
|
|
551
|
+
await addResponse;
|
|
552
|
+
await page.getByRole('link', { name: /^Cart/ }).click();
|
|
553
|
+
await expect(page).toHaveURL(/\/commerce\/cart(?:\/|$)/);
|
|
554
|
+
await page.getByRole('link', { name: 'Checkout', exact: true }).click();
|
|
555
|
+
await expect(page).toHaveURL(/\/commerce\/checkout(?:\/|$)/);
|
|
556
|
+
const addressChoice = page.getByRole('radio', { name: new RegExp(fixture.recipientName) });
|
|
557
|
+
await addressChoice.check();
|
|
558
|
+
await page.getByRole('radio', { name: 'No coupon', exact: true }).check();
|
|
559
|
+
const checkoutResponse = waitForApiResponse(page, 'POST', '/api/commerce/trade/checkout');
|
|
560
|
+
await page.getByRole('button', { name: 'Create order', exact: true }).click();
|
|
561
|
+
const checkout = (await (await checkoutResponse).json()).data;
|
|
562
|
+
const paymentResponse = waitForApiResponse(
|
|
563
|
+
page,
|
|
564
|
+
'POST',
|
|
565
|
+
new RegExp(`/api/commerce/trade/payment/${checkout.paymentAttemptId}/outcome$`),
|
|
566
|
+
);
|
|
567
|
+
await page.getByRole('button', { name: 'Payment succeeded', exact: true }).click();
|
|
568
|
+
expect((await paymentResponse).ok()).toBeTruthy();
|
|
569
|
+
await expect(page.getByText('paid · $45.99')).toBeVisible();
|
|
570
|
+
|
|
571
|
+
const requestResponse = waitForApiResponse(
|
|
572
|
+
page,
|
|
573
|
+
'POST',
|
|
574
|
+
`/api/commerce/trade/order/${checkout.orderId}/requestRefund`,
|
|
575
|
+
);
|
|
576
|
+
await page.getByPlaceholder('Reason for refund').fill('E2E refund request');
|
|
577
|
+
await page.getByRole('button', { name: 'Request refund', exact: true }).click();
|
|
578
|
+
expect((await requestResponse).ok()).toBeTruthy();
|
|
579
|
+
await expect(page.getByText('refund_requested · $45.99')).toBeVisible();
|
|
580
|
+
|
|
581
|
+
const adminContext = await browser.newContext();
|
|
582
|
+
const adminPage = await adminContext.newPage();
|
|
583
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
584
|
+
try {
|
|
585
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
586
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
587
|
+
await adminPage.goto('/commerce-admin/rest/resource/commerce-trade%3Aorder', {
|
|
588
|
+
waitUntil: 'load',
|
|
589
|
+
});
|
|
590
|
+
await expect(adminPage.locator('html')).toHaveAttribute(
|
|
591
|
+
'data-zova-hydrated',
|
|
592
|
+
'commerceAdmin',
|
|
593
|
+
);
|
|
594
|
+
const orderRow = adminPage
|
|
595
|
+
.getByRole('cell', { name: String(checkout.orderId), exact: true })
|
|
596
|
+
.locator('..');
|
|
597
|
+
await expect(orderRow).toBeVisible();
|
|
598
|
+
await orderRow.getByPlaceholder('Decision reason').fill('E2E approval');
|
|
599
|
+
await orderRow.getByRole('checkbox').check();
|
|
600
|
+
const approveResponse = waitForApiResponse(
|
|
601
|
+
adminPage,
|
|
602
|
+
'POST',
|
|
603
|
+
`/api/commerce/trade/order/${checkout.orderId}/approveRefund`,
|
|
604
|
+
);
|
|
605
|
+
await orderRow.getByRole('button', { name: 'Approve refund', exact: true }).click();
|
|
606
|
+
expect((await approveResponse).ok()).toBeTruthy();
|
|
607
|
+
await expect(orderRow).toContainText('refund_approved');
|
|
608
|
+
const executeResponse = waitForApiResponse(
|
|
609
|
+
adminPage,
|
|
610
|
+
'POST',
|
|
611
|
+
`/api/commerce/trade/order/${checkout.orderId}/refundOutcome`,
|
|
612
|
+
);
|
|
613
|
+
await orderRow.getByRole('button', { name: 'Execute refund', exact: true }).click();
|
|
614
|
+
expect((await executeResponse).ok()).toBeTruthy();
|
|
615
|
+
await expect(orderRow).toContainText('refunded');
|
|
616
|
+
expect(adminPageErrors).toEqual([]);
|
|
617
|
+
} finally {
|
|
618
|
+
await adminContext.close().catch(() => {});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const refundedDetailResponse = waitForApiResponse(
|
|
622
|
+
page,
|
|
623
|
+
'GET',
|
|
624
|
+
new RegExp(`/api/commerce/trade/order/viewMine/${checkout.orderId}$`),
|
|
625
|
+
);
|
|
626
|
+
await page.reload({ waitUntil: 'load' });
|
|
627
|
+
await refundedDetailResponse;
|
|
628
|
+
await expect(page.getByText('refunded · $45.99')).toBeVisible();
|
|
629
|
+
await expect(page.getByText('1 × $45.99 = $45.99')).toBeVisible();
|
|
630
|
+
await expect(page.getByRole('heading', { name: 'Shipment' })).toHaveCount(0);
|
|
631
|
+
await expect(page.getByRole('alert')).toHaveCount(0);
|
|
632
|
+
expect(pageErrors).toEqual([]);
|
|
633
|
+
} finally {
|
|
634
|
+
await context.close().catch(() => {});
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
test(
|
|
640
|
+
'ATP-ADDR-01: systemAdmin inspects Address Resource without mutation controls',
|
|
641
|
+
{ tag: ['@admin', '@flow', '@address'] },
|
|
642
|
+
async ({ browser, request }, testInfo) => {
|
|
643
|
+
test.setTimeout(60_000);
|
|
644
|
+
const customer = await createAddressThroughCustomerPage(browser, request, testInfo);
|
|
645
|
+
const adminContext = await browser.newContext();
|
|
646
|
+
const adminPage = await adminContext.newPage();
|
|
647
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
648
|
+
const addressMutationMethods: string[] = [];
|
|
649
|
+
adminPage.on('request', request => {
|
|
650
|
+
const url = new URL(request.url());
|
|
651
|
+
if (
|
|
652
|
+
url.pathname.startsWith(addressActionPath) &&
|
|
653
|
+
['POST', 'PATCH', 'DELETE'].includes(request.method())
|
|
654
|
+
) {
|
|
655
|
+
addressMutationMethods.push(request.method());
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
661
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
662
|
+
await adminPage.goto('/commerce-admin/rest/resource/commerce-member%3Aaddress', {
|
|
663
|
+
waitUntil: 'load',
|
|
664
|
+
});
|
|
665
|
+
await expect(adminPage).toHaveURL(addressResourceUrl);
|
|
666
|
+
const fixtureRow = adminPage.getByRole('row', { name: customer.fixture.recipientName });
|
|
667
|
+
await expect(fixtureRow).toBeVisible();
|
|
668
|
+
await expect(adminPage.getByRole('button', { name: 'Create', exact: true })).toHaveCount(0);
|
|
669
|
+
await expect(
|
|
670
|
+
adminPage.locator('a[href*="/rest/resource/"]').filter({ hasText: 'Create' }),
|
|
671
|
+
).toHaveCount(0);
|
|
672
|
+
|
|
673
|
+
const addressId = await fixtureRow.getByRole('cell').first().textContent();
|
|
674
|
+
expect(addressId).toMatch(/^\d+$/);
|
|
675
|
+
await adminPage.goto(`/commerce-admin/rest/resource/commerce-member%3Aaddress/${addressId}`, {
|
|
676
|
+
waitUntil: 'load',
|
|
677
|
+
});
|
|
678
|
+
await expect(adminPage).toHaveURL(
|
|
679
|
+
/\/commerce-admin\/rest\/resource\/commerce-member(?:%3A|:|%253A)address\/\d+(?:[/?#]|$)/,
|
|
680
|
+
);
|
|
681
|
+
await expect(
|
|
682
|
+
adminPage.getByRole('group', { name: 'Address Line 1 *' }).getByRole('textbox'),
|
|
683
|
+
).toHaveValue(customer.fixture.addressLine1);
|
|
684
|
+
await expect(
|
|
685
|
+
adminPage.getByRole('group', { name: 'City *' }).getByRole('textbox'),
|
|
686
|
+
).toHaveValue(customer.fixture.city);
|
|
687
|
+
await expect(adminPage.getByRole('button', { name: 'Back', exact: true })).toBeVisible();
|
|
688
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
689
|
+
expect(addressMutationMethods).toEqual([]);
|
|
690
|
+
expect(adminPageErrors).toEqual([]);
|
|
691
|
+
|
|
692
|
+
await deleteAddressThroughCustomerPage(customer.page, customer.fixture);
|
|
693
|
+
await expect(customer.page.getByRole('alert')).toHaveCount(0);
|
|
694
|
+
expect(customer.pageErrors).toEqual([]);
|
|
695
|
+
} finally {
|
|
696
|
+
await adminContext.close().catch(() => {});
|
|
697
|
+
await customer.context.close().catch(() => {});
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
);
|
|
701
|
+
|
|
147
702
|
test(
|
|
148
703
|
'Commerce Cart: anonymous browser is redirected to login',
|
|
149
704
|
{ tag: ['@web', '@cart'] },
|
|
@@ -161,6 +716,40 @@ test(
|
|
|
161
716
|
},
|
|
162
717
|
);
|
|
163
718
|
|
|
719
|
+
test(
|
|
720
|
+
'Phase 50: anonymous payment and order routes remain SSR-neutral and protected',
|
|
721
|
+
{ tag: ['@web', '@flow'] },
|
|
722
|
+
async ({ page, request }) => {
|
|
723
|
+
const routes = [
|
|
724
|
+
['/commerce/checkout', '/checkout'],
|
|
725
|
+
['/commerce/payment/1', '/payment/1'],
|
|
726
|
+
['/commerce/orders', '/orders'],
|
|
727
|
+
['/commerce/order/1', '/order/1'],
|
|
728
|
+
] as const;
|
|
729
|
+
for (const [path, routePath] of routes) {
|
|
730
|
+
const response = await request.get(path, { maxRedirects: 0 });
|
|
731
|
+
expect(response.status(), path).toBe(200);
|
|
732
|
+
expect(response.headers().location, path).toBeUndefined();
|
|
733
|
+
const html = await response.text();
|
|
734
|
+
expect(html.toLowerCase(), path).not.toContain('data-zova-hydrated');
|
|
735
|
+
expect(html, path).not.toContain('Payment Customer');
|
|
736
|
+
expect(html, path).not.toContain('Payment succeeded');
|
|
737
|
+
expect(html, path).not.toContain('Order #');
|
|
738
|
+
expect(html, path).not.toContain('Checkout');
|
|
739
|
+
expect(html, path).not.toContain('Mock payment');
|
|
740
|
+
expect(html, path).not.toContain('My orders');
|
|
741
|
+
|
|
742
|
+
const pageErrors = collectPageErrors(page);
|
|
743
|
+
const documentResponse = await page.goto(path, { waitUntil: 'load' });
|
|
744
|
+
expect(documentResponse?.ok(), path).toBeTruthy();
|
|
745
|
+
await expect(page, path).toHaveURL(/\/commerce\/login\?(?:.*&)?returnTo=/);
|
|
746
|
+
const loginUrl = new URL(page.url());
|
|
747
|
+
expect(loginUrl.searchParams.getAll('returnTo'), path).toEqual([routePath]);
|
|
748
|
+
expect(pageErrors, path).toEqual([]);
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
);
|
|
752
|
+
|
|
164
753
|
test(
|
|
165
754
|
'ATP-SSR-02: Commerce Admin is an independent SSR site',
|
|
166
755
|
{ tag: ['@admin', '@smoke'] },
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Local Metrics Stack
|
|
2
|
+
|
|
3
|
+
Start the local OpenTelemetry Collector, Prometheus, and Grafana stack:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
docker compose -f observability/docker-compose.metrics.yml up
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Enable the application metric exporter only for local development:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
METRICS_ENABLED=true
|
|
13
|
+
METRICS_OTLP_HTTP_URL=http://127.0.0.1:4318/v1/metrics
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Prometheus scrapes the Collector at its private exporter endpoint. Cabloy deliberately does not expose an application `/metrics` endpoint: Vona workers export independently, and the Collector preserves each worker stream before PromQL aggregates them.
|
|
17
|
+
|
|
18
|
+
The published ports bind to loopback for development. Production must keep the Collector OTLP receiver and Prometheus exporter on a private workload/monitoring network, inject any OTLP credentials from the deployment secret store, and never proxy those paths through Cloudflare or the public Nginx listener.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
receivers:
|
|
2
|
+
otlp:
|
|
3
|
+
protocols:
|
|
4
|
+
http:
|
|
5
|
+
endpoint: 0.0.0.0:4318
|
|
6
|
+
|
|
7
|
+
processors:
|
|
8
|
+
memory_limiter:
|
|
9
|
+
check_interval: 1s
|
|
10
|
+
limit_mib: 256
|
|
11
|
+
batch:
|
|
12
|
+
timeout: 5s
|
|
13
|
+
transform/metrics_allowlist:
|
|
14
|
+
error_mode: ignore
|
|
15
|
+
metric_statements:
|
|
16
|
+
- context: datapoint
|
|
17
|
+
statements:
|
|
18
|
+
- delete_key(attributes, "http.target") where attributes["http.target"] != nil
|
|
19
|
+
- delete_key(attributes, "url.full") where attributes["url.full"] != nil
|
|
20
|
+
- delete_key(attributes, "user.id") where attributes["user.id"] != nil
|
|
21
|
+
- delete_key(attributes, "enduser.id") where attributes["enduser.id"] != nil
|
|
22
|
+
|
|
23
|
+
exporters:
|
|
24
|
+
prometheus:
|
|
25
|
+
endpoint: 0.0.0.0:9464
|
|
26
|
+
resource_to_telemetry_conversion:
|
|
27
|
+
enabled: true
|
|
28
|
+
|
|
29
|
+
service:
|
|
30
|
+
pipelines:
|
|
31
|
+
metrics:
|
|
32
|
+
receivers: [otlp]
|
|
33
|
+
processors: [memory_limiter, transform/metrics_allowlist, batch]
|
|
34
|
+
exporters: [prometheus]
|