import { describe, it, expect } from "vitest"; import { missingFields } from "../manifest-health"; import type { Manifest } from "../manifest"; /** * A current-shape manifest carries every kind as a top-level array (empty when * unused) plus `graph.events`. Mirrors the real `@nwire/scan` `buildManifest` * output — apps/resolvers/routes/hooks/plugins/sinks/bindings live under * `topology`/`model`, not at the top level, so they're NOT health-checked. */ function fullManifest(): Record { return { version: 3, events: [], actions: [], actors: [], projections: [], queries: [], workflows: [], commands: [], crons: [], externalCalls: [], inboundWebhooks: [], outboxes: [], inboxes: [], resources: [], errors: [], schemas: [], graph: { events: [] }, }; } describe("missingFields", () => { it("reports nothing for a current-shape manifest", () => { expect(missingFields(fullManifest() as unknown as Manifest)).toEqual([]); }); it("does not flag the deep-manifest fields kept under topology/model", () => { // Regression: these are NOT top-level arrays in the real manifest; flagging // them false-fired the rebuild banner on every healthy project. const out = missingFields(fullManifest() as unknown as Manifest); for (const f of [ "apps", "resolvers", "routes", "hooks", "plugins", "sinks", "bindings", "generatedAt", ]) { expect(out).not.toContain(f); } }); it("reports every expected field when the manifest is empty", () => { const out = missingFields({} as unknown as Manifest); expect(out).toContain("actions"); expect(out).toContain("events"); expect(out).toContain("workflows"); expect(out).toContain("commands"); expect(out).toContain("resources"); expect(out).toContain("graph"); }); it("names the genuinely-missing kinds on a legacy manifest (the banner contract)", () => { const legacy = fullManifest(); delete legacy.workflows; delete legacy.commands; const out = missingFields(legacy as unknown as Manifest); expect(out).toContain("workflows"); expect(out).toContain("commands"); expect(out).not.toContain("actions"); }); it("reports the exact list of missing array fields", () => { const partial = { version: 3, events: [], actions: [], actors: [], projections: [], queries: [], // workflows + commands + resources + errors + schemas + graph missing crons: [], externalCalls: [], inboundWebhooks: [], outboxes: [], inboxes: [], }; expect([...missingFields(partial as unknown as Manifest)].sort()).toEqual([ "commands", "errors", "graph", "resources", "schemas", "workflows", ]); }); it("treats a non-array masquerading as the field as missing", () => { const m = fullManifest(); m.workflows = "not an array"; expect(missingFields(m as unknown as Manifest)).toContain("workflows"); }); it("reports graph.events missing while graph exists", () => { const m = fullManifest(); m.graph = {}; expect(missingFields(m as unknown as Manifest)).toContain("graph.events"); }); it("returns [] for unusable (non-object) input — App renders the error banner instead", () => { expect(missingFields(null)).toEqual([]); expect(missingFields(undefined)).toEqual([]); expect(missingFields([] as unknown as Manifest)).toEqual([]); expect(missingFields("oops" as unknown as Manifest)).toEqual([]); expect(missingFields(42 as unknown as Manifest)).toEqual([]); }); });