const fs = require("node:fs/promises"); const path = require("node:path"); const { WecomCallbackError, buildEncryptedApprovalEventSample, buildUrlVerificationSample, createWecomSignature, decryptWecomMessage, extractEncryptedFromXml, maskWecomIdentifier, normalizeApprovalCallbackEvent, readCallbackEvidence, recordCallbackEvidence, verifyWecomSignature, } = require("../../../src/services/integrations/wecomCallbackCrypto"); const TOKEN = "callback-token"; const AES_KEY = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"; const CORP_ID = "ww-callback-corp"; const SENSITIVE_SP_NO = "SPFULLIDENTIFIERSHOULDNOTLEAK0001"; const SENSITIVE_TEMPLATE_ID = "TPLFULLIDENTIFIERSHOULDNOTLEAK0001"; describe("wecomCallbackCrypto", () => { it("creates and verifies WeCom callback signatures", () => { const signature = createWecomSignature({ token: TOKEN, timestamp: "1770000000", nonce: "nonce", encrypted: "encrypted-payload", }); expect(signature).toMatch(/^[0-9a-f]{40}$/); expect(verifyWecomSignature({ token: TOKEN, timestamp: "1770000000", nonce: "nonce", encrypted: "encrypted-payload", signature, })).toBe(true); expect(verifyWecomSignature({ token: TOKEN, timestamp: "1770000000", nonce: "nonce", encrypted: "other", signature, })).toBe(false); }); it("decrypts URL verification echostr samples", () => { const sample = buildUrlVerificationSample({ token: TOKEN, encodingAesKey: AES_KEY, receiveId: CORP_ID, timestamp: "1770000000", nonce: "nonce", plainText: "wcbk1-url-verify-ok", }); const decrypted = decryptWecomMessage({ encrypted: sample.encrypted, encodingAesKey: AES_KEY, receiveId: CORP_ID, }); expect(sample.signature).toMatch(/^[0-9a-f]{40}$/); expect(decrypted.plainXml).toBe("wcbk1-url-verify-ok"); expect(decrypted.receiveId).toBe(CORP_ID); }); it("normalizes approval callback events without leaking raw identifiers", () => { const sample = buildEncryptedApprovalEventSample({ token: TOKEN, encodingAesKey: AES_KEY, receiveId: CORP_ID, timestamp: "1770000000", nonce: "nonce", spNo: SENSITIVE_SP_NO, templateId: SENSITIVE_TEMPLATE_ID, }); const encrypted = extractEncryptedFromXml(sample.encryptedXml); const decrypted = decryptWecomMessage({ encrypted, encodingAesKey: AES_KEY, receiveId: CORP_ID, }); const summary = normalizeApprovalCallbackEvent(decrypted.plainXml); expect(summary).toMatchObject({ eventType: "open_approval_change", msgType: "event", callbackKind: "approval_status_change", approvalType: "leave", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskWecomIdentifier(SENSITIVE_SP_NO), templateIdPrefix: maskWecomIdentifier(SENSITIVE_TEMPLATE_ID), }); expect(JSON.stringify(summary)).not.toContain(SENSITIVE_SP_NO); expect(JSON.stringify(summary)).not.toContain(SENSITIVE_TEMPLATE_ID); expect(summary.idempotencyKey).toHaveLength(32); }); it("rejects mismatched receiveId", () => { const sample = buildUrlVerificationSample({ token: TOKEN, encodingAesKey: AES_KEY, receiveId: CORP_ID, timestamp: "1770000000", nonce: "nonce", plainText: "ok", }); expect(() => decryptWecomMessage({ encrypted: sample.encrypted, encodingAesKey: AES_KEY, receiveId: "other-corp", })).toThrow(WecomCallbackError); }); it("records callback evidence idempotently and only stores redacted fields", async () => { const evidenceFile = path.join("/tmp", `wcbk1-evidence-${Date.now()}-${Math.random()}.jsonl`); const summary = { eventType: "open_approval_change", msgType: "event", callbackKind: "approval_status_change", approvalType: "leave", spStatus: "2", statusLabel: "已通过", spNoPrefix: maskWecomIdentifier(SENSITIVE_SP_NO), templateIdPrefix: maskWecomIdentifier(SENSITIVE_TEMPLATE_ID), createTime: "1770000000", applyTime: "1770000000", agentIdPrefix: "1000***", idempotencyKey: "idem-key", }; const first = await recordCallbackEvidence(summary, { evidenceFile }); const second = await recordCallbackEvidence(summary, { evidenceFile }); const events = await readCallbackEvidence({ evidenceFile }); const raw = await fs.readFile(evidenceFile, "utf8"); expect(first.recorded).toBe(true); expect(second.duplicate).toBe(true); expect(events).toHaveLength(1); expect(raw).not.toContain(SENSITIVE_SP_NO); expect(raw).not.toContain(SENSITIVE_TEMPLATE_ID); }); });