import { describe, expect, it } from "@suaveplan/testing/runner"; import { gzipSync, strToU8 } from "fflate"; import { DMARC_CODES, DmarcError } from "../errors/dmarc-error.js"; import { parseDmarcAggregate } from "./aggregate.js"; const MINIMAL_XML = ` 1.0 Example Corp dmarc@example.com 12345 16094592001609545600 example.com r r

none

100
192.0.2.1 5 none pass pass example.com example.compass example.compassmfrom
`; const MULTI_RECORD_XML = ` 1.0 Multi Corp dmarc@multi.com 99 16094592001609545600 multi.com s s

reject

quarantine 50
10.0.0.1 3 reject fail fail multi.com bounce@multi.com multi.comfaildkim1 multi.comfail 10.0.0.2 1 none pass pass multi.com multi.compass multi.compass
`; const ERROR_XML = ` 1.0 Example Corp dmarc@example.com 12345 16094592001609545600 Something went wrong example.com r r

none

100
`; describe("parseDmarcAggregate", () => { it("parses minimal valid XML", { timeout: 5000 }, () => { const report = parseDmarcAggregate(MINIMAL_XML); // fast-xml-parser coerces "1.0" to the number 1, which str() converts to "1" expect(report.version).toBe("1"); expect(report.reportMetadata.orgName).toBe("Example Corp"); expect(report.reportMetadata.email).toBe("dmarc@example.com"); expect(report.reportMetadata.reportId).toBe("12345"); expect(report.reportMetadata.dateRange.begin).toBe(1609459200); expect(report.reportMetadata.dateRange.end).toBe(1609545600); }); it("maps policy_published fields correctly", { timeout: 5000 }, () => { const report = parseDmarcAggregate(MINIMAL_XML); expect(report.policyPublished.domain).toBe("example.com"); expect(report.policyPublished.adkim).toBe("r"); expect(report.policyPublished.aspf).toBe("r"); expect(report.policyPublished.p).toBe("none"); expect(report.policyPublished.pct).toBe(100); }); it("maps record fields correctly", { timeout: 5000 }, () => { const report = parseDmarcAggregate(MINIMAL_XML); expect(report.record).toHaveLength(1); const rec = report.record[0]; expect(rec.row.sourceIp).toBe("192.0.2.1"); expect(rec.row.count).toBe(5); expect(rec.row.policyEvaluated.disposition).toBe("none"); expect(rec.row.policyEvaluated.dkim).toBe("pass"); expect(rec.row.policyEvaluated.spf).toBe("pass"); expect(rec.identifiers.headerFrom).toBe("example.com"); }); it("maps auth_results fields", { timeout: 5000 }, () => { const report = parseDmarcAggregate(MINIMAL_XML); const auth = report.record[0].authResults; expect(auth.dkim).toBeDefined(); expect(auth.spf).toBeDefined(); }); it("parses gzip-compressed XML (Uint8Array)", { timeout: 5000 }, () => { const compressed = gzipSync(strToU8(MINIMAL_XML)); const report = parseDmarcAggregate(compressed); expect(report.reportMetadata.orgName).toBe("Example Corp"); }); it("parses plain XML as Uint8Array", { timeout: 5000 }, () => { const bytes = new TextEncoder().encode(MINIMAL_XML); const report = parseDmarcAggregate(bytes); expect(report.reportMetadata.orgName).toBe("Example Corp"); }); it("handles multiple records", { timeout: 5000 }, () => { const report = parseDmarcAggregate(MULTI_RECORD_XML); expect(report.record).toHaveLength(2); expect(report.policyPublished.sp).toBe("quarantine"); }); it("handles optional envelope_from", { timeout: 5000 }, () => { const report = parseDmarcAggregate(MULTI_RECORD_XML); expect(report.record[0].identifiers.envelopeFrom).toBe( "bounce@multi.com" ); }); it("handles dkim selector", { timeout: 5000 }, () => { const report = parseDmarcAggregate(MULTI_RECORD_XML); const dkim = report.record[0].authResults.dkim; expect(dkim).toBeDefined(); }); it("parses report with error elements", { timeout: 5000 }, () => { const report = parseDmarcAggregate(ERROR_XML); expect(report.reportMetadata.errors).toEqual(["Something went wrong"]); expect(report.record).toHaveLength(0); }); it("throws DmarcError on missing ", { timeout: 5000 }, () => { expect(() => parseDmarcAggregate("")).toThrowError( DmarcError ); try { parseDmarcAggregate(""); } catch (err) { expect(err).toBeInstanceOf(DmarcError); expect((err as DmarcError).code).toBe(DMARC_CODES.INVALID_REPORT); } }); it("throws DmarcError on missing ", { timeout: 5000, }, () => { const xml = "1.0"; expect(() => parseDmarcAggregate(xml)).toThrowError(DmarcError); try { parseDmarcAggregate(xml); } catch (err) { expect((err as DmarcError).code).toBe(DMARC_CODES.INVALID_REPORT); } }); it("throws DmarcError on missing ", { timeout: 5000, }, () => { const xml = ` 1.0 Xx@x.com1 01 `; expect(() => parseDmarcAggregate(xml)).toThrowError(DmarcError); try { parseDmarcAggregate(xml); } catch (err) { expect((err as DmarcError).code).toBe(DMARC_CODES.INVALID_REPORT); } }); it("throws DmarcError on malformed XML (unclosed attribute)", { timeout: 5000, }, () => { expect(() => parseDmarcAggregate('", { timeout: 5000, }, () => { const xml = ` 1.0 Xx@x.com1 01 x.comrr

none

100
x.com
`; expect(() => parseDmarcAggregate(xml)).toThrowError(DmarcError); try { parseDmarcAggregate(xml); } catch (err) { expect((err as DmarcError).code).toBe(DMARC_CODES.INVALID_REPORT); } }); it("throws DmarcError when record is missing ", { timeout: 5000, }, () => { const xml = ` 1.0 Xx@x.com1 01 x.comrr

none

100
1.2.3.41 x.com
`; expect(() => parseDmarcAggregate(xml)).toThrowError(DmarcError); try { parseDmarcAggregate(xml); } catch (err) { expect((err as DmarcError).code).toBe(DMARC_CODES.INVALID_REPORT); } }); it("accepts decompress: true option explicitly", { timeout: 5000 }, () => { const compressed = gzipSync(strToU8(MINIMAL_XML)); const report = parseDmarcAggregate(compressed, { decompress: true }); expect(report.reportMetadata.orgName).toBe("Example Corp"); }); it("handles multiple DKIM entries in auth_results", { timeout: 5000, }, () => { const xml = ` 1.0 Xx@x.com1 01 x.comrr

none

100
1.2.3.41 nonepasspass x.com x.compasssel1 y.compass
`; const report = parseDmarcAggregate(xml); expect(Array.isArray(report.record[0].authResults.dkim)).toBe(true); }); it("handles invalid disposition value (schema validation failure)", { timeout: 5000, }, () => { const xml = ` 1.0 Xx@x.com1 01 x.comrr

invalid_disposition

100
`; expect(() => parseDmarcAggregate(xml)).toThrowError(DmarcError); try { parseDmarcAggregate(xml); } catch (err) { expect((err as DmarcError).code).toBe(DMARC_CODES.INVALID_REPORT); } }); it("throws DmarcError when record element is an empty string (non-object)", { timeout: 5000, }, () => { // produces "" string in fast-xml-parser, not an object const xml = ` 1.0 Xx@x.com1 01 x.comrr

none

100
`; expect(() => parseDmarcAggregate(xml)).toThrowError(DmarcError); try { parseDmarcAggregate(xml); } catch (err) { expect((err as DmarcError).code).toBe(DMARC_CODES.INVALID_REPORT); } }); it("num() handles non-numeric string (NaN -> 0) and empty string (0) date range", { timeout: 5000, }, () => { // NaN path: non-parseable string -> NaN -> 0 const xmlNaN = ` 1.0 Xx@x.com1 notanumberalsonotanumber x.comrr

none

100
`; const reportNaN = parseDmarcAggregate(xmlNaN); expect(reportNaN.reportMetadata.dateRange.begin).toBe(0); // Finite path: empty string -> Number("") = 0 (finite) -> returns 0 const xmlEmpty = ` 1.0 Xx@x.com1 x.comrr

none

100
`; const reportEmpty = parseDmarcAggregate(xmlEmpty); expect(reportEmpty.reportMetadata.dateRange.begin).toBe(0); }); it("adkim/aspf defaults to 'r' when absent from policy_published", { timeout: 5000, }, () => { // Missing adkim/aspf elements triggers the || "r" fallback const xml = ` 1.0 Xx@x.com1 01 x.com

none

100
`; const report = parseDmarcAggregate(xml); expect(report.policyPublished.adkim).toBe("r"); expect(report.policyPublished.aspf).toBe("r"); }); it("handles multiple SPF entries with scope in auth_results", { timeout: 5000, }, () => { const xml = ` 1.0 Xx@x.com1 01 x.comrr

none

100
1.2.3.41 nonepasspass x.com x.compassmfrom y.comfail
`; const report = parseDmarcAggregate(xml); const spf = report.record[0].authResults.spf; expect(Array.isArray(spf)).toBe(true); }); it("handles envelope_to in identifiers", { timeout: 5000 }, () => { const xml = ` 1.0 Xx@x.com1 01 x.comrr

none

100
1.2.3.41 nonepasspass x.com dest@x.com
`; const report = parseDmarcAggregate(xml); expect(report.record[0].identifiers.envelopeTo).toBe("dest@x.com"); }); });