import assert from "node:assert/strict"; import test from "node:test"; import { createBuiltinFinalHandler, DecisionJournal, TrustRunner, } from "../src/index.js"; import { unknownLocalAuthority, validPublicCertificate, } from "../fixtures/tls.js"; test("built-in handler rejects an unresolved certificate failure", async () => { const result = await new TrustRunner().evaluate(unknownLocalAuthority); assert.equal(result.verdict.trusted, false); assert.equal(result.journal.entries.length, 0); }); test("built-in handler preserves an ordinary successful validation", async () => { const result = await new TrustRunner().evaluate(validPublicCertificate); assert.equal(result.verdict.trusted, true); }); test("a decision plugin can trust a narrowly scoped connection", async () => { const plugin = pluginWith({ decide({ facts }) { return { trusted: true, scope: { hostname: facts.hostname, port: facts.port }, }; }, }); const result = await new TrustRunner({ plugins: [plugin] }).evaluate( unknownLocalAuthority, ); assert.equal(result.verdict.trusted, true); assert.equal(result.journal.entries.at(-1).kind, "resolution"); assert.equal(result.journal.entries.at(-1).pluginId, plugin.manifest.id); }); test("evidence collection is independent but appended in plugin order", async () => { const slowFirst = pluginWith({ id: "test.first", async collectEvidence() { await new Promise((resolve) => setTimeout(resolve, 5)); return { entries: [{ kind: "evidence", code: "first" }] }; }, }); const fastSecond = pluginWith({ id: "test.second", collectEvidence() { return { entries: [{ kind: "warning", code: "second" }] }; }, }); const result = await new TrustRunner({ plugins: [slowFirst, fastSecond] }).evaluate( validPublicCertificate, ); assert.deepEqual( result.journal.entries.map((entry) => entry.code), ["first", "second"], ); }); test("plugin timeout is recorded and falls through safely", async () => { const plugin = pluginWith({ async decide() { await new Promise((resolve) => setTimeout(resolve, 30)); return undefined; }, }); const result = await new TrustRunner({ plugins: [plugin], timeoutMs: 5 }).evaluate( unknownLocalAuthority, ); assert.equal(result.verdict.trusted, false); assert.equal(result.journal.entries.at(-1).code, "plugin-timeout"); }); test("a verdict cannot escape the active hostname and port", async () => { const plugin = pluginWith({ decide() { return { trusted: true, scope: { hostname: "different.test", port: 443 }, }; }, }); const result = await new TrustRunner({ plugins: [plugin] }).evaluate( unknownLocalAuthority, ); assert.equal(result.verdict.trusted, false); assert.match(result.journal.entries.at(-1).message, /scope does not match/); }); test("observer and advisor plugins cannot issue terminal verdicts", async () => { for (const role of ["observer", "advisor"]) { const plugin = pluginWith({ role, decide({ facts }) { return { trusted: true, scope: { hostname: facts.hostname, port: facts.port }, }; }, }); const result = await new TrustRunner({ plugins: [plugin] }).evaluate( unknownLocalAuthority, ); assert.equal(result.verdict.trusted, false); assert.match(result.journal.entries.at(-1).message, /cannot issue/); } }); test("veto authority can reject but cannot trust", async () => { const plugin = pluginWith({ role: "veto-authority", decide({ facts }) { return { trusted: true, scope: { hostname: facts.hostname, port: facts.port }, }; }, }); const result = await new TrustRunner({ plugins: [plugin] }).evaluate( unknownLocalAuthority, ); assert.equal(result.verdict.trusted, false); assert.match(result.journal.entries.at(-1).message, /cannot issue a trusted/); }); test("journal snapshots and entries are immutable", () => { const journal = new DecisionJournal(); journal.append( { id: "test.plugin", name: "Test plugin" }, [{ kind: "evidence", data: { nested: true } }], ); const snapshot = journal.snapshot(); assert.throws(() => snapshot.entries.push({}), TypeError); assert.throws(() => { snapshot.entries[0].data.nested = false; }, TypeError); }); test("only Browsec's immutable handler can occupy the final position", () => { assert.throws( () => new TrustRunner({ finalHandler: { manifest: { id: "test.impostor", name: "Impostor", role: "decision-authority", }, finalize() {}, }, }), /must be Browsec's built-in handler/, ); assert.doesNotThrow(() => new TrustRunner({ finalHandler: createBuiltinFinalHandler() }), ); }); function pluginWith({ id = "test.plugin", role = "decision-authority", collectEvidence, decide, }) { return { manifest: { id, name: id, role }, collectEvidence, decide, }; }