summaryrefslogtreecommitdiff
path: root/qa/driver
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 06:38:58 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 06:38:58 +0800
commit64a4ca5c0a523ffc51c861eac1291671b90a2f3a (patch)
tree12b846aa15ba8563dfbce29f53b966667c8d30a7 /qa/driver
parent03f89aea966dfe9edcd08695a79788a6be703b26 (diff)
downloadfund-lab-64a4ca5c0a523ffc51c861eac1291671b90a2f3a.tar.gz
fix(api,web): 下单/读取请求身份隔离与零手续费支持(3d-1 评审修正)
- Web: orderSeq 拆分为 orderCreateSeq/orderReadSeq,并发刷新不再丢弃在途下单结果;换基金/换 token 双序列失效并携带 fundId 双重守卫 - 零手续费:DB CHECK fee_amount >= 0(幂等迁移),校验改为拒绝负数,UI 提示与占位符同步 - 测试:API 增加零手续费回放与 DB 负手续费约束回归(43/43),浏览器新增 H 系列延迟 POST 并发刷新/失败恢复/换基金隔离(78/78)
Diffstat (limited to 'qa/driver')
-rw-r--r--qa/driver/browser-test.js99
1 files changed, 96 insertions, 3 deletions
diff --git a/qa/driver/browser-test.js b/qa/driver/browser-test.js
index 2f18ac2..4e557d3 100644
--- a/qa/driver/browser-test.js
+++ b/qa/driver/browser-test.js
@@ -318,13 +318,13 @@ async function orderScenario(page) {
"G4 非法金额校验提示"
);
await page.fill(".order-amount-input", "100.00");
- await page.fill(".order-fee-input", "0.00");
+ await page.fill(".order-fee-input", "-0.01");
await page.click(".order-submit-action");
await expectText(
page,
".error-banner",
- "认购手续费必须是大于零的两位小数金额,例如 1.50",
- "G5 非法手续费校验提示"
+ "认购手续费必须是不小于零的两位小数金额,例如 0.00",
+ "G5 负手续费校验提示"
);
check("G6 校验阶段无认购 POST", orderPosts.length === 0, `posts=${orderPosts.length}`);
@@ -406,6 +406,99 @@ async function orderScenario(page) {
await page.unroute("**/api/funds/*/orders");
await page.screenshot({ path: SHOTS + "/09-order-retry.png" });
+ // ==== H 系列: 下单/读取请求身份隔离 + 零手续费 + 换基金隔离 ====
+ await page.route("**/api/funds/*/orders", async (route) => {
+ if (route.request().method() !== "POST") {
+ await route.continue();
+ return;
+ }
+ await new Promise((r) => setTimeout(r, 1200));
+ await route.continue();
+ });
+ await page.fill(".order-code-input", "000001");
+ await page.fill(".order-amount-input", "5.00");
+ await page.fill(".order-fee-input", "0.00");
+ await page.click(".order-submit-action");
+ await expectText(page, ".order-submit-action", "下单中...", "H1 延迟 POST 进入下单中状态");
+ const refreshStart = orderGets;
+ await page.click(".orders-refresh-action");
+ for (let i = 0; i < 60 && orderGets === refreshStart; i++) {
+ await page.waitForTimeout(100);
+ }
+ check("H2 并发刷新在 POST 在途时完成", orderGets > refreshStart, `gets=${orderGets}/${refreshStart}`);
+ const midRows = await page.locator(".order-row").count();
+ check("H3 刷新落地时 POST 仍未完成", midRows === 2, `rows=${midRows}`);
+ await page.waitForFunction(() => document.querySelectorAll(".order-row").length === 3, null, { timeout: 15000 });
+ const doneBtn = ((await page.textContent(".order-submit-action")) || "").trim();
+ check("H4 延迟 POST 完成后按钮恢复认购下单", doneBtn === "认购下单", doneBtn);
+ await summaryValue(page, "可用现金", "19883.00", "H5 零手续费下单后可用现金 19883.00");
+ await summaryValue(page, "保留现金", "117.00", "H6 零手续费下单后保留现金 117.00");
+ const zeroFeeRow = ((await page.textContent(".pending-orders")) || "");
+ check(
+ "H7 零手续费订单行金额 5.00 手续费 0.00 预留 5.00",
+ zeroFeeRow.includes("金额 5.00") && zeroFeeRow.includes("手续费 0.00") && zeroFeeRow.includes("预留合计 5.00"),
+ zeroFeeRow.slice(0, 200)
+ );
+ await page.unroute("**/api/funds/*/orders");
+ await page.screenshot({ path: SHOTS + "/10-order-delayed-refresh.png" });
+
+ await page.route("**/api/funds/*/orders", async (route) => {
+ if (route.request().method() !== "POST") {
+ await route.continue();
+ return;
+ }
+ await new Promise((r) => setTimeout(r, 1200));
+ await route.continue();
+ });
+ await page.fill(".order-amount-input", "99999.00");
+ await page.fill(".order-fee-input", "0.01");
+ await page.click(".order-submit-action");
+ await expectText(page, ".order-submit-action", "下单中...", "H8 失败路径进入下单中状态");
+ const failRefreshStart = orderGets;
+ await page.click(".orders-refresh-action");
+ for (let i = 0; i < 60 && orderGets === failRefreshStart; i++) {
+ await page.waitForTimeout(100);
+ }
+ await page.waitForSelector(".error-banner", { timeout: 15000 });
+ const failText = ((await page.textContent(".error-banner")) || "").trim();
+ check("H9 延迟 409 显示不足横幅", failText.includes("available cash is not enough"), failText.slice(0, 120));
+ const failBtn = ((await page.textContent(".order-submit-action")) || "").trim();
+ check("H10 失败后按钮恢复(不被刷新卡死)", failBtn === "认购下单", failBtn);
+ const failRows = await page.locator(".order-row").count();
+ check("H11 失败后无新订单行", failRows === 3, `rows=${failRows}`);
+ await summaryValue(page, "可用现金", "19883.00", "H12 失败后可用现金不变");
+ await summaryValue(page, "保留现金", "117.00", "H13 失败后保留现金不变");
+ await page.unroute("**/api/funds/*/orders");
+
+ await page.route("**/api/funds/*/orders", async (route) => {
+ if (route.request().method() !== "POST") {
+ await route.continue();
+ return;
+ }
+ await new Promise((r) => setTimeout(r, 1500));
+ await route.continue();
+ });
+ await page.fill(".order-amount-input", "7.00");
+ await page.fill(".order-fee-input", "0.00");
+ await page.click(".order-submit-action");
+ await expectText(page, ".order-submit-action", "下单中...", "H14 换基金前 POST 在途");
+ await page.fill(".fund-name-input", "切换基金测试");
+ await page.fill(".fund-cash-input", "3000.00");
+ await page.click(".fund-create-action");
+ await page.waitForSelector(".order-row", { state: "detached", timeout: 10000 });
+ check("H15 换基金立即清空旧基金订单行", true);
+ const switchBtn = ((await page.textContent(".order-submit-action")) || "").trim();
+ check("H16 换基金后按钮恢复(在途请求被丢弃)", switchBtn === "认购下单", switchBtn);
+ const switchHint = ((await page.textContent(".pending-orders")) || "").trim();
+ check("H17 换基金后显示暂无待确认订单", switchHint.includes("暂无待确认订单"), switchHint.slice(0, 40));
+ await page.waitForTimeout(2200);
+ const staleRows = await page.locator(".order-row").count();
+ check("H18 延迟 POST 落地后旧订单仍未出现", staleRows === 0, `rows=${staleRows}`);
+ await summaryValue(page, "可用现金", "3000.00", "H19 新基金可用现金 3000.00");
+ await summaryValue(page, "保留现金", "0.00", "H20 新基金保留现金 0.00");
+ await page.unroute("**/api/funds/*/orders");
+ await page.screenshot({ path: SHOTS + "/11-fund-switch-isolated.png" });
+
await page.route("**/api/funds/*/orders", async (route) => {
if (route.request().method() !== "GET") {
await route.continue();