summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/host/dag.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/kernel/src/host/dag.test.ts')
-rw-r--r--packages/kernel/src/host/dag.test.ts107
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);
+ });
+});