const { buildSecretCheck, buildWriteGate, executeCheckinSummaryArchive, parseArgs, } = require("../../../src/scripts/check-wecom-checkin-summary-archive"); const { sanitizeCheckinApiResponse, } = require("../../../src/services/integrations/wecomCheckin"); const SENSITIVE_USER_ID = ["checkin", "summary", "full", "userid", "should", "not", "leak"].join("_"); const SENSITIVE_AGENT_SECRET = ["checkin", "agent", "secret", "should", "not", "leak"].join("-"); const SENSITIVE_CHECKIN_SECRET = ["checkin", "override", "secret", "should", "not", "leak"].join("-"); function baseConfig(overrides = {}) { return { appTimezone: "Asia/Shanghai", wecomCorpId: "corp", wecomAgentId: "1000002", wecomAgentSecret: SENSITIVE_AGENT_SECRET, wecomCheckinSecret: "", wecomRequestTimeoutMs: 1000, ...overrides, }; } function employeeRows(overrides = {}) { return [ { id: "employee-1", employee_no: "SMOKE002", status: "active", archived_at: null, wecom_identity_binding: { external_user_id: SENSITIVE_USER_ID }, }, { id: "employee-2", employee_no: "SMOKE101", status: "active", archived_at: null, wecom_identity_binding: { external_user_id: `${SENSITIVE_USER_ID}_approver` }, }, ].map((row) => ({ ...row, ...(overrides[row.employee_no] || {}) })); } function prismaStub() { const dailyRows = [ { employee_id: "employee-1", external_user_id_prefix: "chec***", checkin_date: new Date("2026-05-20T00:00:00.000Z"), record_count: 1, first_checkin_at: new Date("2026-05-20T01:00:00.000Z"), last_checkin_at: new Date("2026-05-20T01:00:00.000Z"), exception_count: 0, abnormal_count: 0, result_summary: { normal: 1 }, raw_hash_digest: "a".repeat(64), }, ]; return { employee: { findMany: vi.fn(async () => employeeRows()) }, wecomCheckinSyncRun: { create: vi.fn(async () => ({ id: "sync-run-1" })), update: vi.fn(async () => ({ id: "sync-run-1" })), }, wecomCheckinRecord: { createMany: vi.fn(async () => ({ count: 1 })), findMany: vi.fn(async () => [ { sync_run_id: "sync-run-1", employee_id: "employee-1", external_user_id: SENSITIVE_USER_ID, checkin_time: new Date("2026-05-20T01:00:00.000Z"), checkin_date: new Date("2026-05-20T00:00:00.000Z"), checkin_type: "OnDuty", exception_type: null, source_type: "app", result: "normal", raw_hash: "a".repeat(64), }, ]), }, wecomCheckinDailySummary: { upsert: vi.fn(async () => ({})), findMany: vi.fn(async () => dailyRows), }, wecomCheckinMonthlySummary: { upsert: vi.fn(async () => ({})), findMany: vi.fn(async () => [ { employee_id: "employee-1", month: "2026-05", summary_day_count: 1, checkin_day_count: 1, exception_day_count: 0, abnormal_record_count: 0, missing_day_candidate_count: 0, first_checkin_at: new Date("2026-05-20T01:00:00.000Z"), last_checkin_at: new Date("2026-05-20T01:00:00.000Z"), }, ]), }, wecomCheckinRuleSnapshot: { upsert: vi.fn(async () => ({})), findMany: vi.fn(async () => [ { employee_id: "employee-1", external_user_id_prefix: "chec***", snapshot_date: new Date("2026-05-20T00:00:00.000Z"), rule_hash: "b".repeat(64), rule_summary: { keyCount: 2 }, }, ]), }, }; } describe("check-wecom-checkin-summary-archive", () => { it("parses defaults and write gate", () => { const options = parseArgs(["--live", "--persist-to-db", "--employee-no=SMOKE002", "--format=json"]); expect(options.live).toBe(true); expect(options.persistToDb).toBe(true); expect(options.employeeNos).toEqual(["SMOKE002"]); expect(buildWriteGate(options, {})).toMatchObject({ status: "NEEDS_CONFIG", missing: ["WECOM_CHECKIN_ARCHIVE_WRITE"], }); expect(buildWriteGate(options, { WECOM_CHECKIN_ARCHIVE_WRITE: "1" })).toMatchObject({ status: "OK", enabled: true, }); }); it("documents fixed Secret source with explicit checkin override", () => { expect(buildSecretCheck(baseConfig())).toMatchObject({ source: "WECOM_AGENT_SECRET", configured: true, }); expect(buildSecretCheck(baseConfig({ wecomCheckinSecret: SENSITIVE_CHECKIN_SECRET }))).toMatchObject({ source: "WECOM_CHECKIN_SECRET", configured: true, override: true, }); }); it("reads offline archive without WeCom API and redacts report", async () => { const prisma = prismaStub(); const report = await executeCheckinSummaryArchive(parseArgs(["--offline", "--format=json"]), { prisma, config: baseConfig(), employees: employeeRows(), generatedAt: "2026-05-20T04:00:00.000Z", }); expect(report.status).toBe("PASS_CHECKIN_ARCHIVE_OFFLINE_READY"); expect(report.checks.offline.counts.daily).toBe(1); expect(JSON.stringify(report)).not.toContain(SENSITIVE_USER_ID); }); it("live persists OA summaries only after write env", async () => { const prisma = prismaStub(); const fetchCheckinDataImpl = vi.fn(async ({ requireSyncEnabled, externalUserIds }) => { expect(requireSyncEnabled).toBe(false); expect(externalUserIds).toContain(SENSITIVE_USER_ID); return { ok: true, code: "wecom_checkin_fetched", records: [ { userid: SENSITIVE_USER_ID, checkin_time: 1779238800, checkin_type: "OnDuty", result: "normal", }, ], response: { errcode: 0, checkindataCount: 1 }, }; }); const fetchCheckinRulesImpl = vi.fn(async () => ({ ok: true, code: "wecom_checkin_rule_fetched", rules: [{ userid: SENSITIVE_USER_ID, groupname: "默认", loc_info: "must-not-leak" }], response: { errcode: 0, ruleInfoCount: 1 }, })); const report = await executeCheckinSummaryArchive(parseArgs([ "--live", "--persist-to-db", "--format=json", "--start-date=2026-05-20", "--end-date=2026-05-20", ]), { prisma, config: baseConfig(), employees: employeeRows(), env: { WECOM_CHECKIN_ARCHIVE_WRITE: "1" }, fetchCheckinDataImpl, fetchCheckinRulesImpl, generatedAt: "2026-05-20T04:00:00.000Z", }); expect(report.status).toBe("PASS_CHECKIN_ARCHIVE_READY"); expect(prisma.wecomCheckinRecord.createMany).toHaveBeenCalledTimes(1); expect(prisma.wecomCheckinDailySummary.upsert).toHaveBeenCalled(); expect(prisma.wecomCheckinRuleSnapshot.upsert).toHaveBeenCalled(); const text = JSON.stringify(report); expect(text).not.toContain(SENSITIVE_USER_ID); expect(text).not.toContain(SENSITIVE_AGENT_SECRET); expect(text).not.toContain("must-not-leak"); }); it("classifies API errors as external config blockers", async () => { const shortSensitiveUserId = "SMOKE002"; const report = await executeCheckinSummaryArchive(parseArgs(["--live", "--format=json"]), { prisma: prismaStub(), config: baseConfig(), employees: employeeRows(), fetchCheckinDataImpl: vi.fn(async () => ({ ok: false, code: "wecom_checkin_api_error", message: "WeCom checkin errcode 301062", records: [], response: { errcode: 301062, invaliduser: "SM***" }, })), fetchCheckinRulesImpl: vi.fn(), }); expect(report.status).toBe("SKIP"); expect(report.conclusion).toBe("BLOCKED_BY_EXTERNAL_CONFIG"); expect(JSON.stringify(report.checks.live.api.response)).not.toContain(shortSensitiveUserId); }); it("redacts invaliduser values from checkin API responses", () => { const sanitized = sanitizeCheckinApiResponse({ errcode: 301062, errmsg: "invalid user", invaliduser: "SMOKE002|SMOKE101", }); expect(sanitized.invaliduser).toBe("SM***|SM***"); expect(JSON.stringify(sanitized)).not.toContain("SMOKE002"); expect(JSON.stringify(sanitized)).not.toContain("SMOKE101"); }); });