docs: introduce project and keep local reports out of source delivery

This commit is contained in:
cieanyau committed 2026-11-06 00:29:13 +08:00
1 parent db5762abd4
commit d37ce57d09
45 files changed
+232 -14820

No files matched your search

+19
View File
@@ -1,3 +1,8 @@
/**
* 独立进程的 HTTP 假后端,仅监听回环地址,按既有 /console/v1 信封响应。
* 只模拟测试需要的外部接口,不是可部署的业务后端,也不执行真实付款或短信投递。
* 控制口允许场景重置数据/制造挂起和失效;它不进入产品接口实现。
*/
import { createServer } from "node:http";
const token = "e2e-session-token";
@@ -8,6 +13,7 @@ const account = {
credit: "100.00",
};
const initialKeys = Array.from({ length: 28 }, (_, index) => ({
// 25个audit匹配项 + 3个other项,覆盖搜索结果10/10/5分页与切换过滤条件。
id: index + 1,
name:
index < 25
@@ -23,11 +29,13 @@ let paid = false;
let keys = initialKeys.map((key) => ({ ...key }));
function reply(response, data = {}, extra = {}, httpStatus = 200) {
// HTTP状态与业务信封可以独立覆盖,确保前端同时处理传输401和信封401。
response.writeHead(httpStatus, { "Content-Type": "application/json" });
response.end(JSON.stringify({ status: httpStatus, msg: "", data, ...extra }));
}
const server = createServer(async (request, response) => {
// 预览服务和假后端端口不同,需要允许测试浏览器发送JSON与Bearer请求。
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader(
"Access-Control-Allow-Headers",
@@ -51,6 +59,7 @@ const server = createServer(async (request, response) => {
}
if (url.pathname === "/__control/reset" && request.method === "POST") {
// 场景从全新列表和未付款状态开始;11行变体用于删除最后一页唯一记录。
options = body ?? {};
requests = [];
paid = false;
@@ -67,6 +76,7 @@ const server = createServer(async (request, response) => {
const path = url.pathname.replace(/^\/console\/v1/, "");
const record = {
// 请求原文仅在测试进程内存供断言使用,测试输出端另做脱敏,不能直接导出此对象。
method: request.method,
path,
query: Object.fromEntries(url.searchParams),
@@ -78,6 +88,7 @@ const server = createServer(async (request, response) => {
requests.push(record);
if (path === "/common" && request.method === "GET") {
// 模拟站点配置决定允许的登录方式;离线场景关闭外部CAPTCHA与OAuth。
return reply(response, {
website_name: "本地前端验收",
login_phone_password: options.loginMode === "sms-only" ? 0 : 1,
@@ -99,6 +110,7 @@ const server = createServer(async (request, response) => {
if (path === "/phone/code" && request.method === "POST")
return reply(response);
// 公共配置和登录之后的接口必须携带实际登录得到的测试令牌。
if (request.headers.authorization !== `Bearer ${token}`) {
record.invalidAuthorization = true;
return reply(response, {}, { msg: "Missing fixture session" }, 401);
@@ -138,6 +150,7 @@ const server = createServer(async (request, response) => {
}
if (path === "/ssh_key" && request.method === "GET") {
if (options.hangSshBody) {
// 只发送响应头和不完整JSON,保持连接以复现“响应体解码永不结束”。
response.writeHead(200, { "Content-Type": "application/json" });
response.write('{"status":200,"msg":"","data":');
return;
@@ -149,6 +162,7 @@ const server = createServer(async (request, response) => {
{ status: 401, msg: "Expired fixture session" },
);
const page = Math.max(1, Number(url.searchParams.get("page")) || 1);
// 列表接口仍走后端分页;搜索聚合/切片由真实前端路径负责。
const limit = Math.max(1, Number(url.searchParams.get("limit")) || 10);
return reply(response, {
list: keys.slice((page - 1) * limit, page * limit),
@@ -172,6 +186,7 @@ const server = createServer(async (request, response) => {
return reply(response);
}
if (path === "/order/42" && request.method === "GET") {
// 余额、订单与支付状态共享paid事实,支付成功后回读必须反映已付金额。
if (options.expiredPayment)
return reply(
response,
@@ -216,6 +231,7 @@ const server = createServer(async (request, response) => {
if (path === "/voucher/pay" && request.method === "GET")
return reply(response, { list: [] });
if (path === "/pay" && request.method === "POST") {
// Paid是现有后端的顶层字段;挂起变体返回渠道HTML,随后状态查询不响应。
if (options.hangPaymentStatus)
return reply(response, { html: '<div role="status">本地支付渠道</div>' });
paid = true;
@@ -247,6 +263,7 @@ const server = createServer(async (request, response) => {
if (path === "/recommend/promoter" && request.method === "GET")
return reply(response, { promoter: { permission: 0 } });
// 未实现接口显式失败并留标记,防止新增产品请求被“统一成功”假后端掩盖。
record.handled = false;
reply(
response,
@@ -257,12 +274,14 @@ const server = createServer(async (request, response) => {
});
server.listen(0, "127.0.0.1", () => {
// 由系统选择空闲端口,首行JSON作为父测试进程的启动握手。
process.stdout.write(
`${JSON.stringify({ origin: `http://127.0.0.1:${server.address().port}` })}\n`,
);
});
process.on("SIGTERM", () => {
// 先关闭挂起连接,确保超时测试也能正常清理进程。
server.closeAllConnections();
server.close(() => process.exit(0));
});