import { test, expect } from "bun:test"; import { escapeLiteral, escapeIri, assertNuri } from "../src/sparql"; // --- escapeLiteral -------------------------------------------------------- test("escapeLiteral escapes backslash, quote and whitespace controls", () => { expect(escapeLiteral('a"b')).toBe('a\\"b'); expect(escapeLiteral("a\\b")).toBe("a\\\\b"); expect(escapeLiteral("a\nb")).toBe("a\\nb"); expect(escapeLiteral("a\rb")).toBe("a\\rb"); expect(escapeLiteral("a\tb")).toBe("a\\tb"); }); test("escapeLiteral backslash-then-quote order does not double-escape", () => { // `\` first so a raw `"` never becomes `\"` before the quote pass mangles it. expect(escapeLiteral('\\"')).toBe('\\\\\\"'); }); test("escapeLiteral output can no longer close a SPARQL literal", () => { const injected = '" ; "pwn'; const escaped = escapeLiteral(injected); // No RAW double-quote survives — every `"` is preceded by a backslash. expect(/(^|[^\\])"/.test(escaped)).toBe(false); }); // --- escapeIri ------------------------------------------------------------ test("escapeIri passes ordinary printable identifier chars through unchanged", () => { expect(escapeIri("alice")).toBe("alice"); expect(escapeIri("a.b-c_d:e/f")).toBe("a.b-c_d:e/f"); }); test("escapeIri percent-encodes every IRI-breaking character", () => { expect(escapeIri("a>b")).toBe("a%3Eb"); expect(escapeIri("a { const attack = 'x> "pwn'; const encoded = escapeIri(attack); // The encoded id cannot contain a raw `>`, `<`, `"` or space, so it // cannot escape the surrounding IRI. expect(encoded).not.toMatch(/[<>" ]/); }); test("escapeIri handles unicode without corrupting it (round-trips via decode)", () => { const u = "élan"; // "é" is a printable letter → left as-is; a space would be encoded. expect(escapeIri(u)).toBe("élan"); expect(escapeIri("é ")).toBe("é%20"); }); // --- assertNuri ----------------------------------------------------------- test("assertNuri returns valid NURIs unchanged", () => { expect(assertNuri("did:ng:o:doc1")).toBe("did:ng:o:doc1"); expect(assertNuri("urn:ng-eventually:shim")).toBe("urn:ng-eventually:shim"); expect(assertNuri("did:ng:PRIV")).toBe("did:ng:PRIV"); }); test("assertNuri throws on empty / non-string", () => { expect(() => assertNuri("")).toThrow(/invalid NURI/); // @ts-expect-error deliberately wrong type expect(() => assertNuri(null)).toThrow(/invalid NURI/); }); test("assertNuri throws on IRI-breaking characters", () => { expect(() => assertNuri("did:ng:o> assertNuri('did:ng:"x')).toThrow(/IRI-forbidden/); expect(() => assertNuri("did:ng: x")).toThrow(/IRI-forbidden/); expect(() => assertNuri("did:ng:\nx")).toThrow(/IRI-forbidden/); expect(() => assertNuri("did:ng:\tx")).toThrow(/IRI-forbidden/); });