import { describe, it, expect, beforeAll } from "vitest"; import { loadP2pdsLexicons, getLexicon, validateP2pdsRecord, } from "./lexicons.js"; describe("lexicons", () => { beforeAll(() => { loadP2pdsLexicons(); }); describe("loadP2pdsLexicons", () => { it("loads peer and offer lexicons", () => { const peer = getLexicon("org.p2pds.peer"); const offer = getLexicon("org.p2pds.replication.offer"); expect(peer).toBeDefined(); expect(peer!.id).toBe("org.p2pds.peer"); expect(peer!.lexicon).toBe(1); expect(peer!.defs.main.type).toBe("record"); expect(offer).toBeDefined(); expect(offer!.id).toBe("org.p2pds.replication.offer"); expect(offer!.defs.main.type).toBe("record"); }); it("peer lexicon has correct schema", () => { const peer = getLexicon("org.p2pds.peer")!; const props = peer.defs.main.record.properties; const required = peer.defs.main.record.required; expect(required).toContain("peerId"); expect(required).toContain("multiaddrs"); expect(required).toContain("createdAt"); expect(props.peerId.type).toBe("string"); expect(props.multiaddrs.type).toBe("array"); expect(props.createdAt.type).toBe("string"); expect(props.createdAt.format).toBe("datetime"); }); it("offer lexicon has correct schema", () => { const offer = getLexicon("org.p2pds.replication.offer")!; const props = offer.defs.main.record.properties; const required = offer.defs.main.record.required; expect(required).toContain("subject"); expect(required).toContain("minCopies"); expect(required).toContain("intervalSec"); expect(required).toContain("priority"); expect(required).toContain("createdAt"); expect(props.subject.type).toBe("string"); expect(props.subject.format).toBe("did"); expect(props.minCopies.type).toBe("integer"); expect(props.priority.maximum).toBe(100); }); }); describe("validateP2pdsRecord — org.p2pds.peer", () => { const validPeer = { $type: "org.p2pds.peer", peerId: "12D3KooWNvSZnPi3RrhrTwEY4LuuBeB6K6facKUCJcyWG1aoDd2p", multiaddrs: ["/ip4/127.0.0.1/tcp/4001"], createdAt: "2026-01-15T10:30:00.000Z", }; it("accepts a valid peer record", () => { expect(() => validateP2pdsRecord("org.p2pds.peer", validPeer), ).not.toThrow(); }); it("rejects missing peerId", () => { const { peerId: _, ...bad } = validPeer; expect(() => validateP2pdsRecord("org.p2pds.peer", bad), ).toThrow("Missing required field: peerId"); }); it("rejects missing multiaddrs", () => { const { multiaddrs: _, ...bad } = validPeer; expect(() => validateP2pdsRecord("org.p2pds.peer", bad), ).toThrow("Missing required field: multiaddrs"); }); it("rejects missing createdAt", () => { const { createdAt: _, ...bad } = validPeer; expect(() => validateP2pdsRecord("org.p2pds.peer", bad), ).toThrow("Missing required field: createdAt"); }); it("rejects non-string peerId", () => { expect(() => validateP2pdsRecord("org.p2pds.peer", { ...validPeer, peerId: 123, }), ).toThrow("must be a string"); }); it("rejects non-array multiaddrs", () => { expect(() => validateP2pdsRecord("org.p2pds.peer", { ...validPeer, multiaddrs: "not-an-array", }), ).toThrow("must be an array"); }); it("rejects invalid datetime", () => { expect(() => validateP2pdsRecord("org.p2pds.peer", { ...validPeer, createdAt: "not-a-date", }), ).toThrow("valid datetime"); }); it("accepts empty multiaddrs array", () => { expect(() => validateP2pdsRecord("org.p2pds.peer", { ...validPeer, multiaddrs: [], }), ).not.toThrow(); }); it("rejects non-string items in multiaddrs", () => { expect(() => validateP2pdsRecord("org.p2pds.peer", { ...validPeer, multiaddrs: [123], }), ).toThrow("must be a string"); }); }); describe("validateP2pdsRecord — org.p2pds.replication.offer", () => { const validOffer = { $type: "org.p2pds.replication.offer", subject: "did:plc:abc123", minCopies: 2, intervalSec: 600, priority: 50, createdAt: "2026-01-15T10:30:00.000Z", }; it("accepts a valid offer record", () => { expect(() => validateP2pdsRecord("org.p2pds.replication.offer", validOffer), ).not.toThrow(); }); it("rejects missing subject", () => { const { subject: _, ...bad } = validOffer; expect(() => validateP2pdsRecord("org.p2pds.replication.offer", bad), ).toThrow("Missing required field: subject"); }); it("rejects non-DID subject", () => { expect(() => validateP2pdsRecord("org.p2pds.replication.offer", { ...validOffer, subject: "not-a-did", }), ).toThrow("must be a valid DID"); }); it("rejects non-integer minCopies", () => { expect(() => validateP2pdsRecord("org.p2pds.replication.offer", { ...validOffer, minCopies: 1.5, }), ).toThrow("must be an integer"); }); it("rejects minCopies below minimum", () => { expect(() => validateP2pdsRecord("org.p2pds.replication.offer", { ...validOffer, minCopies: 0, }), ).toThrow(">= 1"); }); it("rejects priority above maximum", () => { expect(() => validateP2pdsRecord("org.p2pds.replication.offer", { ...validOffer, priority: 101, }), ).toThrow("<= 100"); }); it("rejects priority below minimum", () => { expect(() => validateP2pdsRecord("org.p2pds.replication.offer", { ...validOffer, priority: -1, }), ).toThrow(">= 0"); }); it("accepts boundary values", () => { expect(() => validateP2pdsRecord("org.p2pds.replication.offer", { ...validOffer, minCopies: 1, priority: 0, intervalSec: 1, }), ).not.toThrow(); expect(() => validateP2pdsRecord("org.p2pds.replication.offer", { ...validOffer, priority: 100, }), ).not.toThrow(); }); }); describe("validateP2pdsRecord — unknown NSID", () => { it("throws for unknown NSID", () => { expect(() => validateP2pdsRecord("org.p2pds.unknown", {}), ).toThrow("No lexicon loaded for NSID"); }); }); describe("validateP2pdsRecord — non-object", () => { it("rejects null", () => { expect(() => validateP2pdsRecord("org.p2pds.peer", null), ).toThrow("must be an object"); }); it("rejects string", () => { expect(() => validateP2pdsRecord("org.p2pds.peer", "string"), ).toThrow("must be an object"); }); }); });