summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa')
-rw-r--r--qa/driver/browser-test.js63
-rwxr-xr-xqa/stub/fund-lab-python6
2 files changed, 68 insertions, 1 deletions
diff --git a/qa/driver/browser-test.js b/qa/driver/browser-test.js
index c7ee468..76fa69b 100644
--- a/qa/driver/browser-test.js
+++ b/qa/driver/browser-test.js
@@ -32,6 +32,7 @@ let lastRebalancePlansResponse = null;
let lastRebalancePreviewResponse = null;
let lastRebalanceExecuteResponse = null;
let lastRebalanceExecutionsResponse = null;
+let lastNavDatesResponse = null;
function check(name, ok, detail) {
results.push({ name, ok, detail: detail || "" });
@@ -171,7 +172,12 @@ async function summaryLabelExists(page, label, name) {
lastRebalanceExecutionsResponse = await r.json();
} catch {}
}
- if (r.status() >= 400 && !/\/api\/instruments\//.test(r.url()) && !/\/orders/.test(r.url()) && !/\/redemptions/.test(r.url()) && !/\/capital/.test(r.url())) {
+ if (r.url().includes("/market/nav-dates") && r.request().method() === "GET" && r.status() < 400) {
+ try {
+ lastNavDatesResponse = 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())) {
consoleErrors.push("resource " + r.status() + ": " + r.url());
}
});
@@ -218,6 +224,7 @@ async function summaryLabelExists(page, label, name) {
await capitalScenario(page);
await dividendScenario(page);
await returnsScenario(page);
+ await navDatesScenario(page);
await plansScenario(page);
await rebalanceScenario(page);
} finally {
@@ -501,6 +508,60 @@ async function returnsScenario(page) {
await page.screenshot({ path: SHOTS + "/16-returns-curve.png" });
}
+async function navDatesScenario(page) {
+ // R1: 真实 AKShare 净值日期下拉(可用时显示真实日期,不可用时显示提示)。
+ await page.waitForSelector(".returns-panel .returns-date-picker", { timeout: 10000 });
+
+ const token = process.env.QA_TOKEN || "qa-token";
+
+ for (let i = 0; i < 200 && lastNavDatesResponse === null; i++) {
+ await page.waitForTimeout(100);
+ }
+
+ const navDates = lastNavDatesResponse || {};
+ const dates = Array.isArray(navDates.dates) ? navDates.dates : [];
+ const pickerState = await page.getAttribute(".returns-date-picker", "data-nav-dates-state");
+ const optionValues = await page
+ .locator(".returns-date-input option")
+ .evaluateAll((elements) => elements.map((element) => element.value));
+ const emptyExists = (await page.locator(".returns-date-empty").count()) === 1;
+ const emptyText = ((await page.textContent(".returns-date-empty")) || "").trim();
+
+ check(
+ "R1a 真实可用时下拉显示真实净值日期",
+ navDates.code === "000001" &&
+ dates.includes("2026-09-21") &&
+ pickerState === "available" &&
+ optionValues.includes("2026-09-21") &&
+ emptyExists &&
+ emptyText === "暂无真实净值数据",
+ `state=${pickerState} dates=${JSON.stringify(dates).slice(0, 160)} options=${JSON.stringify(optionValues).slice(0, 160)}`
+ );
+
+ const failure = await page.evaluate(async (token) => {
+ const response = await fetch("/api/market/nav-dates?code=999999", {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ let body = null;
+ try {
+ body = await response.json();
+ } catch {}
+ return { status: response.status, body };
+ }, token);
+
+ check(
+ "R1b 不可用时返回 503 且保留提示路径",
+ failure.status === 503 &&
+ failure.body &&
+ failure.body.error === "MARKET_DATA_UNAVAILABLE" &&
+ emptyExists &&
+ emptyText === "暂无真实净值数据",
+ `status=${failure.status} body=${JSON.stringify(failure.body).slice(0, 160)}`
+ );
+
+ await page.screenshot({ path: SHOTS + "/19-nav-dates.png" });
+}
+
async function postInvestmentPlanViaApi(page, fundId, token) {
return page.evaluate(
async ({ fundId, token }) => {
diff --git a/qa/stub/fund-lab-python b/qa/stub/fund-lab-python
index 9cdfa6a..cefcecd 100755
--- a/qa/stub/fund-lab-python
+++ b/qa/stub/fund-lab-python
@@ -23,6 +23,12 @@ case "$OP" in
printf '{"schema_version":"fund-lab.akshare.v1","operation":"search","source":"stub-synthetic","source_revision":"stub-1","_synthetic":true,"collected_at":"%s","instruments":[{"code":"000001","name":"桩测试基金一号","fund_type":"混合型"},{"code":"110011","name":"桩测试基金二号","fund_type":null}]}\n' "$TS"
;;
nav)
+ # Sentinel code used by QA to exercise the AKShare-unavailable branch of
+ # GET /api/market/nav-dates (collector exits non-zero -> API answers 503).
+ if [ "$CODE" = "999999" ]; then
+ echo "AKShare collector failed: simulated outage for $CODE" >&2
+ exit 2
+ fi
printf '{"schema_version":"fund-lab.akshare.v1","operation":"nav","source":"stub-synthetic","source_revision":"stub-1","_synthetic":true,"collected_at":"%s","instrument":{"code":"%s"},"observations":[{"nav_date":"2026-09-15","published_at":null,"nav":"1.1000","accumulated_nav":null,"daily_return":null},{"nav_date":"2026-09-16","published_at":null,"nav":"1.1205","accumulated_nav":null,"daily_return":"1.86"},{"nav_date":"2026-09-17","published_at":null,"nav":"1.1180","accumulated_nav":null,"daily_return":"-0.22"},{"nav_date":"2026-09-18","published_at":null,"nav":"1.1500","accumulated_nav":null,"daily_return":"2.86"},{"nav_date":"2026-09-21","published_at":null,"nav":"1.1902","accumulated_nav":null,"daily_return":"3.49"}]}\n' "$TS" "$CODE"
;;
*)