import { afterAll, afterEach, describe, expect, test, vi } from "vitest"; import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab"; import { createDashboardTileCache, dashboardTileCacheKey, handleDashboardEventsRoute, handleDashboardTilesRoute, handleDashboardTileRoute, loadDashboardResponse, loadDashboardTilesResponse, loadDashboardTileResponse, type DashboardTileResolutionLogEvent, } from "./dashboard"; describe("dashboard API route", () => { const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => undefined); afterEach(() => { consoleInfo.mockClear(); }); afterAll(() => { consoleInfo.mockRestore(); }); test("returns the ready dashboard shell without hydrating live datasources", async () => { const fetch = vi.spyOn(globalThis, "fetch").mockRejectedValue( new Error("live datasource fetch should not run for the shell response"), ); const response = await loadDashboardResponse({ refreshSeedDocument: true, seedIfEmpty: true, }); expect(response.state).toBe("ready"); if (response.state !== "ready") throw new Error("expected ready dashboard"); expect(response.document.metadata.title).toBe( dimensionLabDashboardFixture.metadata.title, ); expect(fetch).not.toHaveBeenCalled(); fetch.mockRestore(); }); test("reports when client-side live hydration is disabled", async () => { const previous = process.env.DISABLE_LIVE_DATASOURCES; process.env.DISABLE_LIVE_DATASOURCES = "1"; try { const response = await loadDashboardResponse({ refreshSeedDocument: true, seedIfEmpty: true, }); expect(response.state).toBe("ready"); if (response.state !== "ready") throw new Error("expected ready dashboard"); expect(response.liveDatasourceHydration).toEqual({ enabled: false }); } finally { if (previous === undefined) { delete process.env.DISABLE_LIVE_DATASOURCES; } else { process.env.DISABLE_LIVE_DATASOURCES = previous; } } }); test("hydrates a telemetry tile independently from the dashboard shell", async () => { const fetch = vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url.startsWith("https://prometheus.example/api/v1/query_range")) { return jsonResponse({ status: "success", data: { result: [ { metric: { host: "linux-infra" }, values: [ [1771430000, "10"], [1771430060, "20"], [1771430120, "42"], ], }, ], }, }); } if (url.startsWith("https://prometheus.example/api/v1/query")) { return jsonResponse({ status: "success", data: { result: [ { metric: { host: "linux-infra" }, value: [1771430400, "42"], }, ], }, }); } throw new Error(`Unhandled test request: ${url}`); }); const response = await loadDashboardTileResponse( { kind: "telemetry", id: "infra-ram" }, { fetch, prometheusBaseUrl: "https://prometheus.example", refreshSeedDocument: true, seedIfEmpty: true, }, ); expect(response.state).toBe("ready"); if (response.state !== "ready") throw new Error("expected ready tile"); expect(response.tile).toEqual({ kind: "telemetry", id: "infra-ram" }); expect(response.item).toMatchObject({ id: "infra-ram", value: { kind: "percent", value: 42 }, severity: "ok", detail: "linux-infra", sparkline: [10, 20, 42], }); expect(fetch).toHaveBeenCalledWith( expect.stringContaining("/api/v1/query?"), expect.objectContaining({ cache: "no-store" }), ); expect(fetch).toHaveBeenCalledWith( expect.stringContaining("/api/v1/query_range?"), expect.objectContaining({ cache: "no-store" }), ); }); test("hydrates a service tile with its service group identity", async () => { const fetch = vi.fn(async () => jsonResponse({ status: "UP", ping: 42, }), ); const response = await loadDashboardTileResponse( { kind: "service", groupId: "essentials", id: "vaultwarden" }, { fetch, refreshSeedDocument: true, seedIfEmpty: true, }, ); expect(response.state).toBe("ready"); if (response.state !== "ready") throw new Error("expected ready tile"); expect(response.tile).toEqual({ kind: "service", groupId: "essentials", id: "vaultwarden", }); expect(response.item).toMatchObject({ id: "vaultwarden", severity: "ok", detail: "42 ms", }); }); test("caches ready tile responses until the tile ttl expires", async () => { const cache = createDashboardTileCache(); let now = 1_000; const fetch = vi.fn(async () => { now += 7; return jsonResponse({ status: "UP", ping: 42, }); }); const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; const first = await loadDashboardTileResponse(tile, { fetch, now: () => now, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); const second = await loadDashboardTileResponse(tile, { fetch, now: () => now + 29_999, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); now += 30_001; const third = await loadDashboardTileResponse(tile, { fetch, now: () => now, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); expect(first).toEqual(second); expect(third.state).toBe("ready"); expect(fetch).toHaveBeenCalledTimes(2); }); test("logs tile duration and cache hit or miss metadata", async () => { let now = 1_000; const logs: DashboardTileResolutionLogEvent[] = []; const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; const service = dimensionLabDashboardFixture.serviceGroups .flatMap((group) => group.services) .find((item) => item.id === tile.id); if (!service) throw new Error("missing service fixture"); let cacheCalls = 0; const tileCache = { async resolve() { cacheCalls += 1; if (cacheCalls === 1) now += 7; const cacheState = cacheCalls === 1 ? "miss" as const : "hit" as const; return { cache: cacheState, coalesced: false, response: { state: "ready" as const, tile, item: service, }, }; }, }; await loadDashboardTileResponse(tile, { logTileResolution: (event) => logs.push(event), now: () => now, refreshSeedDocument: true, seedIfEmpty: true, tileCache, }); now += 10; await loadDashboardTileResponse(tile, { logTileResolution: (event) => logs.push(event), now: () => now, refreshSeedDocument: true, seedIfEmpty: true, tileCache, }); expect(logs).toEqual([ expect.objectContaining({ cache: "miss", coalesced: false, durationMs: 7, status: "ready", tileKey: dashboardTileCacheKey(tile), }), expect.objectContaining({ cache: "hit", coalesced: false, durationMs: 0, status: "ready", tileKey: dashboardTileCacheKey(tile), }), ]); }); test("keeps telemetry tiles cached for fifteen seconds", async () => { const cache = createDashboardTileCache(); let now = 1_000; const fetch = telemetryFetch(); const tile = { kind: "telemetry", id: "infra-ram" } as const; await loadDashboardTileResponse(tile, { fetch, now: () => now, prometheusBaseUrl: "https://prometheus.example", refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); await loadDashboardTileResponse(tile, { fetch, now: () => now + 14_999, prometheusBaseUrl: "https://prometheus.example", refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); now += 15_001; await loadDashboardTileResponse(tile, { fetch, now: () => now, prometheusBaseUrl: "https://prometheus.example", refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); expect(fetch).toHaveBeenCalledTimes(4); }); test("keeps weather module tiles cached for ten minutes", async () => { const cache = createDashboardTileCache(); let now = 1_000; const fetch = vi.fn(async () => jsonResponse({ current: { apparent_temperature: 19, temperature_2m: 20, weather_code: 0, wind_speed_10m: 11, }, }), ); const tile = { kind: "module", id: "weather-amsterdam" } as const; await loadDashboardTileResponse(tile, { fetch, now: () => now, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); await loadDashboardTileResponse(tile, { fetch, now: () => now + 599_999, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); now += 600_001; await loadDashboardTileResponse(tile, { fetch, now: () => now, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); expect(fetch).toHaveBeenCalledTimes(2); }); test("coalesces concurrent tile requests for the same cache key", async () => { const cache = createDashboardTileCache(); const logs: DashboardTileResolutionLogEvent[] = []; let resolveFetch: ((response: Response) => void) | undefined; const fetch = vi.fn(() => new Promise((resolve) => { resolveFetch = resolve; }) ); const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; const first = loadDashboardTileResponse(tile, { fetch, logTileResolution: (event) => logs.push(event), now: () => 1_000, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); const second = loadDashboardTileResponse(tile, { fetch, logTileResolution: (event) => logs.push(event), now: () => 1_000, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }); await Promise.resolve(); expect(fetch).toHaveBeenCalledTimes(1); resolveFetch?.(jsonResponse({ status: "UP", ping: 42 })); expect(await first).toEqual(await second); expect(logs).toEqual([ expect.objectContaining({ cache: "miss", coalesced: false, status: "ready", tileKey: dashboardTileCacheKey(tile), }), expect.objectContaining({ cache: "miss", coalesced: true, status: "ready", tileKey: dashboardTileCacheKey(tile), }), ]); }); test("logs coalesced metadata when shared tile requests fail", async () => { const cache = createDashboardTileCache(); let rejectLoad: ((error: Error) => void) | undefined; const load = vi.fn(() => new Promise((_resolve, reject) => { rejectLoad = reject; }) ); const first = cache.resolve("tile-a", 30_000, 1_000, load); const second = cache.resolve("tile-a", 30_000, 1_000, load); await Promise.resolve(); expect(load).toHaveBeenCalledTimes(1); rejectLoad?.(new TypeError("upstream failed")); await expect(first).rejects.toThrow("upstream failed"); await expect(second).rejects.toMatchObject({ cache: "miss", coalesced: true, cause: expect.any(TypeError), }); }); test("logs coalesced metadata for failed tile cache resolution", async () => { const logs: DashboardTileResolutionLogEvent[] = []; const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; await expect( loadDashboardTileResponse(tile, { logTileResolution: (event) => logs.push(event), refreshSeedDocument: true, seedIfEmpty: true, tileCache: { async resolve() { throw { cache: "miss", cause: new TypeError("coalesced cache failed"), coalesced: true, }; }, }, }), ).rejects.toThrow("coalesced cache failed"); expect(logs).toEqual([ expect.objectContaining({ cache: "miss", coalesced: true, errorCategory: "TypeError", status: "error", tileKey: dashboardTileCacheKey(tile), }), ]); }); test("logs error categories for failed tile cache resolution", async () => { const logs: DashboardTileResolutionLogEvent[] = []; const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const; await expect( loadDashboardTileResponse(tile, { logTileResolution: (event) => logs.push(event), refreshSeedDocument: true, seedIfEmpty: true, tileCache: { async resolve() { throw new TypeError("cache failed"); }, }, }), ).rejects.toThrow("cache failed"); expect(logs).toEqual([ expect.objectContaining({ cache: "miss", coalesced: false, errorCategory: "TypeError", status: "error", tileKey: dashboardTileCacheKey(tile), }), ]); }); test("uses structured tile cache keys when identifiers contain delimiters", () => { expect( dashboardTileCacheKey({ kind: "service", groupId: "a:b", id: "c" }), ).not.toBe( dashboardTileCacheKey({ kind: "service", groupId: "a", id: "b:c" }), ); expect( dashboardTileCacheKey({ kind: "status", stripId: "a:b", id: "c" }), ).not.toBe( dashboardTileCacheKey({ kind: "status", stripId: "a", id: "b:c" }), ); }); test("uses thirty-second status aggregate and five-minute static status ttl buckets", async () => { const cache = createDashboardTileCache(); const fetch = vi.fn(async () => jsonResponse({ status: "UP", ping: 42 })); const serviceCheckCount = dimensionLabDashboardFixture.serviceGroups .flatMap((group) => group.services).length; await loadDashboardTileResponse( { kind: "status", stripId: "footer-status", id: "system-status" }, { fetch, now: () => 1_000, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }, ); await loadDashboardTileResponse( { kind: "status", stripId: "footer-status", id: "system-status" }, { fetch, now: () => 31_001, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }, ); await loadDashboardTileResponse( { kind: "status", stripId: "footer-status", id: "auto-refresh" }, { fetch, now: () => 1_000, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }, ); await loadDashboardTileResponse( { kind: "status", stripId: "footer-status", id: "auto-refresh" }, { fetch, now: () => 300_999, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }, ); await loadDashboardTileResponse( { kind: "status", stripId: "footer-status", id: "auto-refresh" }, { fetch, now: () => 301_001, refreshSeedDocument: true, seedIfEmpty: true, tileCache: cache, }, ); expect(fetch).toHaveBeenCalledTimes(serviceCheckCount * 2); }); test("serves tile route responses with short private cache headers", async () => { const response = await handleDashboardTileRoute( "/api/dashboard/tile/service/essentials/vaultwarden", { fetch: vi.fn(async () => jsonResponse({ status: "UP", ping: 42 })), refreshSeedDocument: true, seedIfEmpty: true, tileCache: createDashboardTileCache(), }, ); expect(response.headers.get("cache-control")).toBe( "private, max-age=5, stale-while-revalidate=30", ); }); test("serves batch tile route responses", async () => { const response = await handleDashboardTilesRoute( new Request("https://example.test/api/dashboard/tiles", { method: "POST", body: JSON.stringify({ tiles: [ { kind: "status", stripId: "footer-status", id: "auto-refresh", }, ], }), }), { refreshSeedDocument: true, seedIfEmpty: true, tileCache: createDashboardTileCache(), }, ); expect(response.status).toBe(200); expect(response.headers.get("cache-control")).toBe( "private, max-age=5, stale-while-revalidate=30", ); await expect(response.json()).resolves.toMatchObject({ state: "ready", tiles: [ { state: "ready", tile: { kind: "status", stripId: "footer-status", id: "auto-refresh", }, }, ], }); }); test("streams ready dashboard tile events", async () => { const controller = new AbortController(); const response = await handleDashboardEventsRoute({ refreshSeedDocument: true, seedIfEmpty: true, signal: controller.signal, tileCache: { async resolve(key) { controller.abort(); const tile = JSON.parse(key); return { cache: "miss", coalesced: false, response: { state: "ready", tile, item: { id: tile.id, label: "Status", severity: "ok", value: "ok", }, }, }; }, }, }); expect(response.headers.get("content-type")).toBe( "text/event-stream; charset=utf-8", ); expect(response.headers.get("cache-control")).toBe("no-cache"); const body = await response.text(); expect(body).toContain("event: dashboard-tile"); expect(body).toContain('"state":"ready"'); expect(body).toContain('"tile"'); }); test("rejects invalid batch tile requests", async () => { const response = await handleDashboardTilesRoute( new Request("https://example.test/api/dashboard/tiles", { method: "POST", body: JSON.stringify({ tiles: [{ kind: "service", id: "missing-group" }], }), }), ); expect(response.status).toBe(400); }); test("resolves batch tile responses with server concurrency capped at six", async () => { let active = 0; let maxActive = 0; const started: string[] = []; const releases = new Map void>(); const service = dimensionLabDashboardFixture.serviceGroups .flatMap((group) => group.services)[0]; if (!service) throw new Error("missing service fixture"); const tiles = Array.from({ length: 7 }, (_, index) => ({ kind: "service" as const, groupId: "essentials", id: `service-${index}`, })); const batch = loadDashboardTilesResponse(tiles, { refreshSeedDocument: true, seedIfEmpty: true, tileCache: { async resolve(key) { active += 1; maxActive = Math.max(maxActive, active); started.push(key); await new Promise((resolve) => releases.set(key, resolve)); active -= 1; return { cache: "miss", coalesced: false, response: { state: "ready", tile: JSON.parse(key), item: service, }, }; }, }, }); await waitFor(() => started.length === 6); expect(maxActive).toBe(6); releases.get(started[0])?.(); await waitFor(() => started.length === 7); for (const release of releases.values()) release(); await expect(batch).resolves.toMatchObject({ state: "ready", tiles: expect.arrayContaining([ expect.objectContaining({ state: "ready", tile: tiles[0], }), ]), }); expect(maxActive).toBe(6); }); test("does not hydrate tile routes when live datasources are disabled", async () => { const previous = process.env.DISABLE_LIVE_DATASOURCES; process.env.DISABLE_LIVE_DATASOURCES = "1"; const fetch = vi.spyOn(globalThis, "fetch").mockRejectedValue( new Error("live datasource fetch should not run when disabled"), ); try { const response = await loadDashboardTileResponse( { kind: "telemetry", id: "infra-ram" }, { refreshSeedDocument: true, seedIfEmpty: true, }, ); expect(response.state).toBe("disabled"); expect(fetch).not.toHaveBeenCalled(); } finally { fetch.mockRestore(); if (previous === undefined) { delete process.env.DISABLE_LIVE_DATASOURCES; } else { process.env.DISABLE_LIVE_DATASOURCES = previous; } } }); }); function jsonResponse(payload: unknown): Response { return new Response(JSON.stringify(payload), { headers: { "content-type": "application/json" }, }); } async function waitFor(predicate: () => boolean) { for (let attempt = 0; attempt < 20; attempt += 1) { if (predicate()) return; await Promise.resolve(); } throw new Error("condition was not met"); } function telemetryFetch() { return vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url.startsWith("https://prometheus.example/api/v1/query_range")) { return jsonResponse({ status: "success", data: { result: [ { metric: { host: "linux-infra" }, values: [ [1771430000, "10"], [1771430060, "20"], [1771430120, "42"], ], }, ], }, }); } if (url.startsWith("https://prometheus.example/api/v1/query")) { return jsonResponse({ status: "success", data: { result: [ { metric: { host: "linux-infra" }, value: [1771430400, "42"], }, ], }, }); } throw new Error(`Unhandled test request: ${url}`); }); }