const path = require("node:path"); const { pathToFileURL } = require("node:url"); let gate; function csvEscape(value) { return `"${String(value).replace(/"/g, "\"\"")}"`; } function buildCsv(rows) { const header = [ "ID", "Title", "Description", "Acceptance", "Test_Method", "Tools", "Dev_Status", "Review1_Status", "Regression_Status", "Files", "Dependencies", "Notes", ]; return [ header.join(","), ...rows.map((row) => header.map((column) => csvEscape(row[column] ?? "")).join(",")), ].join("\n"); } function validFixtureRow(overrides = {}) { return { ID: "WAPC2F-0", Title: "合法企微审批样件合同", Description: "WAPC2F 自动生成 OA-POC 请假/补卡测试模板;路线:WAPC2F -> WCBK1 -> WACP3 -> WADM1 -> wecom_attendance_approval_mirror_v1。", Acceptance: "固定 WECOM_AGENT_SECRET;不尝试其他 Secret;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审,不是同一 OA 员工,不是同一企微 userid;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", Test_Method: "受影响流程:WAPC2 缺配置 -> 自动生成 POC 样件 -> WAPC2 自动复跑;验证步骤:node scripts/qa/check-process-contract.mjs --issue fixture.csv --profile wecom-approval-fixture --json。", Tools: "node|vitest", Dev_Status: "TODO", Review1_Status: "TODO", Regression_Status: "TODO", Files: "scripts/qa/check-wecom-approval-fixtures.js|server/package.json", Dependencies: "[]", Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 sp_no、完整 userid 或原始 payload;无 fallback。", ...overrides, }; } function validCallbackRow(overrides = {}) { return { ID: "WCBK1-0", Title: "合法企微审批回调只读合同", Description: "WCBK1 回调只读 POC,路线:WCBK1 -> WACP3 -> WADM1 -> wecom_attendance_approval_mirror_v1;范围只验证企微审批回调。", Acceptance: "固定 WECOM_APPROVAL_CALLBACK_URL、WECOM_CALLBACK_TOKEN、WECOM_CALLBACK_ENCODING_AES_KEY;GET /api/wecom/approval/callback 做 URL 验证;POST /api/wecom/approval/callback 接收事件;使用 msg_signature 验签和 EncodingAESKey 解密;只读、幂等,不写 OA 业务镜像,不写企微数据;不使用通讯录 Secret,不尝试其他 Secret,不允许运行时猜 Secret;缺配置记录 NEEDS_CONFIG,外部阻断记录 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", Test_Method: "受影响流程:WAPC2FLIVE 样件 -> WCBK1 回调只读 POC -> WACP3 补偿拉取;验证步骤:node scripts/qa/check-process-contract.mjs --issue callback.csv --profile wecom-callback --json。", Tools: "node|vitest", Dev_Status: "TODO", Review1_Status: "TODO", Regression_Status: "TODO", Files: "server/src/routes/wecomApprovalCallback.js|server/src/services/integrations/wecomCallbackCrypto.js", Dependencies: "[]", Notes: "回滚路径:移除回调 URL 或新增路由;报告不得输出原始 XML、密文、完整 sp_no、完整 userid、完整 template_id、Token、EncodingAESKey 或原始 payload。", ...overrides, }; } function evaluate(rows, profile = "wecom-approval-fixture") { return gate.evaluateIssueContract({ csvText: buildCsv(rows), issuePath: "fixture.csv", profile, }); } function errorCodes(result) { return result.errors.map((item) => item.code); } describe("check-process-contract", () => { beforeAll(async () => { const scriptPath = path.resolve(__dirname, "../../../../scripts/qa/check-process-contract.mjs"); gate = await import(pathToFileURL(scriptPath).href); }); it("passes a valid WAPC2F fixture contract", () => { const result = evaluate([validFixtureRow()]); expect(result.status).toBe("PASS"); expect(errorCodes(result)).toEqual([]); expect(result.checks.some((item) => item.name === "profile.wecom_approval_fixture")).toBe(true); }); it("passes a valid WCBK1 callback contract", () => { const result = evaluate([validCallbackRow()], "wecom-callback"); expect(result.status).toBe("PASS"); expect(errorCodes(result)).toEqual([]); expect(result.checks.some((item) => item.name === "profile.wecom_callback")).toBe(true); }); it("fails when Dependencies is not a JSON array", () => { const result = evaluate([validFixtureRow({ Dependencies: "WAPC2F-0" })]); expect(result.status).toBe("FAIL"); expect(errorCodes(result)).toContain("csv.dependencies"); }); it("fails when Test_Method lacks affected business flow wording", () => { const result = evaluate([validFixtureRow({ Test_Method: "验证步骤:node scripts/qa/check-process-contract.mjs --baseline。" })]); expect(result.status).toBe("FAIL"); expect(errorCodes(result)).toContain("csv.test_method"); }); it("fails self-approval defaults", () => { const result = evaluate([ validFixtureRow({ Acceptance: "默认 wangshunli -> wangshunli;固定 WECOM_AGENT_SECRET;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", }), ]); expect(result.status).toBe("FAIL"); expect(errorCodes(result)).toContain("fixture.self_approval"); }); it("fails when live write switch is missing", () => { const row = validFixtureRow({ Acceptance: "固定 WECOM_AGENT_SECRET;不尝试其他 Secret;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", }); const result = evaluate([row]); expect(result.status).toBe("FAIL"); expect(errorCodes(result)).toContain("fixture.required_term"); expect(result.errors.some((item) => item.message.includes("WECOM_APPROVAL_FIXTURE_WRITE=1"))).toBe(true); }); it("fails when Secret fallback is not explicitly forbidden", () => { const row = validFixtureRow({ Acceptance: "固定 WECOM_AGENT_SECRET;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 sp_no、完整 userid 或原始 payload。", }); const result = evaluate([row]); expect(result.status).toBe("FAIL"); expect(errorCodes(result)).toContain("wecom.secret_source"); }); it("fails when privacy report boundary is incomplete", () => { const row = validFixtureRow({ Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 userid 或原始 payload;无 fallback。", }); const result = evaluate([row]); expect(result.status).toBe("FAIL"); expect(errorCodes(result)).toContain("fixture.privacy"); }); it("parses CLI options for baseline and issue profiles", () => { expect(gate.parseArgs(["--baseline", "--json"])).toMatchObject({ baseline: true, json: true, }); expect(gate.parseArgs(["--issue", "a.csv", "--profile", "wecom"])).toMatchObject({ issue: "a.csv", profile: "wecom", }); expect(gate.parseArgs(["--issue", "a.csv", "--profile", "wecom-callback"])).toMatchObject({ issue: "a.csv", profile: "wecom-callback", }); expect(() => gate.parseArgs(["--issue", "a.csv", "--profile", "unknown"])).toThrow(/unsupported profile/); }); });