/** * 业务闭环断言:写后可读、审批改状态、区划完整、看板/回盘真数据 */ const http = require("http"); const asserts = []; function req(method, path, body) { return new Promise((resolve) => { const data = body == null ? null : JSON.stringify(body); const headers = { Accept: "application/json" }; if (data) { headers["Content-Type"] = "application/json"; headers["Content-Length"] = Buffer.byteLength(data); } const r = http.request( { hostname: "localhost", port: 8088, path: "/sjnmtybt" + path, method, headers }, (res) => { let raw = ""; res.on("data", (c) => (raw += c)); res.on("end", () => { try { resolve(JSON.parse(raw)); } catch (e) { resolve({ code: res.statusCode, parseError: true, raw }); } }); } ); r.on("error", (e) => resolve({ code: 0, error: e.message })); if (data) r.write(data); r.end(); }); } function check(name, cond, detail) { asserts.push({ name, pass: !!cond, detail: detail || "" }); console.log((cond ? "PASS" : "FAIL") + " | " + name + (detail ? " | " + detail : "")); } (async () => { // 1 区划完整性 const towns = await req("GET", "/api/regions?level=TOWN&parentId=D_SJ"); check("7个镇", towns.code === 200 && towns.data.length === 7, "n=" + (towns.data || []).length); const ss = await req("GET", "/api/regions?level=VILLAGE&parentId=T_SHESHAN"); check("佘山有村", ss.code === 200 && ss.data.length >= 1, "n=" + (ss.data || []).length + " " + (ss.data || []).map((x) => x.name).join(",")); const cd = await req("GET", "/api/regions?level=VILLAGE&parentId=T_CHEDUN"); check("车墩有村", cd.code === 200 && cd.data.length >= 1, "n=" + (cd.data || []).length); // 2 人员新增落库 const idNo = "310117199908081" + String(Date.now()).slice(-4); const saved = await req("POST", "/api/personnel", { name: "闭环测试员", idNumber: idNo, townId: "T_YEXIE", villageId: "V_YX_001", bankCardNumber: "6228480030001111222", monthlyStandard: 1280, enjoyStartMonth: "202606", source: "村级录入", }); check("人员保存成功", saved.code === 200 && saved.data && saved.data.id, saved.data && saved.data.id); const pid = saved.data && saved.data.id; const found = await req("POST", "/api/personnel/page", { pageNum: 1, pageSize: 50, idNumber: idNo }); check("人员按身份证可查", found.code === 200 && found.data.total >= 1, "total=" + (found.data && found.data.total)); // 3 创建批次并审批改状态 const batch = await req("POST", "/api/batches", { batchNo: "202606", batchLevel: "VILLAGE", payMonth: "202606", townId: "T_YEXIE", villageId: "V_YX_001", payDate: "2026-06-20", personnelIds: [pid || "P001"], }); check("批次创建", batch.code === 200 && batch.data && batch.data.id, batch.data && batch.data.id); const bid = batch.data && batch.data.id; const before = await req("GET", "/api/batches/" + bid); check("新建批次待初审", before.data && before.data.status === "PENDING_TOWN_APPROVE", before.data && before.data.status); const pass = await req("POST", "/api/batches/town-approve", { bizId: bid, action: "PASS", opinion: "闭环测试通过", }); check("初审接口成功", pass.code === 200, pass.message); const after = await req("GET", "/api/batches/" + bid); check( "初审后阶段进入居保匹配", after.data && after.data.currentStage === "STAGE3_INSURANCE_MATCH", after.data && after.data.currentStage + "/" + after.data.status ); const match = await req("POST", "/api/insurance-match/trigger", { batchId: bid }); check("居保匹配返回人员项", match.code === 200 && match.data && (match.data.items || []).length >= 1, "items=" + ((match.data && match.data.items) || []).length); // 4 回盘明细来自真实发放 const ret = await req("POST", "/api/return-disk/details/page", { pageNum: 1, pageSize: 20, batchId: "MB202603001" }); check("回盘明细条数>=3", ret.code === 200 && ret.data.total >= 3, "total=" + (ret.data && ret.data.total)); const failItem = (ret.data.list || []).find((x) => x.success === false); check("含失败回盘明细", !!failItem, failItem && failItem.name + "/" + failItem.failReasonCode); // 5 看板真实统计 const dash = await req("GET", "/api/dashboard/summary?payMonth=202603&townId=T_YEXIE"); check("看板非假数据100人", dash.code === 200 && dash.data.totalPeople !== 100, "people=" + (dash.data && dash.data.totalPeople)); check("看板有统计明细", dash.data && (dash.data.townStats || []).length >= 1, "stats=" + ((dash.data && dash.data.townStats) || []).length); // 6 特殊业务落库可查 const sp = await req("POST", "/api/special-biz", { personnelId: "P004", batchNo: "202603", bizType: "FUNERAL", amount: 6000, reason: "闭环丧葬测试", heirName: "赵小明", }); check("特殊业务创建", sp.code === 200 && sp.data && sp.data.id, sp.data && sp.data.id); const spPage = await req("GET", "/api/special-biz/page?bizType=FUNERAL&pageNum=1&pageSize=50"); check("特殊业务可分页查出", spPage.code === 200 && spPage.data.total >= 1, "total=" + (spPage.data && spPage.data.total)); if (sp.data && sp.data.id) { const ap = await req("POST", "/api/special-biz/leader-approve", { bizId: sp.data.id, action: "PASS", opinion: "同意丧葬", }); check("特殊业务审批", ap.code === 200 && ap.data.approveStatus === "已通过", ap.data && ap.data.approveStatus); } // 7 调标落库 const std = await req("POST", "/api/amount-standards", { townId: "T_YEXIE", standardType: "MONTHLY", amount: 1380, effectiveTime: Date.now(), adjustReason: "闭环调标", }); check("调标保存", std.code === 200, std.message); const cur = await req("GET", "/api/amount-standards/current?townId=T_YEXIE&standardType=MONTHLY"); check("当前月补变为1380", cur.code === 200 && Number(cur.data.amount) === 1380, "amount=" + (cur.data && cur.data.amount)); // 8 历史批次真数据(回盘+归档) const hist = await req("POST", "/api/dashboard/history-batches", { pageNum: 1, pageSize: 20, townId: "T_YEXIE", }); check( "历史批次含真实回盘/归档", hist.code === 200 && (hist.data.list || []).length >= 1 && !(hist.data.list || []).some((x) => x.id === "VB202510001"), "ids=" + (hist.data.list || []).map((x) => x.id + ":" + x.status).join(",") ); const fail = asserts.filter((a) => !a.pass); console.log("\nSUMMARY pass=" + (asserts.length - fail.length) + "/" + asserts.length); if (fail.length) { fail.forEach((f) => console.log(" - " + f.name + " :: " + f.detail)); process.exit(2); } })().catch((e) => { console.error(e); process.exit(1); });