const { buildInputs, buildSecretCheck, evaluateConclusion, executeApprovalPoc, parseArgs, } = require("../../../src/scripts/check-wecom-approval-poc"); const { fetchWecomApprovalDetail, fetchWecomApprovalTemplateDetail, maskApprovalIdentifier, summarizeApprovalPayload, summarizeTemplatePayload, } = require("../../../src/services/integrations/wecomApproval"); const { clearWecomAppAccessTokenCache } = require("../../../src/services/integrations/wecomAccessToken"); const SENSITIVE_AGENT_CREDENTIAL = ["approval", "agent", "credential", "should", "never", "leak"].join("-"); const SENSITIVE_CONTACT_CREDENTIAL = ["approval", "contact", "credential", "unused"].join("-"); const SENSITIVE_CHECKIN_CREDENTIAL = ["approval", "checkin", "credential", "unused"].join("-"); const SENSITIVE_HR_CREDENTIAL = ["approval", "hr", "credential", "unused"].join("-"); const SENSITIVE_TEMPLATE_ID = ["tpl", "leave", "full", "identifier", "should", "not", "leak"].join("-"); const SENSITIVE_SP_NO = ["sp", "leave", "full", "identifier", "should", "not", "leak"].join("-"); const SENSITIVE_USER_ID = ["approval", "userid", "full", "should", "not", "leak"].join("_"); const SENSITIVE_FORM_VALUE = "这里是低风险测试单中的表单值但报告不得输出"; function baseConfig(overrides = {}) { return { wecomCorpId: "corp-approval", wecomAgentId: "1000002", wecomAgentSecret: SENSITIVE_AGENT_CREDENTIAL, wecomContactSecret: SENSITIVE_CONTACT_CREDENTIAL, wecomCheckinSecret: SENSITIVE_CHECKIN_CREDENTIAL, wecomHrAssistantSecret: SENSITIVE_HR_CREDENTIAL, wecomRequestTimeoutMs: 1000, ...overrides, }; } function baseOptions(overrides = {}) { return { live: true, format: "json", reportDir: "/tmp/wecom-approval-poc-test", templates: {}, spNos: {}, help: false, ...overrides, }; } function templatePayload(overrides = {}) { return { errcode: 0, errmsg: "ok", template_id: SENSITIVE_TEMPLATE_ID, template_names: [{ text: "请假" }], template_content: { controls: [ { property: { control: "Selector" } }, { property: { control: "Textarea" } }, ], }, ...overrides, }; } function approvalPayload(overrides = {}) { return { errcode: 0, errmsg: "ok", info: { sp_no: SENSITIVE_SP_NO, template_id: SENSITIVE_TEMPLATE_ID, sp_status: 2, applyer: { userid: SENSITIVE_USER_ID }, apply_data: { contents: [ { control: "Textarea", value: { text: SENSITIVE_FORM_VALUE } }, { property: { control: "Vacation" }, value: { text: "年假" } }, ], }, sp_record: [{ userid: SENSITIVE_USER_ID }], notifyer: [{ userid: SENSITIVE_USER_ID }], comments: [{ comment: SENSITIVE_FORM_VALUE }], }, ...overrides, }; } function jsonResponse(payload, status = 200) { return { ok: status >= 200 && status < 300, status, json: async () => payload, }; } describe("check-wecom-approval-poc", () => { beforeEach(() => { clearWecomAppAccessTokenCache(); }); it("parses CLI template and sp_no pairs", () => { const options = parseArgs([ "--live", "--format=json", "--template=leave:tpl-leave", "--template=missed_punch:tpl-missed", "--sp-no=leave:sp-leave", "--sp-no=missed_punch:sp-missed", ]); expect(options.live).toBe(true); expect(options.format).toBe("json"); expect(options.templates).toEqual({ leave: "tpl-leave", missed_punch: "tpl-missed", }); expect(options.spNos).toEqual({ leave: "sp-leave", missed_punch: "sp-missed", }); expect(() => parseArgs(["--template=outing:tpl"])).toThrow(/unsupported target/); }); it("builds inputs from CLI first, then env, with masked previews", () => { const inputs = buildInputs(baseOptions({ templates: { leave: SENSITIVE_TEMPLATE_ID }, spNos: { leave: SENSITIVE_SP_NO }, }), { WECOM_APPROVAL_LEAVE_TEMPLATE_ID: "env-should-not-win", WECOM_APPROVAL_MISSED_PUNCH_TEMPLATE_ID: "env-missed-template", WECOM_APPROVAL_MISSED_PUNCH_TEST_SP_NO: "env-missed-sp", }); expect(inputs.find((item) => item.key === "leave")).toMatchObject({ templateSource: "cli", spNoSource: "cli", templateIdPrefix: maskApprovalIdentifier(SENSITIVE_TEMPLATE_ID), spNoPrefix: maskApprovalIdentifier(SENSITIVE_SP_NO), }); expect(inputs.find((item) => item.key === "missed_punch")).toMatchObject({ templateSource: "WECOM_APPROVAL_MISSED_PUNCH_TEMPLATE_ID", spNoSource: "WECOM_APPROVAL_MISSED_PUNCH_TEST_SP_NO", }); }); it("documents WECOM_AGENT_SECRET as the only approval Secret source", () => { const check = buildSecretCheck(baseConfig()); expect(check).toMatchObject({ status: "OK", source: "WECOM_AGENT_SECRET", endpoints: [ "POST /cgi-bin/oa/gettemplatedetail", "POST /cgi-bin/oa/getapprovaldetail", ], }); expect(check.noFallback).toEqual([ "WECOM_CONTACT_SECRET", "WECOM_CHECKIN_SECRET", "WECOM_HR_ASSISTANT_SECRET", ]); expect(buildSecretCheck(baseConfig({ wecomAgentSecret: "" }))).toMatchObject({ status: "BLOCKED", missing: ["WECOM_AGENT_SECRET"], }); }); it("returns PARTIAL_APPROVAL_POC_NEEDS_CONFIG when template IDs and sp_no are missing", async () => { const fetchTemplateDetailImpl = vi.fn(); const fetchApprovalDetailImpl = vi.fn(); const report = await executeApprovalPoc(baseOptions(), { config: baseConfig(), env: {}, fetchTemplateDetailImpl, fetchApprovalDetailImpl, generatedAt: "2026-05-19T09:00:00.000Z", }); expect(report.status).toBe("PARTIAL_APPROVAL_POC_NEEDS_CONFIG"); expect(report.conclusion).toBe("PARTIAL_APPROVAL_POC_NEEDS_CONFIG"); expect(report.configGaps).toHaveLength(4); expect(report.mainBlockers).toEqual([]); expect(fetchTemplateDetailImpl).not.toHaveBeenCalled(); expect(fetchApprovalDetailImpl).not.toHaveBeenCalled(); }); it("reads leave and missed-punch template/detail in live mode and returns PASS_APPROVAL_POC", async () => { const options = baseOptions({ templates: { leave: "tpl-leave", missed_punch: "tpl-missed", }, spNos: { leave: "sp-leave", missed_punch: "sp-missed", }, }); const fetchTemplateDetailImpl = vi.fn(async ({ config, templateId }) => { expect(config.wecomAgentSecret).toBe(SENSITIVE_AGENT_CREDENTIAL); expect([SENSITIVE_CONTACT_CREDENTIAL, SENSITIVE_CHECKIN_CREDENTIAL, SENSITIVE_HR_CREDENTIAL]).not.toContain(config.wecomAgentSecret); return { ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: summarizeTemplatePayload(templatePayload({ template_id: templateId })), response: { errcode: 0, fieldCount: 2, fieldTypeCounts: { Selector: 1, Textarea: 1 } }, }; }); const fetchApprovalDetailImpl = vi.fn(async ({ spNo }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: summarizeApprovalPayload(approvalPayload({ info: { ...approvalPayload().info, sp_no: spNo } })), response: { errcode: 0, spStatus: 2, applyFieldCount: 2, approvalNodeCount: 1 }, })); const report = await executeApprovalPoc(options, { config: baseConfig(), env: {}, fetchTemplateDetailImpl, fetchApprovalDetailImpl, generatedAt: "2026-05-19T09:00:00.000Z", }); expect(report.status).toBe("PASS_APPROVAL_POC"); expect(report.configGaps).toEqual([]); expect(report.mainBlockers).toEqual([]); expect(fetchTemplateDetailImpl).toHaveBeenCalledTimes(2); expect(fetchApprovalDetailImpl).toHaveBeenCalledTimes(2); expect(report.checks.targets.every((item) => item.template.status === "OK" && item.approval.status === "OK")).toBe(true); }); it("keeps WeCom API errors as BLOCKED_BY_EXTERNAL_CONFIG", async () => { const report = await executeApprovalPoc(baseOptions({ templates: { leave: "tpl-leave", missed_punch: "tpl-missed" }, spNos: { leave: "sp-leave", missed_punch: "sp-missed" }, }), { config: baseConfig(), env: {}, fetchTemplateDetailImpl: vi.fn(async ({ templateId }) => ({ ok: templateId !== "tpl-missed", tokenSource: "WECOM_AGENT_SECRET", code: "wecom_approval_api_error", message: "WeCom approval API errcode 48002", response: { errcode: 48002, errmsg: "api forbidden" }, summary: templateId === "tpl-missed" ? null : summarizeTemplatePayload(templatePayload({ template_id: templateId })), })), fetchApprovalDetailImpl: vi.fn(async ({ spNo }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: summarizeApprovalPayload(approvalPayload({ info: { ...approvalPayload().info, sp_no: spNo } })), response: { errcode: 0, applyFieldCount: 2, approvalNodeCount: 1 }, })), generatedAt: "2026-05-19T09:00:00.000Z", }); expect(report.status).toBe("SKIP"); expect(report.conclusion).toBe("BLOCKED_BY_EXTERNAL_CONFIG"); expect(report.mainBlockers).toEqual(expect.arrayContaining([ expect.objectContaining({ id: "approval_template_detail", target: "missed_punch", reason: "wecom_approval_api_error", }), ])); }); it("uses WECOM_AGENT_SECRET for template/detail API calls without fallback", async () => { const calls = []; const fetchImpl = vi.fn(async (url, options = {}) => { calls.push({ url: String(url), body: options.body ? JSON.parse(options.body) : null, }); if (String(url).includes("/cgi-bin/gettoken")) { expect(String(url)).toContain(`corpsecret=${encodeURIComponent(SENSITIVE_AGENT_CREDENTIAL)}`); expect(String(url)).not.toContain(SENSITIVE_CONTACT_CREDENTIAL); expect(String(url)).not.toContain(SENSITIVE_CHECKIN_CREDENTIAL); expect(String(url)).not.toContain(SENSITIVE_HR_CREDENTIAL); return jsonResponse({ errcode: 0, access_token: "approval-token", expires_in: 7200 }); } if (String(url).includes("/cgi-bin/oa/gettemplatedetail")) { return jsonResponse(templatePayload()); } if (String(url).includes("/cgi-bin/oa/getapprovaldetail")) { return jsonResponse(approvalPayload()); } throw new Error(`unexpected url ${url}`); }); const template = await fetchWecomApprovalTemplateDetail({ config: baseConfig(), templateId: SENSITIVE_TEMPLATE_ID, fetchImpl, }); const detail = await fetchWecomApprovalDetail({ config: baseConfig(), spNo: SENSITIVE_SP_NO, fetchImpl, }); expect(template).toMatchObject({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: { fieldCount: 2, }, }); expect(detail).toMatchObject({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: { applyFieldCount: 2, approvalNodeCount: 1, }, }); expect(calls.some((item) => item.url.includes("/cgi-bin/oa/gettemplatedetail") && item.body.template_id === SENSITIVE_TEMPLATE_ID)).toBe(true); expect(calls.some((item) => item.url.includes("/cgi-bin/oa/getapprovaldetail") && item.body.sp_no === SENSITIVE_SP_NO)).toBe(true); }); it("does not treat non-json approval API responses as success", async () => { const fetchImpl = vi.fn(async (url) => { if (String(url).includes("/cgi-bin/gettoken")) { return jsonResponse({ errcode: 0, access_token: "approval-token", expires_in: 7200 }); } return { ok: true, status: 200, json: async () => { throw new Error("invalid json"); }, }; }); const template = await fetchWecomApprovalTemplateDetail({ config: baseConfig(), templateId: SENSITIVE_TEMPLATE_ID, fetchImpl, }); expect(template).toMatchObject({ ok: false, code: "wecom_approval_http_200", summary: null, }); }); it("keeps report output redacted", async () => { const report = await executeApprovalPoc(baseOptions({ templates: { leave: SENSITIVE_TEMPLATE_ID, missed_punch: "tpl-missed" }, spNos: { leave: SENSITIVE_SP_NO, missed_punch: "sp-missed" }, }), { config: baseConfig(), env: {}, fetchTemplateDetailImpl: vi.fn(async ({ templateId }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: summarizeTemplatePayload(templatePayload({ template_id: templateId })), response: { errcode: 0, fieldCount: 2, fieldTypeCounts: { Selector: 1, Textarea: 1 } }, })), fetchApprovalDetailImpl: vi.fn(async ({ spNo }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: summarizeApprovalPayload(approvalPayload({ info: { ...approvalPayload().info, sp_no: spNo } })), response: { errcode: 0, spStatus: 2, applyFieldCount: 2, approvalNodeCount: 1 }, })), generatedAt: "2026-05-19T09:00:00.000Z", }); const serialized = JSON.stringify(report); expect(serialized).not.toContain(SENSITIVE_AGENT_CREDENTIAL); expect(serialized).not.toContain(SENSITIVE_CONTACT_CREDENTIAL); expect(serialized).not.toContain(SENSITIVE_CHECKIN_CREDENTIAL); expect(serialized).not.toContain(SENSITIVE_HR_CREDENTIAL); expect(serialized).not.toContain(SENSITIVE_TEMPLATE_ID); expect(serialized).not.toContain(SENSITIVE_SP_NO); expect(serialized).not.toContain(SENSITIVE_USER_ID); expect(serialized).not.toContain(SENSITIVE_FORM_VALUE); expect(serialized).toContain(maskApprovalIdentifier(SENSITIVE_TEMPLATE_ID)); expect(serialized).toContain(maskApprovalIdentifier(SENSITIVE_SP_NO)); }); it("evaluates conclusion categories", () => { expect(evaluateConclusion({ mainBlockers: [{ id: "approval_secret_matrix" }], configGaps: [], verificationGaps: [], })).toEqual({ status: "SKIP", conclusion: "BLOCKED_BY_EXTERNAL_CONFIG", }); expect(evaluateConclusion({ mainBlockers: [], configGaps: [{ id: "approval_detail" }], verificationGaps: [], })).toEqual({ status: "PARTIAL_APPROVAL_POC_NEEDS_CONFIG", conclusion: "PARTIAL_APPROVAL_POC_NEEDS_CONFIG", }); expect(evaluateConclusion({ mainBlockers: [], configGaps: [], verificationGaps: [], })).toEqual({ status: "PASS_APPROVAL_POC", conclusion: "PASS_APPROVAL_POC", }); }); });