summaryrefslogtreecommitdiff
path: root/qa/driver/browser-test.js
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-22 04:45:10 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-22 04:45:10 +0800
commit0183b86f18d9897ff9487e6d864898954397991d (patch)
tree245c8f197f5803802dd630f278e9f2a08affa4ec /qa/driver/browser-test.js
parentb6008d67232756d743699472865025159f6ec533 (diff)
downloadfund-lab-0183b86f18d9897ff9487e6d864898954397991d.tar.gz
Add bond buy to holdings minimal vertical slice (3d-22)
Diffstat (limited to 'qa/driver/browser-test.js')
-rw-r--r--qa/driver/browser-test.js113
1 files changed, 112 insertions, 1 deletions
diff --git a/qa/driver/browser-test.js b/qa/driver/browser-test.js
index aaa820c..14cffc7 100644
--- a/qa/driver/browser-test.js
+++ b/qa/driver/browser-test.js
@@ -39,6 +39,8 @@ let lastStockQuoteResponse = null;
let lastStockDailyResponse = null;
let lastStockTradeResponse = null;
let lastStockPositionsResponse = null;
+let lastBondTradeResponse = null;
+let lastBondPositionsResponse = null;
function check(name, ok, detail) {
results.push({ name, ok, detail: detail || "" });
@@ -213,7 +215,17 @@ async function summaryLabelExists(page, label, name) {
lastStockPositionsResponse = await r.json();
} catch {}
}
- if (r.status() >= 400 && !/\/api\/instruments\//.test(r.url()) && !/\/api\/market\//.test(r.url()) && !/\/orders/.test(r.url()) && !/\/redemptions/.test(r.url()) && !/\/capital/.test(r.url()) && !/\/stock-trades/.test(r.url()) && !/\/stock-positions/.test(r.url())) {
+ if (/\/bond-trades$/i.test(new URL(r.url()).pathname) && r.request().method() === "POST" && r.status() < 400) {
+ try {
+ lastBondTradeResponse = await r.json();
+ } catch {}
+ }
+ if (/\/bond-positions$/i.test(new URL(r.url()).pathname) && r.request().method() === "GET" && r.status() < 400) {
+ try {
+ lastBondPositionsResponse = await r.json();
+ } catch {}
+ }
+ if (r.status() >= 400 && !/\/api\/instruments\//.test(r.url()) && !/\/api\/market\//.test(r.url()) && !/\/orders/.test(r.url()) && !/\/redemptions/.test(r.url()) && !/\/capital/.test(r.url()) && !/\/stock-trades/.test(r.url()) && !/\/stock-positions/.test(r.url()) && !/\/bond-trades/.test(r.url()) && !/\/bond-positions/.test(r.url())) {
consoleErrors.push("resource " + r.status() + ": " + r.url());
}
});
@@ -268,6 +280,7 @@ async function summaryLabelExists(page, label, name) {
await stocksScenario(page);
await stockDailyScenario(page);
await stockTradeScenario(page);
+ await bondTradeScenario(page);
} finally {
check("G1 无浏览器控制台/页面错误", consoleErrors.length === 0, consoleErrors.slice(0, 3).join(" | "));
await browser.close();
@@ -960,6 +973,104 @@ async function stockTradeScenario(page) {
await page.screenshot({ path: SHOTS + "/24-stock-trade.png" });
}
+async function bondTradeScenario(page) {
+ // B5: 债券买入 -> 持仓最小垂直闭环走真实 API(服务端以桩行情价成交)。
+ await page.waitForSelector(".bonds-panel", { timeout: 10000 });
+
+ const token = process.env.QA_TOKEN || "qa-token";
+
+ lastBondTradeResponse = null;
+ lastBondPositionsResponse = null;
+ await page.fill(".bond-code-input", "110075");
+ await page.fill(".bond-trade-quantity-input", "10");
+ await page.click(".bond-trade-action");
+
+ await page.waitForSelector(".bond-position-row", { timeout: 10000 });
+
+ const rowCount = await page.locator(".bond-position-row").count();
+ const rowText = ((await page.textContent(".bond-position-row")) || "").trim();
+ const trade = lastBondTradeResponse || {};
+ const positions = lastBondPositionsResponse || {};
+ const rows = Array.isArray(positions.positions) ? positions.positions : [];
+ const matched = rows.find((item) => item.instrumentCode === "110075") || {};
+
+ check(
+ "B5a 真实买入生成债券持仓行且金额与行情价一致",
+ trade.instrumentCode === "110075" &&
+ trade.price === "106.36800000" &&
+ trade.costCash === "1063.68" &&
+ rowCount >= 1 &&
+ rowText.includes("110075") &&
+ matched.quantity === "10.00000000" &&
+ matched.costCash === "1063.68",
+ `row=${rowText.slice(0, 160)} trade=${JSON.stringify(trade).slice(0, 160)}`
+ );
+
+ await page.fill(".bond-code-input", "999999");
+ await page.fill(".bond-trade-quantity-input", "10");
+ await page.click(".bond-trade-action");
+ await page.waitForFunction(
+ () => {
+ const element = document.querySelector(".bond-trade-unavailable");
+ return element && !element.hidden && element.textContent.includes("买入失败");
+ },
+ { timeout: 10000 }
+ );
+
+ const unavailableText = ((await page.textContent(".bond-trade-unavailable")) || "").trim();
+ const fundId = await currentFundId(page);
+
+ const failure = await page.evaluate(
+ async ({ token, fundId }) => {
+ const response = await fetch(`/api/funds/${fundId}/bond-trades`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ "Idempotency-Key": "qabondtradefail",
+ },
+ body: JSON.stringify({ instrumentCode: "999999", quantity: "10" }),
+ });
+ let body = null;
+ try {
+ body = await response.json();
+ } catch {}
+ return { status: response.status, body };
+ },
+ { token, fundId }
+ );
+
+ const positionsAfter = await page.evaluate(
+ async ({ token, fundId }) => {
+ const response = await fetch(`/api/funds/${fundId}/bond-positions`, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ let body = null;
+ try {
+ body = await response.json();
+ } catch {}
+ return body;
+ },
+ { token, fundId }
+ );
+
+ const afterRows = positionsAfter && Array.isArray(positionsAfter.positions) ? positionsAfter.positions : [];
+ const hasBadCode = afterRows.some((item) => item.instrumentCode === "999999");
+
+ check(
+ "B5b 无有效行情时买入失败且不产生 999999 持仓",
+ unavailableText.includes("买入失败") &&
+ !unavailableText.includes("0.00") &&
+ failure.status === 503 &&
+ failure.body &&
+ failure.body.error === "MARKET_DATA_UNAVAILABLE" &&
+ !hasBadCode,
+ `message=${unavailableText.slice(0, 200)} status=${failure.status} codes=${afterRows.map((item) => item.instrumentCode).join(",")}`
+ );
+
+ await page.screenshot({ path: SHOTS + "/25-bond-trade.png" });
+}
+
async function postInvestmentPlanViaApi(page, fundId, token) {
return page.evaluate(
async ({ fundId, token }) => {