| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- /**
- * 按业务流程全量调用本地 Swagger 接口。
- * 用法: node scripts/e2e-api-flow-test.js
- */
- const http = require("http");
- const fs = require("fs");
- const path = require("path");
- const BASE = "http://localhost:8088/sjnmtybt";
- const results = [];
- function req(method, pathName, body, contentType) {
- return new Promise((resolve) => {
- const u = new URL(BASE + pathName);
- let data = null;
- const headers = { Accept: "application/json" };
- if (body != null) {
- if (Buffer.isBuffer(body)) {
- data = body;
- if (contentType) headers["Content-Type"] = contentType;
- } else if (typeof body === "string") {
- data = body;
- headers["Content-Type"] = contentType || "application/json";
- } else {
- data = JSON.stringify(body);
- headers["Content-Type"] = "application/json";
- }
- headers["Content-Length"] = Buffer.byteLength(data);
- }
- const r = http.request(
- { hostname: u.hostname, port: u.port, path: u.pathname + u.search, method, headers },
- (res) => {
- let raw = "";
- res.on("data", (c) => (raw += c));
- res.on("end", () => {
- let json = null;
- try {
- json = JSON.parse(raw);
- } catch (e) {
- json = { parseError: true, raw: raw.slice(0, 400) };
- }
- resolve({ status: res.statusCode, json, raw });
- });
- }
- );
- r.on("error", (e) => resolve({ status: 0, json: { error: e.message }, raw: "" }));
- if (data) r.write(data);
- r.end();
- });
- }
- function okBiz(resp) {
- if (resp.status !== 200) return false;
- if (!resp.json || resp.json.parseError || resp.json.error) return false;
- if (typeof resp.json.code === "number") return resp.json.code === 200;
- return true;
- }
- async function call(lane, name, method, pathName, body, opts) {
- opts = opts || {};
- const resp = await req(method, pathName, body, opts.contentType);
- const pass = opts.expectFail ? !okBiz(resp) : okBiz(resp);
- const bizCode = resp.json && resp.json.code;
- const msg = (resp.json && (resp.json.message || resp.json.error)) || "";
- const data = resp.json && resp.json.data;
- let hint = "";
- if (data == null) hint = "data=null";
- else if (Array.isArray(data)) hint = "arr=" + data.length;
- else if (data.list) hint = "total=" + data.total + ",list=" + data.list.length;
- else if (typeof data === "object") hint = "keys=" + Object.keys(data).slice(0, 10).join(",");
- else hint = String(data).slice(0, 60);
- const row = {
- lane,
- name,
- method,
- path: pathName,
- http: resp.status,
- biz: bizCode,
- pass: !!pass,
- msg: String(msg).slice(0, 120),
- hint,
- };
- results.push(row);
- console.log(`[${row.pass ? "OK " : "FAIL"}] ${lane} | ${method} ${pathName} | ${name} | ${hint} | ${row.msg}`);
- return resp;
- }
- async function uploadReturn(batchId) {
- // multipart without form-data dependency
- const boundary = "----SjBoundary" + Date.now();
- const fileContent = "N7R MOCK RETURN FILE\nP001 SUCCESS\nP002 FAIL 003\n";
- const body =
- `--${boundary}\r\n` +
- `Content-Disposition: form-data; name="batchId"\r\n\r\n${batchId}\r\n` +
- `--${boundary}\r\n` +
- `Content-Disposition: form-data; name="file"; filename="LSJ20260320.N7R"\r\n` +
- `Content-Type: application/octet-stream\r\n\r\n` +
- fileContent +
- `\r\n--${boundary}--\r\n`;
- return call(
- "RETURN",
- "上传回盘文件",
- "POST",
- "/api/return-disk/upload",
- Buffer.from(body, "utf8"),
- { contentType: "multipart/form-data; boundary=" + boundary }
- );
- }
- async function main() {
- console.log("=== E2E API FLOW TEST ===\n");
- // 0 META
- await call("META", "流程角色", "GET", "/api/workflow/roles");
- await call("META", "业务泳道", "GET", "/api/workflow/lanes");
- await call("META", "月度节点", "GET", "/api/workflow/stages");
- await call("META", "特殊业务节点", "GET", "/api/workflow/special-biz-nodes");
- await call("META", "看板汇总", "GET", "/api/dashboard/summary?payMonth=202603");
- await call("META", "看板汇总按镇", "GET", "/api/dashboard/summary?payMonth=202603&townId=T_YEXIE");
- await call(
- "META",
- "历史批次",
- "POST",
- "/api/dashboard/history-batches",
- { pageNum: 1, pageSize: 20, townId: "T_YEXIE", status: "RETURNED" }
- );
- // 1 MASTER
- await call("MASTER", "区-镇", "GET", "/api/regions?level=TOWN&parentId=D_SJ");
- await call("MASTER", "镇-村叶榭", "GET", "/api/regions?level=VILLAGE&parentId=T_YEXIE");
- await call("MASTER", "镇-村佘山", "GET", "/api/regions?level=VILLAGE&parentId=T_SHESHAN");
- await call("MASTER", "金额标准", "GET", "/api/amount-standards?townId=T_YEXIE¤t=true");
- await call("MASTER", "当前月补", "GET", "/api/amount-standards/current?townId=T_YEXIE&standardType=MONTHLY");
- await call("MASTER", "当前丧葬", "GET", "/api/amount-standards/current?townId=T_YEXIE&standardType=FUNERAL");
- // 2 MONTHLY PASS
- console.log("\n--- MONTHLY PASS ---\n");
- await call("MONTHLY_PASS", "人员叶榭", "POST", "/api/personnel/page", {
- pageNum: 1,
- pageSize: 50,
- townId: "T_YEXIE",
- });
- await call("MONTHLY_PASS", "人员详情P001", "GET", "/api/personnel/P001");
- await call("MONTHLY_PASS", "人员新增", "POST", "/api/personnel", {
- name: "测试新增",
- idNumber: "310117199001011999",
- townId: "T_YEXIE",
- villageId: "V_YX_001",
- bankCardNumber: "6228480030009999999",
- monthlyStandard: 1280,
- source: "村级录入",
- enjoyStartMonth: "202605",
- });
- await call("MONTHLY_PASS", "批次叶榭", "POST", "/api/batches/page", {
- pageNum: 1,
- pageSize: 20,
- townId: "T_YEXIE",
- });
- await call("MONTHLY_PASS", "批次详情", "GET", "/api/batches/VB202604001");
- await call("MONTHLY_PASS", "流程总览待初审", "GET", "/api/workflow/batches/VB202604001/overview");
- const created = await call("MONTHLY_PASS", "创建村级批次", "POST", "/api/batches", {
- batchNo: "202605",
- batchLevel: "VILLAGE",
- payMonth: "202605",
- townId: "T_YEXIE",
- villageId: "V_YX_001",
- payDate: "2026-05-20",
- personnelIds: ["P001", "P002"],
- });
- const newBatchId = created.json && created.json.data && created.json.data.id;
- if (newBatchId) {
- await call("MONTHLY_PASS", "提交镇经发", "POST", `/api/batches/${newBatchId}/submit`);
- } else {
- results.push({
- lane: "MONTHLY_PASS",
- name: "提交镇经发",
- method: "SKIP",
- path: "/api/batches/{id}/submit",
- http: 0,
- biz: null,
- pass: false,
- msg: "无新批次ID",
- hint: "",
- });
- }
- await call("MONTHLY_PASS", "镇初审通过", "POST", "/api/batches/town-approve", {
- bizId: "VB202604001",
- action: "PASS",
- opinion: "材料齐全,同意",
- operator: "镇经发-王审",
- });
- await call("MONTHLY_PASS", "触发居保匹配", "POST", "/api/insurance-match/trigger", {
- batchId: "VB202604001",
- personnelIds: ["P001", "P003"],
- });
- await call("MONTHLY_PASS", "匹配结果列表", "GET", "/api/insurance-match/batches/VB202604001/results");
- await call("MONTHLY_PASS", "人工确认居保", "POST", "/api/insurance-match/manual-confirm", {
- personnelId: "P005",
- batchId: "VB202604001",
- pass: true,
- remark: "人工核实通过",
- });
- await call("MONTHLY_PASS", "生成月补贴清单", "POST", "/api/payments/batches/MB202604002/generate");
- await call("MONTHLY_PASS", "发放明细待审批", "POST", "/api/payments/details/page", {
- pageNum: 1,
- pageSize: 50,
- batchId: "MB202604002",
- });
- await call("MONTHLY_PASS", "领导审批通过", "POST", "/api/batches/leader-approve", {
- bizId: "MB202604002",
- action: "PASS",
- opinion: "同意发放",
- });
- await call("MONTHLY_PASS", "生成对照出盘", "POST", "/api/payments/export-disk", {
- batchId: "MB202604002",
- payBank: "农商",
- adjustOnly: false,
- });
- await call("MONTHLY_PASS", "流程分页", "POST", "/api/process-logs/page", {
- pageNum: 1,
- pageSize: 50,
- batchId: "VB202604001",
- });
- await call("MONTHLY_PASS", "流程时间线", "GET", "/api/process-logs/batches/VB202604001/timeline");
- await call("MONTHLY_PASS", "流程总览领导批", "GET", "/api/workflow/batches/MB202604002/overview");
- // 3 REJECT
- console.log("\n--- MONTHLY REJECT ---\n");
- await call("MONTHLY_REJECT", "镇初审驳回", "POST", "/api/batches/town-approve", {
- bizId: "VB202604001",
- action: "REJECT",
- opinion: "材料不齐",
- });
- await call("MONTHLY_REJECT", "领导审批驳回", "POST", "/api/batches/leader-approve", {
- bizId: "MB202604002",
- action: "REJECT",
- opinion: "金额有误",
- });
- // 4 SPECIAL
- console.log("\n--- SPECIAL ---\n");
- await call("SPECIAL", "人员状态暂停", "PUT", "/api/personnel/status", {
- personnelId: "P006",
- bizStatus: "PAUSED",
- payThisMonth: false,
- remarks: "暂停测试",
- });
- const sp = await call("SPECIAL", "创建丧葬特殊业务", "POST", "/api/special-biz", {
- personnelId: "P004",
- batchNo: "202603",
- bizType: "FUNERAL",
- amount: 6000,
- reason: "死亡丧葬补贴",
- heirName: "赵小明",
- heirIdNumber: "310117199001011111",
- heirBankCard: "6228480030005234567",
- });
- const spId = sp.json && sp.json.data && sp.json.data.id;
- await call("SPECIAL", "特殊业务分页", "GET", "/api/special-biz/page?bizType=FUNERAL&pageNum=1&pageSize=20");
- if (spId) {
- await call("SPECIAL", "特殊业务领导审批", "POST", "/api/special-biz/leader-approve", {
- bizId: spId,
- action: "PASS",
- opinion: "同意",
- });
- await call("SPECIAL", "特殊业务出盘", "POST", `/api/special-biz/${spId}/export-disk`);
- }
- await call("SPECIAL", "调标保存", "POST", "/api/amount-standards", {
- townId: "T_YEXIE",
- standardType: "MONTHLY",
- amount: 1380,
- effectiveTime: Date.now(),
- adjustReason: "2026调标测试",
- });
- await call("SPECIAL", "创建调标特殊业务", "POST", "/api/special-biz", {
- personnelId: "P001",
- bizType: "STANDARD_ADJUST",
- amount: 100,
- reason: "调标差额补发",
- });
- await call("SPECIAL", "告警分页", "POST", "/api/alerts/page", { pageNum: 1, pageSize: 50 });
- const alerts = await call("SPECIAL", "死亡告警", "POST", "/api/alerts/page", {
- pageNum: 1,
- pageSize: 20,
- type: "DEATH_STATUS",
- });
- const alertId =
- alerts.json && alerts.json.data && alerts.json.data.list && alerts.json.data.list[0] && alerts.json.data.list[0].id;
- if (alertId) {
- await call("SPECIAL", "告警详情", "GET", `/api/alerts/${alertId}`);
- await call("SPECIAL", "告警处置", "POST", "/api/alerts/handle", {
- alertId,
- handleStatus: "CLOSED",
- handleResult: "已停发并进入丧葬流程",
- handler: "业务员A",
- });
- }
- // 5 RETURN
- console.log("\n--- RETURN ---\n");
- await uploadReturn("MB202603001");
- await call("RETURN", "回盘确认", "POST", "/api/return-disk/confirm", {
- batchId: "MB202603001",
- fileName: "LSJ20260320.N7R",
- summaryNo: "170000061804",
- returnDate: "2026-03-22",
- });
- await call("RETURN", "回盘明细", "POST", "/api/return-disk/details/page", {
- pageNum: 1,
- pageSize: 50,
- batchId: "MB202603001",
- });
- await call("RETURN", "批次统计", "POST", "/api/batch-stats/page", {
- pageNum: 1,
- pageSize: 20,
- batchNo: "MB202603001",
- });
- await call("RETURN", "重算统计", "POST", "/api/batch-stats/recalculate/MB202603001");
- await call("RETURN", "发放明细已回盘", "POST", "/api/payments/details/page", {
- pageNum: 1,
- pageSize: 50,
- batchId: "MB202603001",
- });
- await call("RETURN", "流程总览已回盘", "GET", "/api/workflow/batches/MB202603001/overview");
- await call("RETURN", "归档批次", "POST", "/api/dashboard/archive/MB202603001");
- // 6 OTHER
- console.log("\n--- OTHER ---\n");
- await call("OTHER", "区划保存", "POST", "/api/regions", {
- code: "V_TEST",
- name: "测试村",
- level: "VILLAGE",
- parentId: "T_YEXIE",
- enabled: true,
- });
- await call("OTHER", "区划删除", "DELETE", "/api/regions/V_TEST");
- await call("OTHER", "人员删除", "DELETE", "/api/personnel/P_DRAFT_X");
- await call("OTHER", "人员按状态", "POST", "/api/personnel/page", {
- pageNum: 1,
- pageSize: 20,
- bizStatus: "NORMAL",
- });
- await call("OTHER", "人员按村", "POST", "/api/personnel/page", {
- pageNum: 1,
- pageSize: 20,
- villageId: "V_YX_001",
- });
- await call("OTHER", "批次按阶段", "POST", "/api/batches/page", {
- pageNum: 1,
- pageSize: 20,
- currentStage: "STAGE5_LEADER_ONLINE_APPROVE",
- });
- await call("OTHER", "不存在人员应失败", "GET", "/api/personnel/NOT_EXIST", null, { expectFail: true });
- // Excel import empty multipart-ish skip if hard; try minimal
- const b = "----B" + Date.now();
- const excelBody =
- `--${b}\r\nContent-Disposition: form-data; name="townId"\r\n\r\nT_YEXIE\r\n` +
- `--${b}\r\nContent-Disposition: form-data; name="villageId"\r\n\r\nV_YX_001\r\n` +
- `--${b}\r\nContent-Disposition: form-data; name="file"; filename="p.xlsx"\r\n` +
- `Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n\r\n` +
- `fake\r\n--${b}--\r\n`;
- await call("OTHER", "人员Excel导入契约", "POST", "/api/personnel/import", Buffer.from(excelBody), {
- contentType: "multipart/form-data; boundary=" + b,
- });
- const fail = results.filter((r) => !r.pass);
- const pass = results.filter((r) => r.pass);
- console.log("\n=== SUMMARY ===");
- console.log("total=", results.length, "pass=", pass.length, "fail=", fail.length);
- if (fail.length) {
- console.log("\nFAILED:");
- fail.forEach((f) =>
- console.log("-", f.lane, f.method, f.path, "|", f.name, "| http=", f.http, "biz=", f.biz, "|", f.msg, "|", f.hint)
- );
- }
- const out = path.join(__dirname, "e2e-api-flow-result.json");
- fs.writeFileSync(out, JSON.stringify({ summary: { total: results.length, pass: pass.length, fail: fail.length }, results }, null, 2));
- console.log("\nWrote", out);
- process.exit(fail.length ? 2 : 0);
- }
- main().catch((e) => {
- console.error(e);
- process.exit(1);
- });
|