Files
console-dev/tests/review-finance.e2e.test.mjs

167 lines
6.1 KiB
JavaScript

/** 冻结记录的响应乱序回归:只控制本地 HTTP 边界,页面与请求层使用生产实现。 */
import assert from "node:assert/strict";
import { test } from "node:test";
import {
appOrigin,
backendOrigin,
eventually,
login,
scenario,
} from "./helpers/console-browser.mjs";
for (const outcome of ["late success", "late failure", "newer read pending"]) {
test(
`finance freeze records ignore an older response with ${outcome}`,
{ timeout: 35000 },
(t) =>
scenario(t, { loginMode: "password-only" }, async (page) => {
let freezeReads = 0;
let releaseOlder;
let releaseNewer;
const olderHeld = new Promise((resolve) => {
releaseOlder = resolve;
});
const newerHeld = new Promise((resolve) => {
releaseNewer = resolve;
});
const freezePath = "/console/v1/account/credit/freeze";
const emptyPaths = [
"/console/v1/transaction",
"/console/v1/credit",
"/console/v1/withdraw",
"/console/v1/order",
"/console/v1/refund/pending/amount",
];
await page.route(
(url) =>
url.origin === backendOrigin &&
(url.pathname === freezePath || emptyPaths.includes(url.pathname)),
async (route) => {
const path = new URL(route.request().url()).pathname;
const fulfill = (data, status = 200) =>
route.fulfill({
status: 200,
contentType: "application/json",
headers: { "Access-Control-Allow-Origin": appOrigin },
body: JSON.stringify({
status,
msg: status === 200 ? "" : "旧冻结请求失败",
data,
}),
});
if (path === "/console/v1/refund/pending/amount")
return fulfill({ amount: "0.00" });
if (path !== freezePath)
return fulfill({ list: [], count: 0, total_amount: "0.00" });
const sequence = ++freezeReads;
if (sequence === 2) await olderHeld;
if (sequence === 3 && outcome === "newer read pending")
await newerHeld;
if (sequence === 2 && outcome === "late failure")
return fulfill({}, 400);
return fulfill({
list: [
{
id: 9,
amount: sequence === 2 ? "110.00" : "220.00",
client_notes:
sequence === 1
? "初始冻结快照"
: sequence === 2
? "较早冻结快照"
: "最新冻结快照",
nickname: "audit-user",
create_time: 1784619935,
},
],
count: 1,
});
},
);
try {
await login(page, "/finance?tab=freeze");
await page.getByText("初始冻结快照", { exact: true }).waitFor();
const transaction = page.getByRole("tab", {
name: "交易记录",
exact: true,
});
const freeze = page.getByRole("tab", {
name: "冻结记录",
exact: true,
});
await transaction.click();
await freeze.click();
await eventually(
() => freezeReads,
(value) => value === 2,
"Older freeze read is held at the HTTP boundary",
);
await transaction.click();
await freeze.click();
await eventually(
() => freezeReads,
(value) => value === 3,
"Newer freeze read has started",
);
const panel = page.getByRole("tabpanel").filter({ visible: true });
const loading = panel.locator(".p-datatable-mask");
if (outcome === "newer read pending")
await loading.waitFor({ state: "visible" });
else await page.getByText("最新冻结快照", { exact: true }).waitFor();
const olderResponse = page.waitForResponse(
(response) => new URL(response.url()).pathname === freezePath,
);
releaseOlder();
await (await olderResponse).finished();
// 等浏览器完成响应处理与绘制后再核对当前列表,不直接读取组件内部状态。
await page.evaluate(
() =>
new Promise((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(resolve)),
),
);
if (outcome === "newer read pending") {
// 已开始淡出的遮罩仍暂时可见;等待它自身的过渡结束再判断是否仍在加载。
await loading.evaluateAll((nodes) =>
Promise.all(
nodes.flatMap((node) =>
node
.getAnimations()
.map((animation) => animation.finished.catch(() => {})),
),
),
);
assert.equal(
await loading.isVisible(),
true,
"An older request must not finish the newer request's loading state",
);
releaseNewer();
await page.getByText("最新冻结快照", { exact: true }).waitFor();
}
assert.equal(
await page.getByText("最新冻结快照", { exact: true }).isVisible(),
true,
);
assert.equal(
await page.getByText("较早冻结快照", { exact: true }).isVisible(),
false,
);
assert.equal(await panel.locator(".p-message-error").count(), 0);
await loading.waitFor({ state: "hidden" });
assert.equal(new URL(page.url()).searchParams.get("tab"), "freeze");
return {
olderResponse: outcome,
freezeReads,
finalSnapshot: "latest",
staleErrorVisible: false,
loadingBelongsToLatestRead: true,
};
} finally {
releaseOlder();
releaseNewer();
}
}),
);
}