const { buildDailySummaries, buildMonthlySummaries, buildRuleSnapshots, maskExternalUserId, sanitizeDailySummary, summarizeRuleOption, } = require("../../../src/services/integrations/wecomCheckinArchive"); const SENSITIVE_USER_ID = ["checkin", "archive", "full", "userid", "should", "not", "leak"].join("_"); describe("wecomCheckinArchive", () => { it("builds daily summaries from normalized records without raw payload fields", () => { const summaries = buildDailySummaries([ { 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: "", source_type: "app", result: "normal", raw_hash: "a".repeat(64), }, { employee_id: "employee-1", external_user_id: SENSITIVE_USER_ID, checkin_time: new Date("2026-05-20T10:00:00.000Z"), checkin_date: new Date("2026-05-20T00:00:00.000Z"), checkin_type: "OffDuty", exception_type: "late", source_type: "app", result: "exception", raw_hash: "b".repeat(64), }, ]); expect(summaries).toHaveLength(1); expect(summaries[0]).toMatchObject({ employee_id: "employee-1", external_user_id_prefix: "chec***", record_count: 2, exception_count: 1, abnormal_count: 1, }); expect(summaries[0].checkin_type_summary).toEqual({ OnDuty: 1, OffDuty: 1 }); expect(JSON.stringify(summaries)).not.toContain(SENSITIVE_USER_ID); }); it("builds monthly summaries from daily summaries", () => { const daily = buildDailySummaries([ { employee_id: "employee-1", external_user_id: SENSITIVE_USER_ID, checkin_time: new Date("2026-05-19T01:00:00.000Z"), checkin_date: new Date("2026-05-19T00:00:00.000Z"), checkin_type: "OnDuty", result: "normal", raw_hash: "a".repeat(64), }, { 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", result: "normal", raw_hash: "b".repeat(64), }, ]); const monthly = buildMonthlySummaries(daily, { expectedWorkdaysByMonth: { "2026-05": 3 } }); expect(monthly).toHaveLength(1); expect(monthly[0]).toMatchObject({ employee_id: "employee-1", month: "2026-05", summary_day_count: 2, checkin_day_count: 2, missing_day_candidate_count: 1, }); }); it("summarizes rule snapshots without location, face, device or raw payload", () => { const employeeByExternalUserId = new Map([[SENSITIVE_USER_ID, { id: "employee-1" }]]); const snapshots = buildRuleSnapshots({ snapshotDate: "2026-05-20", employeeByExternalUserId, rules: [ { userid: SENSITIVE_USER_ID, groupname: { text: "默认规则" }, checkin_time: [{ work_sec: 32400 }], loc_infos: [{ lat: 30, lng: 120 }], deviceid: "sensitive-device", face: "sensitive-face", }, ], }); expect(snapshots).toHaveLength(1); expect(snapshots[0].employee_id).toBe("employee-1"); expect(snapshots[0].external_user_id_prefix).toBe(maskExternalUserId(SENSITIVE_USER_ID)); const text = JSON.stringify(snapshots); expect(text).not.toContain(SENSITIVE_USER_ID); expect(text).not.toContain("sensitive-device"); expect(text).not.toContain("sensitive-face"); expect(text).not.toContain("loc_infos"); }); it("sanitizes daily rows for reports", () => { const sanitized = sanitizeDailySummary({ 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), }); expect(sanitized).toMatchObject({ employeeId: "employee-1", userIdPrefix: "chec***", checkinDate: "2026-05-20", rawHashDigestPrefix: "aaaaaaaaaaaa...", }); }); it("masks short external user ids without exposing the full value", () => { expect(maskExternalUserId("ABCD")).toBe("A***"); expect(maskExternalUserId("SMOKE002")).toBe("SM***"); expect(maskExternalUserId("SMOKE002X")).toBe("SMOK***"); }); it("filters sensitive rule keys from summaries", () => { const summary = summarizeRuleOption({ userid: SENSITIVE_USER_ID, loc_info: "must-not-appear", device: "must-not-appear", face: "must-not-appear", groupname: "默认规则", }); const text = JSON.stringify(summary); expect(text).not.toContain("loc_info"); expect(text).not.toContain("device"); expect(text).not.toContain("face"); }); });