/*
Unit tests for overlay covering RFC-1038: resolveOverlays; RFC-1038: inspectOverlays. It also guards the documented public contract and regression-sensitive edge cases.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the overlay suite's contract for test-file scaffold coverage.
*/
import { describe, it, expect } from "vitest";
import { resolveOverlays, inspectOverlays, OverlayConflictError } from "../overlay.ts";
import type {
DesiredState,
DesiredStateOverlay,
ComponentDeclaration,
} from "@warpgogol/werkstatt-shared/kernel";
import type { Sha256Digest } from "@warpgogol/werkstatt-shared/fingerprint";
const VALID_SHA = ("sha256:" + "a".repeat(64)) as Sha256Digest;
function makeDeclaration(id: string): ComponentDeclaration {
return {
schema: "werkstatt/component-declaration@1",
componentId: id as `${string}/${string}`,
version: "1.0.0",
artifactHash: VALID_SHA,
scope: "per-workshop",
provides: [],
requires: [],
requestedGrants: [],
effects: [],
isolation: { tier: 0, adapterId: null },
resources: [],
priority: 0,
active: true,
};
}
function makeBaseState(components: ComponentDeclaration[] = []): DesiredState {
return {
components: new Map(components.map((c) => [c.componentId, c])),
requiredCapabilities: [],
availableArtifacts: new Map(),
admittedGrants: [],
profileId: "test",
};
}
function makeOverlay(
id: string,
addOrReplace: ComponentDeclaration[] = [],
remove: string[] = [],
priority = 100,
): DesiredStateOverlay {
return {
id,
scope: "per-workshop",
context: { scope: "per-workshop" },
addOrReplace: new Map(addOrReplace.map((c) => [c.componentId, c])),
remove,
priority,
};
}
describe("RFC-1038: resolveOverlays", () => {
it("AC-7: overlay.apply applies overlay to base desired state", () => {
const base = makeBaseState();
const decl = makeDeclaration("werkstatt/overlay-comp");
const overlay = makeOverlay("test-overlay", [decl]);
const resolved = resolveOverlays(base, [overlay]);
expect(resolved.components.has("werkstatt/overlay-comp")).toBe(true);
expect(resolved.components.get("werkstatt/overlay-comp")).toBe(decl);
});
it("overlay with remove deletes component from base", () => {
const decl = makeDeclaration("werkstatt/remove-me");
const base = makeBaseState([decl]);
const overlay = makeOverlay("test-overlay", [], ["werkstatt/remove-me"]);
const resolved = resolveOverlays(base, [overlay]);
expect(resolved.components.has("werkstatt/remove-me")).toBe(false);
});
it("AC-8: two overlays same priority same componentId → COMPOSITION-01", () => {
const base = makeBaseState();
const decl1 = makeDeclaration("werkstatt/conflict");
const decl2 = makeDeclaration("werkstatt/conflict");
const overlay1 = makeOverlay("overlay-a", [decl1], [], 100);
const overlay2 = makeOverlay("overlay-b", [decl2], [], 100);
expect(() => resolveOverlays(base, [overlay1, overlay2])).toThrow(OverlayConflictError);
expect(() => resolveOverlays(base, [overlay1, overlay2])).toThrow(/COMPOSITION-01/);
});
it("different priorities for same componentId do not conflict", () => {
const base = makeBaseState();
const decl1 = makeDeclaration("werkstatt/priority-test");
const decl2 = makeDeclaration("werkstatt/priority-test");
const overlay1 = makeOverlay("overlay-low", [decl1], [], 50);
const overlay2 = makeOverlay("overlay-high", [decl2], [], 100);
const resolved = resolveOverlays(base, [overlay1, overlay2]);
expect(resolved.components.has("werkstatt/priority-test")).toBe(true);
});
it("empty overlays returns base unchanged", () => {
const base = makeBaseState([makeDeclaration("werkstatt/base")]);
const resolved = resolveOverlays(base, []);
expect(resolved).toBe(base);
});
});
describe("RFC-1038: inspectOverlays", () => {
it("returns summary of each overlay", () => {
const overlays = [
makeOverlay("overlay-a", [makeDeclaration("werkstatt/a")], [], 100),
makeOverlay("overlay-b", [], ["werkstatt/b"], 50),
];
const summary = inspectOverlays(overlays);
expect(summary).toHaveLength(2);
expect(summary[0]!.id).toBe("overlay-a");
expect(summary[0]!.addCount).toBe(1);
expect(summary[0]!.removeCount).toBe(0);
expect(summary[1]!.id).toBe("overlay-b");
expect(summary[1]!.addCount).toBe(0);
expect(summary[1]!.removeCount).toBe(1);
});
});