import { describe, it, expect } from "vitest"; import type { ConsentPreview } from "@courtmitra/contracts"; import { computeConsentHash, consentHashMatches } from "../consent-hash.js"; const base: ConsentPreview = { actionType: "send_email", destination: "grievance@acme.example", public: false, body: "Dear Acme,\nPlease refund my order.\nRegards", attachments: [ { id: "ev_2", sha256: "bbb", redacted: true }, { id: "ev_1", sha256: "aaa", redacted: true }, ], piiDisclosed: ["order_id", "name"], risks: ["company may not reply"], createdAt: "2026-05-27T10:00:00+05:30", }; describe("consent hash", () => { it("is stable regardless of attachment / array ordering and createdAt", () => { const reordered: ConsentPreview = { ...base, attachments: [ { id: "ev_1", sha256: "aaa", redacted: true }, { id: "ev_2", sha256: "bbb", redacted: true }, ], piiDisclosed: ["name", "order_id"], createdAt: "2099-01-01T00:00:00+05:30", // volatile — must not affect hash }; expect(computeConsentHash(base)).toBe(computeConsentHash(reordered)); }); it("normalizes CRLF line endings in the body", () => { const crlf: ConsentPreview = { ...base, body: base.body.replace(/\n/g, "\r\n") }; expect(computeConsentHash(crlf)).toBe(computeConsentHash(base)); }); it("changes when the body text changes (bait-and-switch guard)", () => { const harsher: ConsentPreview = { ...base, body: base.body + "\nYou are a SCAM." }; expect(computeConsentHash(harsher)).not.toBe(computeConsentHash(base)); expect(consentHashMatches(computeConsentHash(base), harsher)).toBe(false); }); it("changes when an attachment hash changes", () => { const tampered: ConsentPreview = { ...base, attachments: [ { id: "ev_1", sha256: "DIFFERENT", redacted: true }, { id: "ev_2", sha256: "bbb", redacted: true }, ], }; expect(consentHashMatches(computeConsentHash(base), tampered)).toBe(false); }); it("matches itself", () => { expect(consentHashMatches(computeConsentHash(base), base)).toBe(true); }); });