mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-10-28 09:01:00 +00:00
The evaluator was a skeleton with invented thresholds (5/3/10/5) and two unimplemented branches. Rebuild it against the migration plan's spec and the behavior characterized from the pinned reference commit. Core: - four independent decision dimensions (ranking, visibility, interaction, transaction) with severity ladders and max-merge composition - stable reason identifiers replacing free-text strings - structured evidence, redacted unless a profile opts in - administrative-state reduction with capability filtering at merge time, so revoking a role drops that actor's entries immediately - protected actors stripped from every denial set - snapshot fingerprints for cache invalidation - generic policy engine: applications supply profiles and category thresholds; the core carries none of its own Characterization: - 34 generated fixtures covering every behavior the plan's Phase 0 requires - conformance suite that actually evaluates them, replacing a placeholder that asserted a list's length - reference thresholds (3/3/20/5, 60-day mute window) live in bitvid-compat as an application profile, not as engine defaults Fixes found while building: - reduceAdminState accepted malformed target identifiers, because getTargetKey formats anything it is given - community-user-deny fired for any contributor rather than only for community-curated sources 273 tests pass; typecheck and the reference-import guard are clean.
95 lines
2.9 KiB
JavaScript
95 lines
2.9 KiB
JavaScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
canonicalStringify,
|
|
fingerprint,
|
|
hashString,
|
|
snapshotFingerprint,
|
|
} from "../src/fingerprint.js";
|
|
|
|
describe("canonicalStringify", () => {
|
|
it("sorts object keys at every depth", () => {
|
|
expect(canonicalStringify({ b: 1, a: { d: 2, c: 3 } })).toBe(
|
|
canonicalStringify({ a: { c: 3, d: 2 }, b: 1 }),
|
|
);
|
|
});
|
|
|
|
it("preserves array order", () => {
|
|
expect(canonicalStringify([1, 2])).not.toBe(canonicalStringify([2, 1]));
|
|
});
|
|
|
|
it("serializes Sets order-independently", () => {
|
|
expect(canonicalStringify(new Set(["b", "a"]))).toBe(canonicalStringify(new Set(["a", "b"])));
|
|
});
|
|
|
|
it("serializes Maps order-independently", () => {
|
|
const a = new Map([["x", 1], ["y", 2]]);
|
|
const b = new Map([["y", 2], ["x", 1]]);
|
|
expect(canonicalStringify(a)).toBe(canonicalStringify(b));
|
|
});
|
|
|
|
it("skips undefined values", () => {
|
|
expect(canonicalStringify({ a: 1, b: undefined })).toBe(canonicalStringify({ a: 1 }));
|
|
});
|
|
|
|
it("handles primitives and null", () => {
|
|
expect(canonicalStringify(null)).toBe("null");
|
|
expect(canonicalStringify(5)).toBe("5");
|
|
expect(canonicalStringify("x")).toBe('"x"');
|
|
});
|
|
});
|
|
|
|
describe("hashString", () => {
|
|
it("returns a 16-character hex digest", () => {
|
|
expect(hashString("anything")).toMatch(/^[0-9a-f]{16}$/);
|
|
});
|
|
|
|
it("is stable across calls", () => {
|
|
expect(hashString("stable")).toBe(hashString("stable"));
|
|
});
|
|
|
|
it("separates similar inputs", () => {
|
|
expect(hashString("a")).not.toBe(hashString("b"));
|
|
expect(hashString("ab")).not.toBe(hashString("ba"));
|
|
});
|
|
|
|
it("handles the empty string", () => {
|
|
expect(hashString("")).toMatch(/^[0-9a-f]{16}$/);
|
|
});
|
|
});
|
|
|
|
describe("fingerprint", () => {
|
|
it("matches for structurally equal values", () => {
|
|
expect(fingerprint({ a: 1, b: 2 })).toBe(fingerprint({ b: 2, a: 1 }));
|
|
});
|
|
|
|
it("differs when a value changes", () => {
|
|
expect(fingerprint({ a: 1 })).not.toBe(fingerprint({ a: 2 }));
|
|
});
|
|
});
|
|
|
|
describe("snapshotFingerprint", () => {
|
|
it("ignores fields outside the snapshot", () => {
|
|
const base = { authority: { root: "x" }, admin: { userDeny: new Set(["a"]) } };
|
|
expect(snapshotFingerprint(base)).toBe(
|
|
snapshotFingerprint(/** @type {any} */ ({ ...base, viewer: "someone" })),
|
|
);
|
|
});
|
|
|
|
it("changes when administrative state changes", () => {
|
|
const before = snapshotFingerprint({ admin: { userDeny: new Set() } });
|
|
const after = snapshotFingerprint({ admin: { userDeny: new Set(["user:a"]) } });
|
|
expect(before).not.toBe(after);
|
|
});
|
|
|
|
it("changes when the policy changes", () => {
|
|
expect(snapshotFingerprint({ policy: { version: "1" } })).not.toBe(
|
|
snapshotFingerprint({ policy: { version: "2" } }),
|
|
);
|
|
});
|
|
|
|
it("tolerates a fully empty snapshot", () => {
|
|
expect(snapshotFingerprint({})).toMatch(/^[0-9a-f]{16}$/);
|
|
});
|
|
});
|