File size: 2,136 Bytes
170b9b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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);
  });
});