docs: introduce project and keep local reports out of source delivery
This commit is contained in:
1 parent
db5762abd4
commit
d37ce57d09
45 files changed
+232
-14820
No files matched your search
Vendored
+19
@@ -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));
|
||||
});
|
||||
Reference in new issue
Block a user