diff options
Diffstat (limited to 'packages/kernel/src/host/dag.test.ts')
| -rw-r--r-- | packages/kernel/src/host/dag.test.ts | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/packages/kernel/src/host/dag.test.ts b/packages/kernel/src/host/dag.test.ts new file mode 100644 index 0000000..1a0431f --- /dev/null +++ b/packages/kernel/src/host/dag.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from "vitest"; +import type { Manifest } from "../contracts/extension.js"; +import { resolveActivationOrder } from "./dag.js"; + +function manifest(id: string, deps?: readonly string[]): Manifest { + const base: Manifest = { + id, + name: id, + version: "1.0.0", + apiVersion: "^0.1.0", + trust: "bundled", + }; + if (deps !== undefined) { + return { ...base, dependsOn: deps }; + } + return base; +} + +describe("resolveActivationOrder", () => { + it("returns empty array for no extensions", () => { + expect(resolveActivationOrder([])).toEqual([]); + }); + + it("returns a single extension with no deps", () => { + const result = resolveActivationOrder([manifest("a")]); + expect(result.map((m) => m.id)).toEqual(["a"]); + }); + + it("orders a linear chain (A → B → C)", () => { + const a = manifest("a"); + const b = manifest("b", ["a"]); + const c = manifest("c", ["b"]); + + const result = resolveActivationOrder([c, b, a]); + const ids = result.map((m) => m.id); + + expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("b")); + expect(ids.indexOf("b")).toBeLessThan(ids.indexOf("c")); + }); + + it("orders a diamond (A → B, A → C, B → D, C → D)", () => { + const a = manifest("a"); + const b = manifest("b", ["a"]); + const c = manifest("c", ["a"]); + const d = manifest("d", ["b", "c"]); + + const result = resolveActivationOrder([d, c, b, a]); + const ids = result.map((m) => m.id); + + expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("b")); + expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("c")); + expect(ids.indexOf("b")).toBeLessThan(ids.indexOf("d")); + expect(ids.indexOf("c")).toBeLessThan(ids.indexOf("d")); + }); + + it("handles independent sets (no deps between them)", () => { + const a = manifest("a"); + const b = manifest("b"); + const c = manifest("c"); + + const result = resolveActivationOrder([a, b, c]); + expect(result).toHaveLength(3); + expect(result.map((m) => m.id).sort()).toEqual(["a", "b", "c"]); + }); + + it("throws on a cycle (A → B → A)", () => { + const a = manifest("a", ["b"]); + const b = manifest("b", ["a"]); + + expect(() => resolveActivationOrder([a, b])).toThrow(/cycle/i); + }); + + it("throws on a larger cycle (A → B → C → A)", () => { + const a = manifest("a", ["c"]); + const b = manifest("b", ["a"]); + const c = manifest("c", ["b"]); + + expect(() => resolveActivationOrder([a, b, c])).toThrow(/cycle/i); + }); + + it("throws on a missing dependency", () => { + const a = manifest("a", ["nonexistent"]); + + expect(() => resolveActivationOrder([a])).toThrow(/not available/); + }); + + it("throws on duplicate extension ids", () => { + const a1 = manifest("a"); + const a2 = manifest("a"); + + expect(() => resolveActivationOrder([a1, a2])).toThrow(/duplicate/i); + }); + + it("handles mixed independent and dependent extensions", () => { + const a = manifest("a"); + const b = manifest("b", ["a"]); + const c = manifest("c"); + const d = manifest("d", ["c"]); + + const result = resolveActivationOrder([a, b, c, d]); + const ids = result.map((m) => m.id); + + expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("b")); + expect(ids.indexOf("c")).toBeLessThan(ids.indexOf("d")); + expect(result).toHaveLength(4); + }); +}); |
