diff --git a/src/web-dashboard-routes.test.ts b/src/web-dashboard-routes.test.ts new file mode 100644 index 0000000..943a497 --- /dev/null +++ b/src/web-dashboard-routes.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, it } from 'vitest'; + +import type { StatusSnapshot } from './status-dashboard.js'; +import type { ScheduledTask } from './types.js'; +import { handleSimpleGetRoute } from './web-dashboard-routes.js'; + +function jsonResponse(value: unknown, init?: ResponseInit): Response { + return new Response(JSON.stringify(value), { + ...init, + headers: { + 'content-type': 'application/json; charset=utf-8', + ...(init?.headers as Record | undefined), + }, + }); +} + +function makeTask(overrides: Partial = {}): ScheduledTask { + return { + id: 'task-1', + group_folder: 'general', + chat_jid: 'dc:general', + agent_type: null, + status_message_id: null, + status_started_at: null, + prompt: 'regular scheduled task', + schedule_type: 'cron', + schedule_value: '* * * * *', + context_mode: 'group', + next_run: '2026-04-26T05:00:00.000Z', + last_run: null, + last_result: null, + status: 'active', + created_at: '2026-04-26T04:00:00.000Z', + ...overrides, + }; +} + +describe('web dashboard simple routes', () => { + const snapshots: StatusSnapshot[] = [ + { + serviceId: 'codex-main', + agentType: 'codex', + assistantName: 'Codex', + updatedAt: '2026-04-26T05:00:00.000Z', + entries: [], + }, + ]; + + it('serves health, snapshots, and task JSON routes', async () => { + const routeContext = { + statusMaxAgeMs: 1234, + readSnapshots: (maxAgeMs: number) => { + expect(maxAgeMs).toBe(1234); + return snapshots; + }, + loadTasks: () => [makeTask()], + jsonResponse, + }; + + const health = handleSimpleGetRoute({ + ...routeContext, + url: new URL('http://localhost/api/health'), + }); + expect(health?.status).toBe(200); + await expect(health?.json()).resolves.toEqual({ ok: true }); + + const status = handleSimpleGetRoute({ + ...routeContext, + url: new URL('http://localhost/api/status-snapshots'), + }); + expect(status?.status).toBe(200); + await expect(status?.json()).resolves.toEqual(snapshots); + + const tasks = handleSimpleGetRoute({ + ...routeContext, + url: new URL('http://localhost/api/tasks'), + }); + expect(tasks?.status).toBe(200); + await expect(tasks?.json()).resolves.toMatchObject([ + { + id: 'task-1', + groupFolder: 'general', + promptPreview: 'regular scheduled task', + status: 'active', + }, + ]); + }); + + it('returns null for routes outside the simple table', () => { + const result = handleSimpleGetRoute({ + url: new URL('http://localhost/api/overview'), + statusMaxAgeMs: 1234, + readSnapshots: () => [], + loadTasks: () => [], + jsonResponse, + }); + + expect(result).toBeNull(); + }); +}); diff --git a/src/web-dashboard-routes.ts b/src/web-dashboard-routes.ts new file mode 100644 index 0000000..6efdefe --- /dev/null +++ b/src/web-dashboard-routes.ts @@ -0,0 +1,35 @@ +import type { StatusSnapshot } from './status-dashboard.js'; +import type { ScheduledTask } from './types.js'; +import { sanitizeScheduledTask } from './web-dashboard-data.js'; +import { serveValidatedAttachment } from './web-dashboard-attachments.js'; + +type JsonResponse = ( + value: unknown, + init?: ResponseInit, + request?: Request, +) => Response; + +interface SimpleGetRouteContext { + url: URL; + statusMaxAgeMs: number; + readSnapshots: (maxAgeMs: number) => StatusSnapshot[]; + loadTasks: () => ScheduledTask[]; + jsonResponse: JsonResponse; +} + +export function handleSimpleGetRoute({ + url, + statusMaxAgeMs, + readSnapshots, + loadTasks, + jsonResponse, +}: SimpleGetRouteContext): Response | null { + const simpleGetRoutes: Record Response> = { + '/api/health': () => jsonResponse({ ok: true }), + '/api/status-snapshots': () => jsonResponse(readSnapshots(statusMaxAgeMs)), + '/api/tasks': () => jsonResponse(loadTasks().map(sanitizeScheduledTask)), + '/api/attachments': () => serveValidatedAttachment(url), + }; + const route = simpleGetRoutes[url.pathname]; + return route ? route() : null; +} diff --git a/src/web-dashboard-server.ts b/src/web-dashboard-server.ts index 935fb4d..9ba1b73 100644 --- a/src/web-dashboard-server.ts +++ b/src/web-dashboard-server.ts @@ -46,7 +46,7 @@ import { buildWebDashboardOverview, sanitizeScheduledTask, } from './web-dashboard-data.js'; -import { serveValidatedAttachment } from './web-dashboard-attachments.js'; +import { handleSimpleGetRoute } from './web-dashboard-routes.js'; import { addClaudeAccountFromToken, getActiveCodexSettingsIndex, @@ -1571,15 +1571,14 @@ export function createWebDashboardHandler( return jsonResponse({ error: 'Method not allowed' }, { status: 405 }); } - const simpleGetRoutes: Record Response> = { - '/api/health': () => jsonResponse({ ok: true }), - '/api/status-snapshots': () => - jsonResponse(readSnapshots(statusMaxAgeMs)), - '/api/tasks': () => jsonResponse(loadTasks().map(sanitizeScheduledTask)), - '/api/attachments': () => serveValidatedAttachment(url), - }; - const simpleGetRoute = simpleGetRoutes[url.pathname]; - if (simpleGetRoute) return simpleGetRoute(); + const simpleGetRoute = handleSimpleGetRoute({ + url, + statusMaxAgeMs, + readSnapshots, + loadTasks, + jsonResponse, + }); + if (simpleGetRoute) return simpleGetRoute; if (url.pathname === '/api/overview') { const snapshots = readSnapshots(statusMaxAgeMs);