// start-next-task.test.mjs — contract tests for startNextTask fetch helper // Run: node --test web/test/start-next-task.test.mjs import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; // ── Contract: startNextTask(basePath, fetchFn) ───────────────────────────────── // POSTs to ${basePath}/api/scripts/start-next-task // Returns {output, exit_code} on HTTP 2xx // Throws on HTTP error async function startNextTask(basePath, fetchFn, agent) { const url = agent && agent !== 'auto' ? `${basePath}/api/scripts/start-next-task?agent=${agent}` : `${basePath}/api/scripts/start-next-task`; const res = await fetchFn(url, { method: 'POST' }); if (!res.ok) { let msg = `HTTP ${res.status}`; try { const body = await res.json(); msg = body.error || msg; } catch {} throw new Error(msg); } return res.json(); } describe('startNextTask', () => { it('POSTs to /api/scripts/start-next-task', async () => { let captured = null; const mockFetch = (url, opts) => { captured = { url, opts }; return Promise.resolve({ ok: true, json: () => Promise.resolve({ output: 'claudomator start abc-123\n', exit_code: 0 }), }); }; await startNextTask('http://localhost:8484', mockFetch); assert.equal(captured.url, 'http://localhost:8484/api/scripts/start-next-task'); assert.equal(captured.opts.method, 'POST'); }); it('appends agent as query parameter when provided', async () => { let captured = null; const mockFetch = (url, opts) => { captured = { url, opts }; return Promise.resolve({ ok: true, json: () => Promise.resolve({ output: 'claudomator start --agent claude abc-123\n', exit_code: 0 }), }); }; await startNextTask('http://localhost:8484', mockFetch, 'claude'); assert.equal(captured.url, 'http://localhost:8484/api/scripts/start-next-task?agent=claude'); }); it('returns output and exit_code on success', async () => { const mockFetch = () => Promise.resolve({ ok: true, json: () => Promise.resolve({ output: 'claudomator start abc-123\n', exit_code: 0 }), }); const result = await startNextTask('', mockFetch); assert.equal(result.output, 'claudomator start abc-123\n'); assert.equal(result.exit_code, 0); }); it('returns output when no task available', async () => { const mockFetch = () => Promise.resolve({ ok: true, json: () => Promise.resolve({ output: 'No task to start.\n', exit_code: 0 }), }); const result = await startNextTask('', mockFetch); assert.equal(result.output, 'No task to start.\n'); }); it('throws with server error message on HTTP error', async () => { const mockFetch = () => Promise.resolve({ ok: false, status: 500, json: () => Promise.resolve({ error: 'script execution failed' }), }); await assert.rejects( () => startNextTask('', mockFetch), /script execution failed/, ); }); it('throws with HTTP status on non-JSON error response', async () => { const mockFetch = () => Promise.resolve({ ok: false, status: 503, json: () => Promise.reject(new Error('not json')), }); await assert.rejects( () => startNextTask('', mockFetch), /HTTP 503/, ); }); });