import { describe, it, expect } from "vitest"; import { manifestView } from "../manifest"; import type { Manifest } from "../manifest"; function fixture(): Manifest { return { version: 4, model: { nodes: [ { id: "action:orders.place", kind: "action", name: "orders.place" }, { id: "event:OrderPlaced", kind: "event", name: "OrderPlaced" }, { id: "app:orders", kind: "app", name: "orders" }, ], edges: [ { from: "action:orders.place", to: "event:OrderPlaced", type: "emits" }, { from: "app:orders", to: "route:POST /orders", type: "mounts" }, ], }, } as unknown as Manifest; } describe("manifestView", () => { it("selects nodes by kind", () => { const v = manifestView(fixture()); expect(v.byKind("action").map((n) => n.name)).toEqual(["orders.place"]); expect(v.byKind("app")).toHaveLength(1); expect(v.byKind("nope")).toEqual([]); }); it("looks up a node by id", () => { const v = manifestView(fixture()); expect(v.node("event:OrderPlaced")?.name).toBe("OrderPlaced"); expect(v.node("missing")).toBeUndefined(); }); it("filters edges by direction and type", () => { const v = manifestView(fixture()); expect(v.edgesOf("action:orders.place", "out")).toHaveLength(1); expect(v.edgesOf("event:OrderPlaced", "in")).toHaveLength(1); expect(v.edgesOf("event:OrderPlaced", "out")).toHaveLength(0); expect(v.edgesOf("action:orders.place", "both")).toHaveLength(1); expect(v.edgesByType("emits")).toHaveLength(1); expect(v.edgesByType("mounts")).toHaveLength(1); }); it("flags a version mismatch", () => { expect(manifestView(fixture()).versionMismatch).toBe(false); expect( manifestView({ version: 99, model: { nodes: [], edges: [] } } as unknown as Manifest) .versionMismatch, ).toBe(true); }); it("tolerates a manifest with no model (empty graph, no throw)", () => { const v = manifestView({} as Manifest); expect(v.model.nodes).toEqual([]); expect(v.byKind("action")).toEqual([]); expect(v.edgesOf("x")).toEqual([]); }); it("synthesizes a model from flat arrays when `model` is absent", () => { const flat = { actions: [ { name: "orders.place", app: "orders", emits: ["orders.placed"], description: "Place" }, ], events: [{ name: "orders.placed", app: "orders" }], queries: [{ name: "orders.by-id", projection: "orders" }], plugins: [{ name: "auth" }], } as unknown as Manifest; const v = manifestView(flat); expect(v.byKind("action").map((n) => n.name)).toEqual(["orders.place"]); expect(v.byKind("plugin")).toHaveLength(1); // action→event emits + query→projection reads edges are derived. expect(v.edgesOf("action:orders.place", "out")).toContainEqual({ from: "action:orders.place", to: "event:orders.placed", type: "emits", }); expect(v.edgesOf("query:orders.by-id", "out")).toContainEqual({ from: "query:orders.by-id", to: "projection:orders", type: "reads", }); }); it("prefers the deep model when present (does not synthesize)", () => { const v = manifestView(fixture()); // fixture's model has app + action nodes only — no synthesized 'plugin'. expect(v.byKind("query")).toEqual([]); }); });