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", "--target=leave", "--targets=missed_punch", "--discover-template-ids", "--discover-applicant-employee-no=SMOKE002", "--filter-creator-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.filterCreatorEmployeeNo).toBe("SMOKE002"); expect(options.persistToDb).toBe(false); expect(options.targets).toEqual(["leave", "missed_punch"]); expect(options.templates).toEqual({ leave: "tpl-leave", missed_punch: "tpl-missed", }); expect(parseArgs(["--target=outing", "--targets=overtime"]).targets).toEqual(["outing", "overtime"]); expect(parseArgs(["--template=outing:tpl"]).templates).toMatchObject({ outing: "tpl" }); expect(() => parseArgs(["--target=unknown"])).toThrow(/unsupported target/); expect(() => parseArgs(["--size=101"])).toThrow(/--size/); expect(parseArgs(["--persist-to-db"]).persistToDb).toBe(true); }); 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("builds explicit WEXT1-FORMAL outing and overtime inputs without changing defaults", () => { const defaultInputs = buildInputs(baseOptions({ templates: {} }), {}); expect(defaultInputs.map((item) => item.key)).toEqual(["leave", "missed_punch"]); const inputs = buildInputs(baseOptions({ targets: ["outing", "overtime"], templates: { outing: "cli-outing-template" }, }), { WECOM_APPROVAL_OUTING_TEMPLATE_ID: "env-outing-template", WECOM_APPROVAL_OVERTIME_TEMPLATE_ID: "env-overtime-template", }); expect(inputs.map((item) => item.key)).toEqual(["outing", "overtime"]); expect(inputs.find((item) => item.key === "outing")).toMatchObject({ templateSource: "cli", templateIdPrefix: maskApprovalIdentifier("cli-outing-template"), }); expect(inputs.find((item) => item.key === "overtime")).toMatchObject({ templateSource: "WECOM_APPROVAL_OVERTIME_TEMPLATE_ID", templateIdPrefix: maskApprovalIdentifier("env-overtime-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 discoveredOutingTemplateId = "tplout-auto-discovered-outing-sensitive"; const discoveredOvertimeTemplateId = "tplot-auto-discovered-overtime-sensitive"; const fetchApprovalInfoListImpl = vi.fn(async ({ filters }) => { if (filters[0].key === "creator") { return { ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 4, spNoPrefixes: [ maskApprovalIdentifier("sp-discovery-leave"), maskApprovalIdentifier("sp-discovery-missed"), maskApprovalIdentifier("sp-discovery-outing"), maskApprovalIdentifier("sp-discovery-overtime"), ], }, spNos: ["sp-discovery-leave", "sp-discovery-missed", "sp-discovery-outing", "sp-discovery-overtime"], 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" }, }; } if (spNo === "sp-discovery-outing") { return { ok: true, tokenSource: "WECOM_AGENT_SECRET", templateId: discoveredOutingTemplateId, spName: "外出", summary: detailSummary(spNo, { templateIdPrefix: maskApprovalIdentifier(discoveredOutingTemplateId) }), response: { errcode: 0, spStatus: "2" }, }; } if (spNo === "sp-discovery-overtime") { return { ok: true, tokenSource: "WECOM_AGENT_SECRET", templateId: discoveredOvertimeTemplateId, spName: "加班", summary: detailSummary(spNo, { templateIdPrefix: maskApprovalIdentifier(discoveredOvertimeTemplateId) }), response: { errcode: 0, spStatus: "2" }, }; } return { ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo, { templateIdPrefix: spNo.startsWith(discoveredLeaveTemplateId) ? maskApprovalIdentifier(discoveredLeaveTemplateId) : spNo.startsWith(discoveredMissedTemplateId) ? maskApprovalIdentifier(discoveredMissedTemplateId) : spNo.startsWith(discoveredOutingTemplateId) ? maskApprovalIdentifier(discoveredOutingTemplateId) : maskApprovalIdentifier(discoveredOvertimeTemplateId), }), response: { errcode: 0, spStatus: "2" }, }; }); const report = await executeCompensationPoc(baseOptions({ templates: {}, targets: ["leave", "missed_punch", "outing", "overtime"], 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`), }, { callbackKind: "approval_status_change", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier(`${discoveredOutingTemplateId}-sp`), }, { callbackKind: "approval_status_change", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier(`${discoveredOvertimeTemplateId}-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 }, ]); expect(fetchApprovalInfoListImpl.mock.calls[3][0].filters).toEqual([ { key: "template_id", value: discoveredOutingTemplateId }, ]); expect(fetchApprovalInfoListImpl.mock.calls[4][0].filters).toEqual([ { key: "template_id", value: discoveredOvertimeTemplateId }, ]); const serialized = JSON.stringify(report); expect(serialized).not.toContain(discoveredLeaveTemplateId); expect(serialized).not.toContain(discoveredMissedTemplateId); expect(serialized).not.toContain(discoveredOutingTemplateId); expect(serialized).not.toContain(discoveredOvertimeTemplateId); expect(serialized).not.toContain(SENSITIVE_USER_ID); expect(report.checks.templateDiscovery.discoveredTemplatePrefixes).toEqual({ leave: maskApprovalIdentifier(discoveredLeaveTemplateId), missed_punch: maskApprovalIdentifier(discoveredMissedTemplateId), outing: maskApprovalIdentifier(discoveredOutingTemplateId), overtime: maskApprovalIdentifier(discoveredOvertimeTemplateId), }); }); 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([]); expect(fetchApprovalDetailImpl.mock.calls.every((call) => call[0].includeRawPayload === false)).toBe(true); expect(report.checks.archivePersistence.status).toBe("SKIPPED"); }); it("filters WEXT1-FORMAL template pulls by creator employee to avoid broad formal-template reads", async () => { const creatorUserId = "formal_creator_userid_should_not_leak"; const outingTemplateId = "outing-template-sensitive"; const overtimeTemplateId = "overtime-template-sensitive"; const fetchApprovalInfoListImpl = vi.fn(async ({ filters }) => { expect(filters).toHaveLength(2); expect(filters[0].key).toBe("template_id"); expect(filters[1]).toEqual({ key: "creator", value: creatorUserId }); 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 }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo, { spStatus: "2" }), response: { errcode: 0, spStatus: "2" }, })); const report = await executeCompensationPoc(baseOptions({ targets: ["outing", "overtime"], templates: { outing: outingTemplateId, overtime: overtimeTemplateId, }, filterCreatorEmployeeNo: "SMOKE002", }), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", queryApplicantActorImpl: vi.fn(async () => ({ employeeNo: "SMOKE002", status: "active", archived: false, wecomExternalUserId: creatorUserId, })), fetchApprovalInfoListImpl, fetchApprovalDetailImpl, readCallbackEvidenceImpl: vi.fn(async () => [ { callbackKind: "approval_status_change", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier(`${outingTemplateId}-sp`), }, { callbackKind: "approval_status_change", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskApprovalIdentifier(`${overtimeTemplateId}-sp`), }, ]), }); expect(report.status).toBe("PASS_COMPENSATION_POC"); expect(report.checks.creatorFilter).toMatchObject({ status: "OK", employeeNo: "SMOKE002", userIdPrefix: maskApprovalIdentifier(creatorUserId), }); expect(fetchApprovalInfoListImpl).toHaveBeenCalledTimes(2); const serialized = JSON.stringify(report); expect(serialized).not.toContain(creatorUserId); expect(serialized).not.toContain(outingTemplateId); expect(serialized).not.toContain(overtimeTemplateId); }); it("does not pull formal templates when the requested creator filter cannot be resolved", async () => { const fetchApprovalInfoListImpl = vi.fn(); const report = await executeCompensationPoc(baseOptions({ targets: ["outing"], templates: { outing: "formal-outing-template-sensitive" }, filterCreatorEmployeeNo: "SMOKE002", }), { config: baseConfig(), env: {}, generatedAt: "2026-05-20T00:00:00.000Z", queryApplicantActorImpl: vi.fn(async () => ({ employeeNo: "SMOKE002", status: "active", archived: false, wecomExternalUserId: "", })), fetchApprovalInfoListImpl, fetchApprovalDetailImpl: vi.fn(), readCallbackEvidenceImpl: vi.fn(async () => []), }); expect(report.status).toBe("PARTIAL_COMPENSATION_NEEDS_CONFIG"); expect(fetchApprovalInfoListImpl).not.toHaveBeenCalled(); expect(report.checks.targets[0]).toMatchObject({ target: "outing", status: "NEEDS_CONFIG", reason: "creator_filter_not_ready", }); }); it("requires both --persist-to-db and WECOM_ARCHIVE_WRITE=1 before OA archive writes", async () => { const report = await executeCompensationPoc(baseOptions({ live: true, persistToDb: true, }), { config: baseConfig(), env: { DATABASE_URL: "postgres://example", }, generatedAt: "2026-05-20T00:00:00.000Z", fetchApprovalInfoListImpl: vi.fn(async () => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 0 }, spNos: [], nextCursor: "", })), fetchApprovalDetailImpl: vi.fn(), readCallbackEvidenceImpl: vi.fn(async () => []), }); expect(report.status).toBe("PARTIAL_COMPENSATION_NEEDS_CONFIG"); expect(report.checks.archivePersistence).toMatchObject({ status: "NEEDS_CONFIG", reason: "missing_archive_write_switch", }); expect(report.configGaps.map((item) => item.id)).toContain("archive_persistence"); }); it("persists pulled details to OA archive only when the double switch is present", async () => { const sensitiveLeaveSpNo = `${SENSITIVE_TEMPLATE_ID}-sp`; const sensitiveMissedSpNo = "tpl-missed-punch-sp"; const mockPrisma = buildArchiveMockPrisma(); mockPrisma.wecomApprovalCallbackEvent.findMany.mockResolvedValueOnce([ { callback_kind: "approval_status_change", approval_type: "leave", wecom_status: "2", sp_no: sensitiveLeaveSpNo, template_id: SENSITIVE_TEMPLATE_ID, received_at: new Date("2026-05-20T00:00:00.000Z"), }, { callback_kind: "approval_status_change", approval_type: "missed_punch", wecom_status: "2", sp_no: sensitiveMissedSpNo, template_id: "tpl-missed-punch", received_at: new Date("2026-05-20T00:00:01.000Z"), }, ]); 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, includeRawPayload }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo, { spStatus: "2", templateIdPrefix: spNo === sensitiveLeaveSpNo ? maskApprovalIdentifier(SENSITIVE_TEMPLATE_ID) : maskApprovalIdentifier("tpl-missed-punch"), }), rawPayload: includeRawPayload ? { errcode: 0, info: { sp_no: spNo, template_id: spNo === sensitiveLeaveSpNo ? SENSITIVE_TEMPLATE_ID : "tpl-missed-punch", sp_name: spNo === sensitiveLeaveSpNo ? "OA-POC-请假" : "OA-POC-补卡", sp_status: "2", apply_time: "1779148800", applyer: { userid: SENSITIVE_USER_ID }, apply_data: { contents: [] }, sp_record: [], }, } : undefined, response: { errcode: 0, spStatus: "2" }, })); const readCallbackEvidenceImpl = vi.fn(async () => []); const report = await executeCompensationPoc(baseOptions({ live: true, persistToDb: true, }), { config: baseConfig(), env: { DATABASE_URL: "postgres://example", WECOM_ARCHIVE_WRITE: "1", }, generatedAt: "2026-05-20T00:00:00.000Z", prisma: mockPrisma, fetchApprovalInfoListImpl, fetchApprovalDetailImpl, readCallbackEvidenceImpl, }); expect(report.status).toBe("PASS_COMPENSATION_POC"); expect(report.checks.callbackEvidence.source).toBe("db"); expect(readCallbackEvidenceImpl).not.toHaveBeenCalled(); expect(fetchApprovalDetailImpl.mock.calls.every((call) => call[0].includeRawPayload === true)).toBe(true); expect(mockPrisma.wecomApprovalCompensationRun.create).toHaveBeenCalledTimes(1); expect(mockPrisma.wecomApprovalArchive.upsert).toHaveBeenCalledTimes(2); expect(mockPrisma.wecomApprovalCompensationRun.update).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "run-archive-1" }, data: expect.objectContaining({ status: "completed", item_count: 2, detail_count: 2, }), })); expect(report.checks.archivePersistence).toMatchObject({ status: "OK", storedCount: 2, failedCount: 0, }); const serialized = JSON.stringify(report); expect(serialized).not.toContain(SENSITIVE_SP_NO); expect(serialized).not.toContain(SENSITIVE_TEMPLATE_ID); expect(serialized).not.toContain(SENSITIVE_USER_ID); }); it("records compare findings on the correct archive when masked prefixes collide", async () => { const leaveSpNo = "20260519-leave-approval-full-id"; const missedSpNo = "20260519-missed-punch-sp"; const mockPrisma = buildArchiveMockPrisma(); const fetchApprovalInfoListImpl = vi.fn(async ({ filters }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", response: { errcode: 0, spNoCount: 1 }, spNos: [filters[0].value === SENSITIVE_TEMPLATE_ID ? leaveSpNo : missedSpNo], nextCursor: "", })); const fetchApprovalDetailImpl = vi.fn(async ({ spNo, includeRawPayload }) => ({ ok: true, tokenSource: "WECOM_AGENT_SECRET", summary: detailSummary(spNo, { spStatus: "2" }), rawPayload: includeRawPayload ? { errcode: 0, info: { sp_no: spNo, template_id: spNo === leaveSpNo ? SENSITIVE_TEMPLATE_ID : "tpl-missed-punch", sp_name: spNo === leaveSpNo ? "OA-POC-请假" : "OA-POC-补卡", sp_status: "2", apply_time: "1779148800", applyer: { userid: SENSITIVE_USER_ID }, apply_data: { contents: [] }, sp_record: [], }, } : undefined, response: { errcode: 0, spStatus: "2" }, })); const report = await executeCompensationPoc(baseOptions({ live: true, persistToDb: true, }), { config: baseConfig(), env: { DATABASE_URL: "postgres://example", WECOM_ARCHIVE_WRITE: "1", }, generatedAt: "2026-05-20T00:00:00.000Z", prisma: mockPrisma, fetchApprovalInfoListImpl, fetchApprovalDetailImpl, readCallbackEvidenceImpl: vi.fn(async () => []), }); expect(report.status).toBe("PASS_WITH_FINDINGS"); expect(report.checks.callbackCompare.findings).toHaveLength(2); expect(report.checks.callbackCompare.findings.every((item) => item.spNoPrefix === "20260519***")).toBe(true); const archiveIds = mockPrisma.wecomApprovalReconcileFinding.upsert.mock.calls .map((call) => call[0].create.archive_id) .sort(); expect(archiveIds).toEqual(["archive-leave", "archive-missed"]); const serialized = JSON.stringify(report); expect(serialized).not.toContain(leaveSpNo); expect(serialized).not.toContain(missedSpNo); }); 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("已通过"); }); }); function buildArchiveMockPrisma() { const prisma = { wecomIdentityBinding: { findUnique: vi.fn(async () => null), }, wecomApprovalCompensationRun: { create: vi.fn(async () => ({ id: "run-archive-1", started_at: new Date("2026-05-20T00:00:00.000Z"), })), update: vi.fn(async (args) => ({ id: args.where.id, ...args.data })), }, wecomApprovalArchive: { findUnique: vi.fn(async () => null), upsert: vi.fn(async (args) => ({ id: args.create.sp_no.endsWith("missed-punch-sp") ? "archive-missed" : "archive-leave", ...args.create, })), }, wecomApprovalFieldSummary: { deleteMany: vi.fn(async () => ({ count: 0 })), createMany: vi.fn(async () => ({ count: 0 })), }, wecomApprovalParticipant: { deleteMany: vi.fn(async () => ({ count: 0 })), createMany: vi.fn(async () => ({ count: 0 })), }, wecomApprovalAction: { upsert: vi.fn(async () => ({ id: "action-archive" })), }, wecomApprovalCompensationItem: { upsert: vi.fn(async () => ({ id: "item-archive" })), }, wecomApprovalCallbackEvent: { findMany: vi.fn(async () => []), }, wecomApprovalReconcileFinding: { upsert: vi.fn(async () => ({ id: "finding-archive" })), }, }; prisma.$transaction = vi.fn(async (callback) => callback(prisma)); return prisma; }