1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
// 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/,
);
});
});
|