import { describe, expect, it, vi } from "vitest"; import { bootstrapDisplayHost, loadModuleDisplay, moduleDisplayDescriptorPath, parseDisplayRoute, parseHostModuleDisplay, } from "../src/host-protocol.js"; const apiBaseUrl = "https://ekklesia.example.test"; function displayPayload(overrides: Record = {}): { readonly module: Record; readonly viewer: Record; } { return { // The host states who it authorized, so the display never has to infer it from the absence of a // human session (ADR 0036). viewer: { kind: "authenticated", userId: "user-1" }, module: { contributions: [], api: { baseUrl: "/api/v1/modules/announcements" }, display: { url: "/api/v1/module-ui/announcements/1.2.0/display" }, id: "announcements", page: { url: "/display/announcements" }, permissions: ["announcements.slideshow.view"], realtime: { baseUrl: "/api/v1/module-realtime/announcements" }, version: "1.2.0", ...overrides, }, }; } function json(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { headers: { "content-type": "application/json" }, status, }); } describe("display host protocol — strict descriptor", () => { it("parses the exact display role into the generic mount descriptor", () => { const parsed = parseHostModuleDisplay(displayPayload()); expect(parsed).toEqual({ module: { apiBaseUrl: "/api/v1/modules/announcements", contributions: [], displayPageUrl: "/display/announcements", id: "announcements", permissions: ["announcements.slideshow.view"], realtimeBaseUrl: "/api/v1/module-realtime/announcements", uiUrl: "/api/v1/module-ui/announcements/1.2.0/display", version: "1.2.0", }, viewer: { kind: "authenticated", userId: "user-1" }, }); expect(Object.isFrozen(parsed)).toBe(true); expect(Object.isFrozen(parsed.module)).toBe(true); expect(Object.isFrozen(parsed.module.permissions)).toBe(true); }); it("rejects malformed identities, shapes, permissions, and any non-root-relative URL", () => { const invalid: unknown[] = [ {}, { ...displayPayload(), extra: true }, displayPayload({ id: "Announcements" }), displayPayload({ id: "announcements/other" }), displayPayload({ id: "a".repeat(65) }), displayPayload({ version: "v1.2.0" }), displayPayload({ version: "01.2.0" }), displayPayload({ permissions: "announcements.slideshow.view" }), displayPayload({ permissions: [""] }), displayPayload({ permissions: ["announcements.slideshow.view", "announcements.slideshow.view"], }), displayPayload({ api: { baseUrl: "https://evil.example.test/api" } }), displayPayload({ display: { url: "//evil.example.test/display.js" } }), displayPayload({ display: { url: "/display.js?token=secret" } }), displayPayload({ display: { url: "/api/%2e%2e/grants" } }), displayPayload({ display: { url: "/api/%2Fgrants" } }), displayPayload({ display: { url: "/api//display.js" } }), displayPayload({ realtime: { baseUrl: "/\\evil.example.test/live" } }), displayPayload({ display: { url: "/display.js", extra: true } }), ]; for (const payload of invalid) { expect(() => parseHostModuleDisplay(payload), JSON.stringify(payload)).toThrowError( expect.objectContaining({ code: "HOST_RESPONSE_INVALID" }), ); } }); it("does not accept a Dashboard artifact in place of a display artifact", () => { const module = (displayPayload() as { module: Record }).module; const { display: _display, ...withoutDisplay } = module; expect(() => parseHostModuleDisplay({ module: { ...withoutDisplay, ui: { url: "/api/v1/module-ui/announcements/1.2.0" }, }, }), ).toThrowError(expect.objectContaining({ code: "HOST_RESPONSE_INVALID" })); }); }); describe("display host protocol — outer route", () => { it("accepts one exact stable version-independent module route", () => { expect(parseDisplayRoute("/display/announcements")).toBe("announcements"); expect(moduleDisplayDescriptorPath("announcements")).toBe( "/api/v1/module-displays/announcements", ); }); it("rejects extra segments, trailing slashes, malformed escapes, and path escapes", () => { for (const pathname of [ "/display", "/display/", "/display/announcements/", "/display/announcements/settings", "/display/%2fapi", "/display/..", "/display/%", "/modules/announcements/display", ]) { expect(() => parseDisplayRoute(pathname), pathname).toThrowError( expect.objectContaining({ code: "DISPLAY_ROUTE_INVALID" }), ); } }); }); describe("display host protocol — authenticated bootstrap", () => { it("asks only the descriptor, which is the display's actual authorization boundary", async () => { const calls: Array<{ init: RequestInit | undefined; url: string }> = []; const fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); calls.push({ init, url }); return json(displayPayload()); }) as unknown as typeof globalThis.fetch; const bootstrap = await bootstrapDisplayHost({ apiBaseUrl, fetch }, "announcements"); expect(bootstrap.status).toBe("ready"); // Deliberately **not** principal-first, unlike the Dashboard: `/api/v1/principal` answers about // people, and a paired device is anonymous to it by design, so asking it first would conclude // that every display in the building was signed out (ADR 0036). expect(calls.map((call) => call.url)).toEqual([ `${apiBaseUrl}/api/v1/module-displays/announcements`, ]); expect(calls.every((call) => call.init?.credentials === "include")).toBe(true); }); it("reports the viewer the host authorized, for a person and for a device alike", async () => { const forViewer = async (viewer: Record) => { const fetch = vi.fn(async () => json({ ...displayPayload(), viewer }), ) as unknown as typeof globalThis.fetch; return bootstrapDisplayHost({ apiBaseUrl, fetch }, "announcements"); }; expect((await forViewer({ kind: "authenticated", userId: "user-1" })).principal).toEqual({ kind: "authenticated", userId: "user-1", }); expect( (await forViewer({ deviceId: "3f1c8a4e-1d2b-4c3a-9e5f-6a7b8c9d0e1f", kind: "device" })) .principal, ).toEqual({ deviceId: "3f1c8a4e-1d2b-4c3a-9e5f-6a7b8c9d0e1f", kind: "device" }); }); it("refuses a descriptor that claims an anonymous or malformed viewer", async () => { // A `restricted` display permission admits no anonymous principal, so a server saying it // authorized one is a protocol violation rather than a state to render. for (const viewer of [ { kind: "anonymous" }, { kind: "device" }, { kind: "authenticated" }, { deviceId: "", kind: "device" }, { kind: "device", userId: "user-1" }, ]) { expect(() => parseHostModuleDisplay({ ...displayPayload(), viewer })).toThrowError( expect.objectContaining({ code: "HOST_RESPONSE_INVALID" }), ); } }); it("distinguishes denied, unavailable, unauthenticated, and infrastructure failures safely", async () => { for (const [status, code] of [ [401, "HOST_UNAUTHENTICATED"], [403, "DISPLAY_ACCESS_DENIED"], [404, "DISPLAY_NOT_AVAILABLE"], [503, "HOST_REQUEST_FAILED"], ] as const) { const fetch = vi.fn(async () => json({ detail: "/srv/modules/announcements secret-token" }, status), ) as unknown as typeof globalThis.fetch; const error = await loadModuleDisplay({ apiBaseUrl, fetch }, "announcements").catch( (caught: unknown) => caught, ); expect(error).toMatchObject({ code }); expect((error as Error).message).not.toContain("/srv/"); expect((error as Error).message).not.toContain("secret-token"); } }); it("rejects a stale or misrouted descriptor whose identity differs from the requested display", async () => { const fetch = vi.fn(async () => json(displayPayload({ id: "calendar" })), ) as unknown as typeof globalThis.fetch; await expect(loadModuleDisplay({ apiBaseUrl, fetch }, "announcements")).rejects.toMatchObject({ code: "HOST_RESPONSE_INVALID", }); }); });