import { describe, expect, test } from "bun:test"; import { assertSafeObject, createTrustedHtml, isPrivateAddress, isTrustedHtml, secureJsonStringify, setSecureCookie, unwrapTrustedHtml, validateUrl, } from "../src/index.ts"; describe("@wrnexus/security", () => { test("escapes HTML-significant JSON and redacts secrets", () => { const json = secureJsonStringify({ html: "", token: "secret", count: 1 }); expect(json).toContain("\\u003c/script\\u003e"); expect(json).toContain("[REDACTED]"); expect(json).not.toContain("secret"); }); test("rejects prototype-pollution keys and unsafe URL protocols", () => { const unsafe = JSON.parse('{"__proto__":{"admin":true}}'); expect(() => assertSafeObject(unsafe)).toThrow(); expect(() => validateUrl("javascript:alert(1)")).toThrow(); }); test("identifies private IP ranges", () => { expect(isPrivateAddress("127.0.0.1")).toBe(true); expect(isPrivateAddress("10.1.2.3")).toBe(true); expect(isPrivateAddress("8.8.8.8")).toBe(false); }); test("enforces __Host cookie rules", () => { const writes: unknown[] = []; const ctx = { url: new URL("https://example.com"), cookies: { set: (...args: unknown[]) => writes.push(args) }, } as any; setSecureCookie(ctx, "__Host-session", "value"); expect(writes).toHaveLength(1); expect(writes[0]).toEqual([ "__Host-session", "value", expect.objectContaining({ secure: true, httpOnly: true, path: "/", domain: undefined }), ]); }); test("accepts repeated references while still rejecting cycles", () => { const shared = { value: 1 }; expect(() => assertSafeObject({ first: shared, second: shared })).not.toThrow(); const cyclic: Record = {}; cyclic.self = cyclic; expect(() => assertSafeObject(cyclic)).toThrow(); }); test("trusted HTML requires an explicit sanitizer policy", () => { const value = createTrustedHtml('

Hello

', { name: "test-policy", sanitize: (input) => input.replace(/\s+onclick="[^"]*"/g, ""), }); expect(isTrustedHtml(value)).toBe(true); expect(unwrapTrustedHtml(value)).toBe("

Hello

"); expect(() => unwrapTrustedHtml({ value: "unsafe" } as any)).toThrow(); }); });