summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa')
-rw-r--r--qa/driver/browser-test.js124
1 files changed, 122 insertions, 2 deletions
diff --git a/qa/driver/browser-test.js b/qa/driver/browser-test.js
index 46a8f85..677ae54 100644
--- a/qa/driver/browser-test.js
+++ b/qa/driver/browser-test.js
@@ -11,6 +11,8 @@ const orderPosts = [];
const orderPostStatuses = [];
const confirmPosts = [];
const confirmPostStatuses = [];
+const redemptionPosts = [];
+const redemptionConfirmResponses = [];
let positionGets = 0;
let lastPositionsResponse = null;
let fundPostResponses = 0;
@@ -20,6 +22,7 @@ let createdFund = null;
let createdFundResponse = null;
let lastOrdersResponse = null;
let lastConfirmResponse = null;
+let lastRedemptionConfirmResponse = null;
function check(name, ok, detail) {
results.push({ name, ok, detail: detail || "" });
@@ -106,7 +109,12 @@ async function summaryLabelExists(page, label, name) {
} catch {}
}
if (r.url().includes("/orders") && r.request().method() === "POST") orderPostStatuses.push(r.status());
- if (r.url().includes("/confirm") && r.request().method() === "POST") {
+ if (r.url().includes("/confirm") && r.url().includes("/redemptions") && r.request().method() === "POST") {
+ try {
+ lastRedemptionConfirmResponse = await r.json();
+ redemptionConfirmResponses.push(r.status());
+ } catch {}
+ } else if (r.url().includes("/confirm") && r.request().method() === "POST") {
confirmPostStatuses.push(r.status());
try {
lastConfirmResponse = await r.json();
@@ -118,7 +126,7 @@ async function summaryLabelExists(page, label, name) {
lastPositionsResponse = await r.json();
} catch {}
}
- if (r.status() >= 400 && !/\/api\/instruments\//.test(r.url()) && !/\/orders/.test(r.url())) {
+ if (r.status() >= 400 && !/\/api\/instruments\//.test(r.url()) && !/\/orders/.test(r.url()) && !/\/redemptions/.test(r.url())) {
consoleErrors.push("resource " + r.status() + ": " + r.url());
}
});
@@ -132,6 +140,9 @@ async function summaryLabelExists(page, label, name) {
if (r.url().includes("/confirm") && r.method() === "POST") {
confirmPosts.push({ url: r.url(), key: r.headers()["idempotency-key"] || "" });
}
+ if (r.url().includes("/redemptions") && r.method() === "POST" && !r.url().includes("/confirm")) {
+ redemptionPosts.push({ url: r.url(), key: r.headers()["idempotency-key"] || "", body: r.postData() || "" });
+ }
});
page.on("console", (m) => {
if (/^DBG /.test(m.text())) console.log("[dbg] " + m.text());
@@ -155,11 +166,120 @@ async function summaryLabelExists(page, label, name) {
await idempotentRetryScenario(page);
await orderScenario(page);
await confirmScenario(page);
+ await redeemScenario(page);
} finally {
check("G1 无浏览器控制台/页面错误", consoleErrors.length === 0, consoleErrors.slice(0, 3).join(" | "));
await browser.close();
}
+
+async function redeemScenario(page) {
+ // K 系列: 赎回提交(冻结份额) -> 确认(现金到账+份额核销),接 J 系列持仓 84.01949252
+ await page.waitForSelector(".redeem-panel", { timeout: 10000 });
+ const redeemEyebrow = ((await page.textContent(".redeem-panel .eyebrow")) || "").trim();
+ check("K1 赎回面板 eyebrow 为 06 / REDEEM", redeemEyebrow === "06 / REDEEM", redeemEyebrow);
+
+ await page.fill(".redemption-code-input", "000001");
+ await page.fill(".redemption-units-input", "34.01949252");
+ await page.fill(".redemption-fee-input", "1.00");
+ await page.click(".redemption-submit-action");
+ await page.waitForSelector(".pending-redemptions .order-row", { timeout: 15000 });
+ const submittedRedemption = ((await page.textContent(".pending-redemptions")) || "");
+ check(
+ "K2 提交赎回后显示已提交·待确认",
+ submittedRedemption.includes("已提交 · 待确认") && submittedRedemption.includes("份额 34.01949252"),
+ submittedRedemption.slice(0, 200)
+ );
+
+ // 提交后: 份额冻结但总份额与现金不变
+ await page.waitForFunction(
+ () => {
+ const panel = document.querySelector(".positions-panel");
+ return panel !== null && panel.textContent.includes("冻结份额 34.01949252");
+ },
+ null,
+ { timeout: 15000 }
+ );
+ const frozenCash = await page.$$eval(".positions-panel .fund-detail-value", (els) => els.map((el) => el.textContent.trim()));
+ check(
+ "K3 提交后冻结份额且现金不变",
+ frozenCash.includes("4898.51") && frozenCash.includes("0.00"),
+ JSON.stringify(frozenCash)
+ );
+
+ await page.click(".redemption-confirm-action");
+ for (let i = 0; i < 200 && (redemptionConfirmResponses.length === 0 || lastRedemptionConfirmResponse === null); i++) {
+ await page.waitForTimeout(100);
+ }
+ const redemptionStatus = lastRedemptionConfirmResponse ? lastRedemptionConfirmResponse.status : "";
+ check("K4 赎回确认响应为 confirmed", redemptionStatus === "confirmed", redemptionStatus);
+ check(
+ "K5 赎回确认展示 NAV 与到账金额精确字符串",
+ lastRedemptionConfirmResponse.confirmedNav === "1.19020000" &&
+ /^[0-9]+\.[0-9]{2}$/.test(lastRedemptionConfirmResponse.confirmedProceeds || "") &&
+ /^[0-9]+\.[0-9]{2}$/.test(lastRedemptionConfirmResponse.confirmedCostReleased || ""),
+ JSON.stringify(lastRedemptionConfirmResponse).slice(0, 220)
+ );
+
+ await page.waitForFunction(
+ () => {
+ const panel = document.querySelector(".positions-panel");
+ return panel !== null && panel.textContent.includes("份额 50.00000000");
+ },
+ null,
+ { timeout: 15000 }
+ );
+ const proceeds = parseFloat(lastRedemptionConfirmResponse.confirmedProceeds);
+ const costReleased = parseFloat(lastRedemptionConfirmResponse.confirmedCostReleased);
+ const expectedAvailable = (4898.51 + proceeds).toFixed(2);
+ const expectedCost = (99.99 - costReleased).toFixed(2);
+ await page.waitForFunction(
+ (want) => {
+ const values = Array.from(document.querySelectorAll(".positions-panel .fund-detail-value")).map((el) => el.textContent.trim());
+ return values.includes(want.available);
+ },
+ { available: expectedAvailable },
+ { timeout: 15000 }
+ );
+ const settledCash = await page.$$eval(".positions-panel .fund-detail-value", (els) => els.map((el) => el.textContent.trim()));
+ const positionText = ((await page.textContent(".positions-panel")) || "");
+ check(
+ "K6 确认后份额核销且现金按应收入账",
+ positionText.includes("份额 50.00000000") &&
+ !positionText.includes("冻结份额") &&
+ positionText.includes(expectedCost) &&
+ settledCash.includes(expectedAvailable) &&
+ settledCash.includes("0.00"),
+ JSON.stringify({ positionText: positionText.slice(0, 220), settledCash, expectedAvailable, expectedCost })
+ );
+
+ const redemptionRowText = ((await page.textContent(".pending-redemptions")) || "");
+ check(
+ "K7 赎回订单行显示已确认与到账金额",
+ redemptionRowText.includes("已确认") && redemptionRowText.includes(`赎回到账 ${lastRedemptionConfirmResponse.confirmedProceeds}`),
+ redemptionRowText.slice(0, 240)
+ );
+
+ // 份额上限校验: 超额赎回 409 且不产生冻结
+ await page.fill(".redemption-units-input", "999999999.00000000");
+ await page.fill(".redemption-fee-input", "0.00");
+ await page.click(".redemption-submit-action");
+ await page.waitForSelector(".error-banner", { timeout: 15000 });
+ const insufficientText = ((await page.textContent(".error-banner")) || "").trim();
+ check(
+ "K8 超额赎回被 409 拒绝",
+ insufficientText.includes("available holdings are not enough"),
+ insufficientText.slice(0, 140)
+ );
+ const frozenAfterConflict = await page.$$eval(".positions-panel .fund-detail-value", (els) => els.map((el) => el.textContent.trim()));
+ check(
+ "K9 超额赎回后现金与冻结不变",
+ frozenAfterConflict.includes(expectedAvailable) && !((await page.textContent(".positions-panel")) || "").includes("冻结份额"),
+ JSON.stringify(frozenAfterConflict)
+ );
+ await page.screenshot({ path: SHOTS + "/13-redemption-settled.png" });
+}
+
const failed = results.filter((r) => !r.ok);
console.log(`\n==== ${results.length - failed.length}/${results.length} passed ====`);
process.exit(failed.length > 0 ? 1 : 0);