-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbattle-api.spec.ts
More file actions
203 lines (170 loc) · 6.96 KB
/
Copy pathbattle-api.spec.ts
File metadata and controls
203 lines (170 loc) · 6.96 KB
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/**
* Battle-test E2E suite — API & infrastructure tests.
*
* Covers: REST API endpoints, connection indicator, SPA routing, test API.
*/
import { test, expect } from './fixtures.js';
import {
resetServer,
launchViaUI,
getTasks,
getLatestTmuxName,
getTmuxNameForPrompt,
injectSessionStart,
injectStopEvent,
injectPermissionEvent,
} from './battle-helpers.js';
// ---------------------------------------------------------------------------
// Suite 1: REST API endpoints
// ---------------------------------------------------------------------------
test.describe('API endpoints', () => {
test.beforeEach(async ({ request }) => {
await resetServer(request);
});
test('GET /api/health returns ok with agent count', async ({ request }) => {
const res = await request.get('/api/health');
expect(res.ok()).toBeTruthy();
const data = await res.json();
expect(data.status).toBe('ok');
expect(typeof data.agents).toBe('number');
});
test('GET /api/tasks returns empty array initially', async ({ request }) => {
const res = await request.get('/api/tasks');
const tasks = await res.json();
expect(Array.isArray(tasks)).toBe(true);
expect(tasks.length).toBe(0);
});
test('GET /api/snapshot returns empty array initially', async ({ request }) => {
const res = await request.get('/api/snapshot');
const snapshot = await res.json();
expect(Array.isArray(snapshot)).toBe(true);
expect(snapshot.length).toBe(0);
});
test('GET /api/queue returns empty array initially', async ({ request }) => {
const res = await request.get('/api/queue');
const queue = await res.json();
expect(Array.isArray(queue)).toBe(true);
expect(queue.length).toBe(0);
});
test('GET /api/capture/:name returns 404 for non-existent session', async ({ request }) => {
const res = await request.get('/api/capture/nonexistent');
expect(res.status()).toBe(404);
});
test('tasks API reflects launched agents', async ({ page, request }) => {
await page.goto('/');
await expect(page.locator('.logo')).toHaveText('KOOKR');
await launchViaUI(page, 'Test task', '/test/project');
const tasks = await getTasks(request);
expect(tasks.length).toBe(1);
expect(tasks[0].prompt).toBe('Test task');
expect(tasks[0].cwd).toBe('/test/project');
expect(tasks[0].status).toBe('inProgress');
expect(tasks[0].sessions.length).toBe(1);
});
test('snapshot API reflects agent state after events', async ({ page, request }) => {
await page.goto('/');
await expect(page.locator('.logo')).toHaveText('KOOKR');
await launchViaUI(page, 'Snapshot test', '/test/project');
const tmuxName = await getLatestTmuxName(request);
await injectSessionStart(request, tmuxName);
await injectStopEvent(request, tmuxName);
const res = await request.get('/api/snapshot');
const snapshot = await res.json();
expect(snapshot.length).toBe(1);
expect(snapshot[0].agentId).toBe(tmuxName);
expect(snapshot[0].anomaly).not.toBeNull();
expect(snapshot[0].anomaly.type).toBe('needs_input');
});
test('queue API reflects anomalies in priority order', async ({ page, request }) => {
await page.goto('/');
await expect(page.locator('.logo')).toHaveText('KOOKR');
// Agent A: needs_input (info)
await launchViaUI(page, 'Agent A', '/test/a');
const tmuxA = await getTmuxNameForPrompt(request, 'Agent A');
await injectSessionStart(request, tmuxA);
await injectStopEvent(request, tmuxA);
// Agent B: permission_blocked (warning)
await launchViaUI(page, 'Agent B', '/test/b');
const tmuxB = await getTmuxNameForPrompt(request, 'Agent B');
await injectSessionStart(request, tmuxB);
await injectPermissionEvent(request, tmuxB);
const res = await request.get('/api/queue');
const queue = await res.json();
expect(queue.length).toBe(2);
// Warning (permission) should come before info (needs_input)
expect(queue[0].anomaly.type).toBe('permission_blocked');
expect(queue[1].anomaly.type).toBe('needs_input');
});
});
// ---------------------------------------------------------------------------
// Suite 15: Connection resilience
// ---------------------------------------------------------------------------
test.describe('Connection indicator', () => {
test.beforeEach(async ({ page, request }) => {
await resetServer(request);
await page.goto('/');
await expect(page.locator('.logo')).toHaveText('KOOKR');
});
test('connected state shows health dot connected', async ({ page }) => {
await expect(page.locator('.health-dot-connected')).toBeVisible();
});
test('health dot does not show disconnected class when connected', async ({ page }) => {
await expect(page.locator('.health-dot-disconnected')).not.toBeVisible();
});
});
// ---------------------------------------------------------------------------
// Suite 16: SPA routing & static assets
// ---------------------------------------------------------------------------
test.describe('SPA & static assets', () => {
test('root URL loads the app', async ({ page }) => {
await page.goto('/');
await expect(page.locator('.logo')).toHaveText('KOOKR');
});
test('unknown path falls back to SPA', async ({ page }) => {
await page.goto('/unknown/path');
// Should still load the SPA
await expect(page.locator('.logo')).toHaveText('KOOKR');
});
test('page title or root element exists', async ({ page }) => {
await page.goto('/');
await expect(page.locator('#root')).toBeVisible();
});
});
// ---------------------------------------------------------------------------
// Suite 17: Event injection via test API
// ---------------------------------------------------------------------------
test.describe('Test API', () => {
test.beforeEach(async ({ page, request }) => {
await resetServer(request);
await page.goto('/');
await expect(page.locator('.logo')).toHaveText('KOOKR');
});
test('inject-event endpoint accepts and processes events', async ({ page, request }) => {
await launchViaUI(page, 'Inject test', '/test/project');
const tmuxName = await getLatestTmuxName(request);
const res = await request.post('/api/test/inject-event', {
data: {
tmuxName,
event: {
session_id: 'sess-test',
transcript_path: '/tmp/test.jsonl',
cwd: '/test',
hook_event_name: 'SessionStart',
},
},
});
expect(res.ok()).toBeTruthy();
const body = await res.json();
expect(body.ok).toBe(true);
});
test('reset endpoint clears all state', async ({ request }) => {
// Create some state first (launch via API would need the UI, so just verify reset works)
const resetRes = await request.post('/api/test/reset');
expect(resetRes.ok()).toBeTruthy();
const tasks = await getTasks(request);
expect(tasks.length).toBe(0);
const snapshotRes = await request.get('/api/snapshot');
const snapshot = await snapshotRes.json();
expect(snapshot.length).toBe(0);
});
});