summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/host/dag.test.ts
blob: 352965cc07a5601422cac4647e5b2560a8f61acd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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);
  });
});