import { test } from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { lineActionErrorDetail, runLineAction } from "../src/utils/lineOffline"; /** 线级启停纯逻辑单测:不发真实请求,边界=请求/刷新编排,不代表后端或真实 UI 已验证 */ test("停发成功:按 flowGuid 调一次接口,按返回行数刷新", async () => { const calls: string[] = []; let reloads = 0; const out = await runLineAction({ flowGuid: " g1 ", lineOffline: 0, action: "offline", request: async (guid) => { calls.push(guid); return 3; }, reload: async () => { reloads++; }, }); assert.deepEqual(calls, ["g1"]); assert.equal(out.kind, "updated"); assert.equal(out.affected, 3); assert.equal(out.reloadFailed, false); assert.equal(reloads, 1); }); test("恢复成功:已停发线才发恢复请求", async () => { let requested = ""; let reloads = 0; const out = await runLineAction({ flowGuid: "g2", lineOffline: 1, action: "enable", request: async (guid) => { requested = guid; return 2; }, reload: async () => { reloads++; }, }); assert.equal(requested, "g2"); assert.equal(out.kind, "updated"); assert.equal(out.affected, 2); assert.equal(reloads, 1); }); test("重复停发被前端状态守卫拦下:不发请求、不刷新", async () => { let requests = 0; let reloads = 0; const out = await runLineAction({ flowGuid: "g3", lineOffline: 1, action: "offline", request: async () => { requests++; return 1; }, reload: async () => { reloads++; }, }); assert.equal(out.kind, "skipped"); assert.equal(out.reason, "already-offline"); assert.equal(requests, 0); assert.equal(reloads, 0); }); test("恢复一条未停发的线被拦下:不发请求、不刷新", async () => { let requests = 0; const out = await runLineAction({ flowGuid: "g4", lineOffline: 0, action: "enable", request: async () => { requests++; return 1; }, reload: async () => {}, }); assert.equal(out.kind, "skipped"); assert.equal(out.reason, "not-offline"); assert.equal(requests, 0); }); test("空 flowGuid 不发请求(历史脏行防误操作整线)", async () => { let requests = 0; const out = await runLineAction({ flowGuid: "", lineOffline: 0, action: "offline", request: async () => { requests++; return 1; }, reload: async () => {}, }); assert.equal(out.kind, "skipped"); assert.equal(out.reason, "no-guid"); assert.equal(requests, 0); }); test("接口返回 0 行:报 empty 但仍刷新列表以同步真实状态", async () => { let reloads = 0; const out = await runLineAction({ flowGuid: "g5", lineOffline: 0, action: "offline", request: async () => 0, reload: async () => { reloads++; }, }); assert.equal(out.kind, "empty"); assert.equal(out.affected, 0); assert.equal(reloads, 1); }); test("接口失败向上抛出,且不触发刷新", async () => { let reloads = 0; await assert.rejects(() => runLineAction({ flowGuid: "g6", lineOffline: 0, action: "offline", request: async () => { throw new Error("无流程管理权限"); }, reload: async () => { reloads++; }, }), /无流程管理权限/); assert.equal(reloads, 0); }); test("操作成功但刷新失败:保留成功结果并标记 reloadFailed", async () => { const out = await runLineAction({ flowGuid: "g7", lineOffline: 1, action: "enable", request: async () => 1, reload: async () => { throw new Error("网络异常"); }, }); assert.equal(out.kind, "updated"); assert.equal(out.affected, 1); assert.equal(out.reloadFailed, true); }); test("错误文案:权限/网络异常与后端业务错误分开提示", () => { assert.match(lineActionErrorDetail("网络错误"), /权限/); assert.match(lineActionErrorDetail("Failed to fetch"), /网络异常/); assert.match(lineActionErrorDetail(""), /网络异常/); assert.equal(lineActionErrorDetail("流程线不存在:g1"), "流程线不存在:g1"); }); test("静态接线:Defs.vue 使用线级接口,不混入 listAll 或重复查询", () => { const defs = readFileSync(resolve(process.cwd(), "src/views/flow/admin/Defs.vue"), "utf8"); assert.match(defs, /defOfflineLine/); assert.match(defs, /defEnableLine/); assert.match(defs, /lineOffline/); assert.match(defs, /停发流程线/); assert.match(defs, /恢复流程线/); assert.match(defs, /versionActions/); assert.doesNotMatch(defs, /listAll/); }); test("静态接线:API 客户端使用既有 admin 端点与 flowGuid 请求体", () => { const api = readFileSync(resolve(process.cwd(), "src/api/module/flow.ts"), "utf8"); assert.match(api, /`\$\{ROOT\}\/admin\/def\/offlineLine`, \{ flowGuid \}/); assert.match(api, /`\$\{ROOT\}\/admin\/def\/enableLine`, \{ flowGuid \}/); });