const { buildInputs, buildSecretCheck, buildTimeWindow, compareCallbackEvidence, executeCompensationPoc, parseArgs, statusLabelFromSpStatus, } = require("../../../src/scripts/check-wecom-approval-compensation-poc"); const { fetchWecomApprovalInfoList, maskApprovalIdentifier, } = require("../../../src/services/integrations/wecomApproval"); const { clearWecomAppAccessTokenCache } = require("../../../src/services/integrations/wecomAccessToken"); const SENSITIVE_AGENT_CREDENTIAL = ["compensation", "agent", "credential", "should", "never", "leak"].join("-"); const SENSITIVE_CONTACT_CREDENTIAL = ["compensation", "contact", "credential", "unused"].join("-"); const SENSITIVE_CHECKIN_CREDENTIAL = ["compensation", "checkin", "credential", "unused"].join("-"); const SENSITIVE_HR_CREDENTIAL = ["compensation", "hr", "credential", "unused"].join("-"); const SENSITIVE_TEMPLATE_ID = ["tpl", "compensation", "full", "identifier", "must", "not", "leak"].join("-"); const SENSITIVE_SP_NO = ["sp", "compensation", "full", "identifier", "must", "not", "leak"].join("-"); const SENSITIVE_USER_ID = ["compensation", "userid", "full", "must", "not", "leak"].join("_"); const SENSITIVE_FORM_VALUE = "这里是审批表单值,报告不得输出"; function baseConfig(overrides = {}) { return { wecomCorpId: "corp-compensation", wecomAgentId: "1000002", wecomAgentSecret: SENSITIVE_AGENT_CREDENTIAL, wecomContactSecret: SENSITIVE_CONTACT_CREDENTIAL, wecomCheckinSecret: SENSITIVE_CHECKIN_CREDENTIAL, wecomHrAssistantSecret: SENSITIVE_HR_CREDENTIAL, wecomApprovalCallbackEvidenceFile: "/tmp/wacp3-test-evidence.jsonl", wecomRequestTimeoutMs: 1000, ...overrides, }; } function baseOptions(overrides = {}) { return { live: true, format: "json", reportDir: "/tmp/wecom-approval-compensation-test", startTime: "2026-05-19T00:00:00.000Z", endTime: "2026-05-20T00:00:00.000Z", templates: { leave: SENSITIVE_TEMPLATE_ID, missed_punch: "tpl-missed-punch", }, size: 100, maxPages: 10, help: false, ...overrides, }; } function jsonResponse(payload, status = 200) { return { ok: status >= 200 && status < 300, status, json: async () => payload, }; } function detailSummary(spNo = SENSITIVE_SP_NO, overrides = {}) { return { spNoPrefix: maskApprovalIdentifier(spNo), templateIdPrefix: maskApprovalIdentifier(SENSITIVE_TEMPLATE_ID), spStatus: "2", applyFieldCount: 3, approvalNodeCount: 1, notifyerCount: 1, commentCount: 0, ...overrides, }; } describe("check-wecom-approval-compensation-poc", () => { beforeEach(() => { clearWecomAppAccessTokenCache(); }); it("parses CLI window, size and template filters", () => { const options = parseArgs([ "--live", "--format=json", "--start-time=2026-05-19T00:00:00.000Z", "--end-time=2026-05-20T00:00:00.000Z", "--size=50", "--template=leave:tpl-leave", "--template=missed_punch:tpl-missed", "--discover-template-ids", "--discover-applicant-employee-no=SMOKE002", ]); expect(options.live).toBe(true); expect(options.format).toBe("json"); expect(options.size).toBe(50); expect(options.discoverTemplateIds).toBe(true); expect(options.discoverApplicantEmployeeNo).toBe("SMOKE002"); expect(options.templates).toEqual({ leave: "tpl-leave", missed_punch: "tpl-missed", }); expect(() => parseArgs(["--template=outing:tpl"])).toThrow(/unsupported target/); expect(() => parseArgs(["--size=101"])).toThrow(/--size/); }); it("builds a default 72-hour time window", () => { const window = buildTimeWindow(baseOptions({ startTime: "", endTime: "" }), "2026-05-20T00:00:00.000Z"); expect(window.endTime - window.startTime).toBe(72 * 60 * 60); expect(window.defaultWindowHours).toBe(72); }); it("builds inputs from CLI first, then env, with masked template previews", () => { const inputs = buildInputs(baseOptions({ templates: { leave: SENSITIVE_TEMPLATE_ID }, }), { WECOM_APPROVAL_LEAVE_TEMPLATE_ID: "env-should-not-win", WECOM_APPROVAL_MISSED_PUNCH_TEMPLATE_ID: "env-missed-template", }); expect(inputs.find((item) => item.key === "leave")).toMatchObject({ templateSource: "cli", templateIdPrefix: maskApprovalIdentifier(SENSITIVE_TEMPLATE_ID), }); expect(inputs.find((item) => item.key === "missed_punch")).toMatchObject({ templateSource: "WECOM_APPROVAL_MISSED_PUNCH_TEMPLATE_ID", templateIdPrefix: maskApprovalIdentifier("env-missed-template"), }); }); it("documents WECOM_AGENT_SECRET as the only Secret source", () => { const check = buildSecretCheck(baseConfig()); expect(check).toMatchObject({ status: "OK", source: "WECOM_AGENT_SECRET", endpoints: [ "POST /cgi-bin/oa/getapprovalinfo", "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("calls getapprovalinfo with template filter and redacts sp_no list", async () => { const fetchImpl = vi.fn(async (url, init) => { const urlText = String(url); if (urlText.includes("/cgi-bin/gettoken")) { expect(urlText).toContain("corpsecret="); return jsonResponse({ errcode: 0, errmsg: "ok", access_token: "token-compensation", expires_in: 7200 }); } expect(urlText).toContain("/cgi-bin/oa/getapprovalinfo"); expect(urlText).not.toContain(SENSITIVE_AGENT_CREDENTIAL); const body = JSON.parse(init.body); expect(body).toMatchObject({ starttime: 1779148800, endtime: 1779235200, size: 50, filters: [{ key: "template_id", value: SENSITIVE_TEMPLATE_ID }], }); return jsonResponse({ errcode: 0, errmsg: "ok", sp_no_list: [SENSITIVE_SP_NO], next_cursor: "", }); }); const result = await fetchWecomApprovalInfoList({ config: baseConfig(), startTime: 1779148800, endTime: 1779235200, size: 50, filters: [{ key: "template_id", value: SENSITIVE_TEMPLATE_ID }], fetchImpl, }); expect(result.ok).toBe(true); expect(result.spNos).toEqual([SENSITIVE_SP_NO]); expect(result.response.spNoPrefixes).toEqual([maskApprovalIdentifier(SENSITIVE_SP_NO)]); expect(JSON.stringify(result.response)).not.toContain(SENSITIVE_SP_NO); }); it("returns PARTIAL_COMPENSATION_NEEDS_CONFIG when template filters are missing", async () => { const report = await executeCompensationPoc(baseOptions({ templates: {}, }), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", fetchApprovalInfoListImpl: vi.fn(), fetchApprovalDetailImpl: vi.fn(), readCallbackEvidenceImpl: vi.fn(async () => []), }); expect(report.status).toBe("PARTIAL_COMPENSATION_NEEDS_CONFIG"); expect(report.configGaps.map((item) => item.id)).toContain("approval_compensation_pull"); expect(report.configGaps.map((item) => item.id)).not.toContain("approval_compensation_window_empty"); expect(report.checks.targets.every((item) => item.status === "NEEDS_CONFIG")).toBe(true); }); it("returns PARTIAL_COMPENSATION_NEEDS_CONFIG in dry-run even when templates are configured", async () => { const report = await executeCompensationPoc(baseOptions({ live: false }), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", readCallbackEvidenceImpl: vi.fn(async () => []), }); expect(report.status).toBe("PARTIAL_COMPENSATION_NEEDS_CONFIG"); expect(report.configGaps.map((item) => item.id)).toContain("live_read"); expect(report.checks.targets.every((item) => item.status === "DRY_RUN_READY")).toBe(true); }); it("auto-discovers missing template IDs by applicant creator without leaking full identifiers", async () => { const discoveredLeaveTemplateId = "tpllv-auto-discovered-leave-sensitive"; const discoveredMissedTemplateId = "tplmp-auto-discovered-missed-sensitive"; const fetchApprovalInfoListImpl = vi.fn(async ({ filters }) => { if (filters[0].key === "creator") { return { ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 2, spNoPrefixes: [maskApprovalIdentifier("sp-discovery-leave"), maskApprovalIdentifier("sp-discovery-missed")], }, spNos: ["sp-discovery-leave", "sp-discovery-missed"], nextCursor: "", }; } return { ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 1, spNoPrefixes: [maskApprovalIdentifier(`${filters[0].value}-sp`)], }, spNos: [`${filters[0].value}-sp`], nextCursor: "", }; }); const fetchApprovalDetailImpl = vi.fn(async ({ spNo }) => { if (spNo === "sp-discovery-leave") { return { ok: true, tokenSource: "WECOM_AGENT_SECRET", templateId: discoveredLeaveTemplateId, spName: "OA-POC-请假-2026-05-19", summary: detailSummary(spNo, { templateIdPrefix: maskApprovalIdentifier(discoveredLeaveTemplateId) }), response: { errcode: 0, spStatus: "2" }, }; } if (spNo === "sp-discovery-missed") { return { ok: true, tokenSource: "WECOM_AGENT_SECRET", templateId: discoveredMissedTemplateId, spName: "OA-POC-补卡-2026-05-19", summary: detailSummary(spNo, { templateIdPrefix: maskApprovalIdentifier(discoveredMissedTemplateId) }), response: { errcode: 0, spStatus: "2" }, }; } return { ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo, { templateIdPrefix: spNo.startsWith(discoveredLeaveTemplateId) ? maskApprovalIdentifier(discoveredLeaveTemplateId) : maskApprovalIdentifier(discoveredMissedTemplateId), }), response: { errcode: 0, spStatus: "2" }, }; }); const report = await executeCompensationPoc(baseOptions({ templates: {}, discoverTemplateIds: true, discoverApplicantEmployeeNo: "SMOKE002", }), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", queryApplicantActorImpl: vi.fn(async () => ({ employeeNo: "SMOKE002", status: "active", archived: false, wecomExternalUserId: SENSITIVE_USER_ID, })), fetchApprovalInfoListImpl, fetchApprovalDetailImpl, readCallbackEvidenceImpl: vi.fn(async () => [ { callbackKind: "approval_status_change", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier(`${discoveredLeaveTemplateId}-sp`), }, { callbackKind: "approval_status_change", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier(`${discoveredMissedTemplateId}-sp`), }, ]), }); expect(report.status).toBe("PASS_COMPENSATION_POC"); expect(fetchApprovalInfoListImpl.mock.calls[0][0].filters).toEqual([ { key: "creator", value: SENSITIVE_USER_ID }, ]); expect(fetchApprovalInfoListImpl.mock.calls[1][0].filters).toEqual([ { key: "template_id", value: discoveredLeaveTemplateId }, ]); expect(fetchApprovalInfoListImpl.mock.calls[2][0].filters).toEqual([ { key: "template_id", value: discoveredMissedTemplateId }, ]); const serialized = JSON.stringify(report); expect(serialized).not.toContain(discoveredLeaveTemplateId); expect(serialized).not.toContain(discoveredMissedTemplateId); expect(serialized).not.toContain(SENSITIVE_USER_ID); expect(report.checks.templateDiscovery.discoveredTemplatePrefixes).toEqual({ leave: maskApprovalIdentifier(discoveredLeaveTemplateId), missed_punch: maskApprovalIdentifier(discoveredMissedTemplateId), }); }); it("pulls approval numbers and details, then passes when callback evidence matches", async () => { const fetchApprovalInfoListImpl = vi.fn(async ({ filters }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 1, spNoPrefixes: [maskApprovalIdentifier(`${filters[0].value}-sp`)], }, spNos: [`${filters[0].value}-sp`], nextCursor: "", })); const fetchApprovalDetailImpl = vi.fn(async ({ spNo }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo, { templateIdPrefix: spNo.startsWith(SENSITIVE_TEMPLATE_ID) ? maskApprovalIdentifier(SENSITIVE_TEMPLATE_ID) : maskApprovalIdentifier("tpl-missed-punch"), }), response: { errcode: 0, spStatus: "2", applyFieldCount: 3, approvalNodeCount: 1, commentCount: 0, }, })); const evidence = [ { callbackKind: "approval_status_change", approvalType: "leave", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier(`${SENSITIVE_TEMPLATE_ID}-sp`), templateIdPrefix: maskApprovalIdentifier(SENSITIVE_TEMPLATE_ID), }, { callbackKind: "approval_status_change", approvalType: "missed_punch", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier("tpl-missed-punch-sp"), templateIdPrefix: maskApprovalIdentifier("tpl-missed-punch"), }, ]; const report = await executeCompensationPoc(baseOptions(), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", fetchApprovalInfoListImpl, fetchApprovalDetailImpl, readCallbackEvidenceImpl: vi.fn(async () => evidence), }); expect(report.status).toBe("PASS_COMPENSATION_POC"); expect(fetchApprovalInfoListImpl).toHaveBeenCalledTimes(2); expect(fetchApprovalDetailImpl).toHaveBeenCalledTimes(2); expect(report.checks.callbackCompare.findings).toEqual([]); }); it("returns PASS_WITH_FINDINGS for missing callback and status mismatch", async () => { const report = await executeCompensationPoc(baseOptions(), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", fetchApprovalInfoListImpl: vi.fn(async () => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 1 }, spNos: [SENSITIVE_SP_NO], nextCursor: "", })), fetchApprovalDetailImpl: vi.fn(async ({ spNo }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo, { spStatus: "2" }), response: { errcode: 0, spStatus: "2" }, })), readCallbackEvidenceImpl: vi.fn(async () => [{ callbackKind: "approval_status_change", spStatus: "3", statusLabel: "已驳回", spNoPrefix: maskApprovalIdentifier(SENSITIVE_SP_NO), }]), }); expect(report.status).toBe("PASS_WITH_FINDINGS"); expect(report.checks.callbackCompare.findings.map((item) => item.id)).toContain("callback_status_mismatch"); }); it("treats missing callback evidence after successful detail pull as findings", async () => { const report = await executeCompensationPoc(baseOptions({ templates: { leave: SENSITIVE_TEMPLATE_ID, missed_punch: "tpl-missed-punch" }, }), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", fetchApprovalInfoListImpl: vi.fn(async () => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 1 }, spNos: [SENSITIVE_SP_NO], nextCursor: "", })), fetchApprovalDetailImpl: vi.fn(async ({ spNo }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo, { spStatus: "2" }), response: { errcode: 0, spStatus: "2" }, })), readCallbackEvidenceImpl: vi.fn(async () => []), }); expect(report.status).toBe("PASS_WITH_FINDINGS"); expect(report.configGaps.map((item) => item.id)).not.toContain("callback_evidence_missing"); expect(report.checks.callbackCompare.findings.map((item) => item.id)).toContain("callback_missing_for_pulled_detail"); }); it("classifies WeCom API errors as BLOCKED_BY_EXTERNAL_CONFIG", async () => { const report = await executeCompensationPoc(baseOptions({ templates: { leave: SENSITIVE_TEMPLATE_ID }, }), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", fetchApprovalInfoListImpl: vi.fn(async () => ({ ok: false, tokenSource: "WECOM_AGENT_SECRET", code: "wecom_approval_api_error", message: "WeCom approval API errcode 48002", response: { errcode: 48002, errmsg: "api forbidden" }, spNos: [], })), fetchApprovalDetailImpl: vi.fn(), readCallbackEvidenceImpl: vi.fn(async () => []), }); expect(report.status).toBe("SKIP"); expect(report.conclusion).toBe("BLOCKED_BY_EXTERNAL_CONFIG"); expect(report.mainBlockers[0]).toMatchObject({ reason: "wecom_approval_api_error", }); }); it("compares callback evidence using redacted prefixes", () => { const compare = compareCallbackEvidence([ { details: [{ target: "leave", spNoPrefix: "SPMATCH***", spStatus: "2", statusLabel: "已通过", status: "OK", }], }, ], { eventCount: 2, bySpNoPrefix: { "SPMATCH***": [ { spNoPrefix: "SPMATCH***", spStatus: "2", statusLabel: "已通过" }, { spNoPrefix: "SPMATCH***", spStatus: "2", statusLabel: "已通过" }, ], "SPONLYCB***": [ { spNoPrefix: "SPONLYCB***", spStatus: "2", statusLabel: "已通过" }, ], }, }); expect(compare.status).toBe("FINDINGS"); expect(compare.findings.map((item) => item.id)).toEqual(expect.arrayContaining([ "callback_duplicate_for_pulled_detail", "callback_not_in_compensation_window", ])); }); it("redacts secrets, full identifiers and form values from reports", async () => { const report = await executeCompensationPoc(baseOptions({ templates: { leave: SENSITIVE_TEMPLATE_ID }, }), { config: baseConfig(), env: { WECOM_AGENT_SECRET: SENSITIVE_AGENT_CREDENTIAL, }, generatedAt: "2026-05-20T00:00:00.000Z", fetchApprovalInfoListImpl: vi.fn(async () => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 1, spNoPrefixes: [maskApprovalIdentifier(SENSITIVE_SP_NO)] }, spNos: [SENSITIVE_SP_NO], nextCursor: "", })), fetchApprovalDetailImpl: vi.fn(async ({ spNo }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo), response: { errcode: 0, spStatus: "2", applyFieldCount: 3, approvalNodeCount: 1, }, })), readCallbackEvidenceImpl: vi.fn(async () => [{ callbackKind: "approval_status_change", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier(SENSITIVE_SP_NO), templateIdPrefix: maskApprovalIdentifier(SENSITIVE_TEMPLATE_ID), }]), }); 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_SP_NO); expect(serialized).not.toContain(SENSITIVE_TEMPLATE_ID); expect(serialized).not.toContain(SENSITIVE_USER_ID); expect(serialized).not.toContain(SENSITIVE_FORM_VALUE); expect(statusLabelFromSpStatus("2")).toBe("已通过"); }); });