testblocks-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/__tests__/unit/client.test.d.ts +1 -0
- package/dist/__tests__/unit/client.test.js +429 -0
- package/dist/__tests__/unit/client.test.js.map +1 -0
- package/dist/__tests__/unit/config.test.d.ts +1 -0
- package/dist/__tests__/unit/config.test.js +387 -0
- package/dist/__tests__/unit/config.test.js.map +1 -0
- package/dist/__tests__/unit/executor.test.d.ts +1 -0
- package/dist/__tests__/unit/executor.test.js +1017 -0
- package/dist/__tests__/unit/executor.test.js.map +1 -0
- package/dist/__tests__/unit/index.test.d.ts +1 -0
- package/dist/__tests__/unit/index.test.js +406 -0
- package/dist/__tests__/unit/index.test.js.map +1 -0
- package/dist/client.d.ts +71 -0
- package/dist/client.js +66 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +96 -0
- package/dist/config.js.map +1 -0
- package/dist/executor.d.ts +36 -0
- package/dist/executor.js +375 -0
- package/dist/executor.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# TestBlocks Agent
|
|
2
|
+
|
|
3
|
+
Standalone Node.js CLI that polls the TestBlocks server for test jobs, executes them locally using Playwright, and reports results back.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
agent/src/
|
|
9
|
+
index.ts CLI entry point, poll loop, graceful shutdown (SIGINT/SIGTERM)
|
|
10
|
+
config.ts Configuration loading: CLI args > env vars > ~/.testblocks/agent.json
|
|
11
|
+
client.ts HTTP client for server API (validate, poll, progress, results, artifact upload)
|
|
12
|
+
executor.ts Job execution: writes spec files, spawns Playwright, parses results, uploads artifacts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Installation & Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# From the monorepo root
|
|
19
|
+
npm install
|
|
20
|
+
|
|
21
|
+
# Configure the agent
|
|
22
|
+
npx testblocks-agent config --server http://localhost:3001/api --token tbat_xxx --project <project-id>
|
|
23
|
+
|
|
24
|
+
# Start listening for jobs
|
|
25
|
+
npx testblocks-agent
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
Configuration is loaded with this priority (highest first):
|
|
31
|
+
|
|
32
|
+
1. **CLI flags**: `--server`, `--token`, `--project`
|
|
33
|
+
2. **Environment variables**: `TESTBLOCKS_SERVER`, `TESTBLOCKS_TOKEN`, `TESTBLOCKS_PROJECT`
|
|
34
|
+
3. **Config file**: `~/.testblocks/agent.json`
|
|
35
|
+
|
|
36
|
+
### Config File Format
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"serverUrl": "http://localhost:3001/api",
|
|
41
|
+
"token": "tbat_...",
|
|
42
|
+
"projectId": "uuid-here"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## How Execution Works
|
|
47
|
+
|
|
48
|
+
When a job is received from the server:
|
|
49
|
+
|
|
50
|
+
1. **Create temp directory** in `os.tmpdir()/testblocks-agent/<jobId>/`
|
|
51
|
+
2. **Write files**:
|
|
52
|
+
- `test-<n>.spec.ts` — Generated Playwright spec for each test case
|
|
53
|
+
- `playwright.config.ts` — Configuration (browser, timeout, retries, viewport, artifacts)
|
|
54
|
+
- `.env` — Environment variables for the test run
|
|
55
|
+
- `package.json` — Minimal package for the test project
|
|
56
|
+
3. **Symlink** `node_modules` from workspace root for Playwright access
|
|
57
|
+
4. **Spawn** `npx playwright test --reporter=json` as child process
|
|
58
|
+
5. **Stream** progress updates to server (test count parsing from stdout)
|
|
59
|
+
6. **Parse** `report.json` for per-test results (pass/fail/skip, duration, error messages)
|
|
60
|
+
7. **Collect artifacts** — screenshots, videos, traces from the `test-results/` directory
|
|
61
|
+
8. **Upload** artifacts to server via multipart API
|
|
62
|
+
9. **Report** final results (status, per-test outcomes, timing)
|
|
63
|
+
10. **Cleanup** temp directory
|
|
64
|
+
|
|
65
|
+
## Cancellation
|
|
66
|
+
|
|
67
|
+
- Server can cancel a running job via the cancel API
|
|
68
|
+
- Agent checks for cancellation during progress reporting
|
|
69
|
+
- On cancel: sends SIGTERM to Playwright process, reports cancelled status
|
|
70
|
+
|
|
71
|
+
## Graceful Shutdown
|
|
72
|
+
|
|
73
|
+
- Handles SIGINT (Ctrl+C) and SIGTERM signals
|
|
74
|
+
- Kills any running Playwright child process
|
|
75
|
+
- Cleans up temp directories
|
|
76
|
+
- Exits with code 0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const client_1 = require("../../client");
|
|
5
|
+
// --- Helpers ---
|
|
6
|
+
const baseConfig = {
|
|
7
|
+
serverUrl: 'http://localhost:3000',
|
|
8
|
+
token: 'tbat_test-token-abc',
|
|
9
|
+
projectId: 'proj-123',
|
|
10
|
+
};
|
|
11
|
+
function mockFetchResponse(body, status = 200, statusText = 'OK') {
|
|
12
|
+
return vitest_1.vi.fn().mockResolvedValue({
|
|
13
|
+
ok: status >= 200 && status < 300,
|
|
14
|
+
status,
|
|
15
|
+
statusText,
|
|
16
|
+
json: () => Promise.resolve(body),
|
|
17
|
+
text: () => Promise.resolve(JSON.stringify(body)),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function mockFetchErrorResponse(body, status, statusText = 'Error') {
|
|
21
|
+
const textBody = typeof body === 'string' ? body : JSON.stringify(body);
|
|
22
|
+
return vitest_1.vi.fn().mockResolvedValue({
|
|
23
|
+
ok: false,
|
|
24
|
+
status,
|
|
25
|
+
statusText,
|
|
26
|
+
json: () => Promise.resolve(typeof body === 'object' ? body : {}),
|
|
27
|
+
text: () => Promise.resolve(textBody),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// --- Tests ---
|
|
31
|
+
(0, vitest_1.describe)('AgentClient', () => {
|
|
32
|
+
let originalFetch;
|
|
33
|
+
(0, vitest_1.beforeEach)(() => {
|
|
34
|
+
originalFetch = globalThis.fetch;
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.afterEach)(() => {
|
|
37
|
+
globalThis.fetch = originalFetch;
|
|
38
|
+
vitest_1.vi.restoreAllMocks();
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.describe)('constructor', () => {
|
|
41
|
+
(0, vitest_1.it)('stores serverUrl and token from config', () => {
|
|
42
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
43
|
+
// We cannot access private fields directly, but we can verify
|
|
44
|
+
// they are used correctly by calling a method
|
|
45
|
+
(0, vitest_1.expect)(client).toBeInstanceOf(client_1.AgentClient);
|
|
46
|
+
});
|
|
47
|
+
(0, vitest_1.it)('uses the provided config for subsequent requests', async () => {
|
|
48
|
+
const fetchMock = mockFetchResponse({ valid: true, projectId: 'p1', projectName: 'Test' });
|
|
49
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
50
|
+
const client = new client_1.AgentClient({
|
|
51
|
+
serverUrl: 'https://custom-server.com',
|
|
52
|
+
token: 'custom-token',
|
|
53
|
+
projectId: 'custom-proj',
|
|
54
|
+
});
|
|
55
|
+
await client.validate();
|
|
56
|
+
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledWith('https://custom-server.com/api/agent/validate', vitest_1.expect.objectContaining({
|
|
57
|
+
method: 'GET',
|
|
58
|
+
headers: vitest_1.expect.objectContaining({
|
|
59
|
+
Authorization: 'Bearer custom-token',
|
|
60
|
+
}),
|
|
61
|
+
}));
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.describe)('validate', () => {
|
|
65
|
+
(0, vitest_1.it)('sends GET to /api/agent/validate with auth header', async () => {
|
|
66
|
+
const fetchMock = mockFetchResponse({
|
|
67
|
+
valid: true,
|
|
68
|
+
projectId: 'proj-123',
|
|
69
|
+
projectName: 'My Project',
|
|
70
|
+
});
|
|
71
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
72
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
73
|
+
const result = await client.validate();
|
|
74
|
+
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledTimes(1);
|
|
75
|
+
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/agent/validate', {
|
|
76
|
+
method: 'GET',
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: 'Bearer tbat_test-token-abc',
|
|
79
|
+
},
|
|
80
|
+
body: undefined,
|
|
81
|
+
});
|
|
82
|
+
(0, vitest_1.expect)(result).toEqual({
|
|
83
|
+
valid: true,
|
|
84
|
+
projectId: 'proj-123',
|
|
85
|
+
projectName: 'My Project',
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
(0, vitest_1.it)('throws on non-200 response with error message from body', async () => {
|
|
89
|
+
const fetchMock = mockFetchErrorResponse({ error: 'Invalid token' }, 401, 'Unauthorized');
|
|
90
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
91
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
92
|
+
await (0, vitest_1.expect)(client.validate()).rejects.toThrow('API 401: Invalid token');
|
|
93
|
+
});
|
|
94
|
+
(0, vitest_1.it)('throws on non-200 response falling back to body text when no error field', async () => {
|
|
95
|
+
const fetchMock = mockFetchErrorResponse({ message: 'no error field here' }, 403, 'Forbidden');
|
|
96
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
97
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
98
|
+
// The body text is the full JSON string since `error` field is undefined
|
|
99
|
+
// and `|| errBody` will use the text since `undefined` is falsy
|
|
100
|
+
await (0, vitest_1.expect)(client.validate()).rejects.toThrow('API 403:');
|
|
101
|
+
});
|
|
102
|
+
(0, vitest_1.it)('falls back to raw text when JSON parse fails on error body', async () => {
|
|
103
|
+
vitest_1.vi.stubGlobal('fetch', vitest_1.vi.fn().mockResolvedValue({
|
|
104
|
+
ok: false,
|
|
105
|
+
status: 500,
|
|
106
|
+
statusText: 'Internal Server Error',
|
|
107
|
+
text: () => Promise.resolve('plain text error, not JSON'),
|
|
108
|
+
}));
|
|
109
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
110
|
+
await (0, vitest_1.expect)(client.validate()).rejects.toThrow('API 500: plain text error, not JSON');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
(0, vitest_1.describe)('poll', () => {
|
|
114
|
+
(0, vitest_1.it)('sends GET to /api/agent/poll', async () => {
|
|
115
|
+
const jobPayload = {
|
|
116
|
+
jobId: 'job-1',
|
|
117
|
+
testRunId: 'run-1',
|
|
118
|
+
specs: [{ testCaseId: 'tc-1', filename: 'test1', specCode: '...', fixturesCode: null }],
|
|
119
|
+
config: {
|
|
120
|
+
browser: 'chrome',
|
|
121
|
+
timeout: 30000,
|
|
122
|
+
retries: 0,
|
|
123
|
+
headless: true,
|
|
124
|
+
screenshots: 'off',
|
|
125
|
+
video: 'off',
|
|
126
|
+
trace: 'off',
|
|
127
|
+
viewportWidth: 1280,
|
|
128
|
+
viewportHeight: 720,
|
|
129
|
+
},
|
|
130
|
+
envVars: [],
|
|
131
|
+
};
|
|
132
|
+
const fetchMock = mockFetchResponse({ job: jobPayload });
|
|
133
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
134
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
135
|
+
const result = await client.poll();
|
|
136
|
+
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/agent/poll', vitest_1.expect.objectContaining({ method: 'GET' }));
|
|
137
|
+
(0, vitest_1.expect)(result).toEqual(jobPayload);
|
|
138
|
+
});
|
|
139
|
+
(0, vitest_1.it)('returns null when response body has job: null', async () => {
|
|
140
|
+
const fetchMock = mockFetchResponse({ job: null });
|
|
141
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
142
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
143
|
+
const result = await client.poll();
|
|
144
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
(0, vitest_1.it)('returns null when response body has no job field', async () => {
|
|
147
|
+
const fetchMock = mockFetchResponse({});
|
|
148
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
149
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
150
|
+
const result = await client.poll();
|
|
151
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
(0, vitest_1.it)('throws on error response', async () => {
|
|
154
|
+
const fetchMock = mockFetchErrorResponse({ error: 'Server busy' }, 503, 'Service Unavailable');
|
|
155
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
156
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
157
|
+
await (0, vitest_1.expect)(client.poll()).rejects.toThrow('API 503: Server busy');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
(0, vitest_1.describe)('reportProgress', () => {
|
|
161
|
+
(0, vitest_1.it)('sends POST to /api/agent/progress with progress data', async () => {
|
|
162
|
+
const fetchMock = mockFetchResponse({ cancelled: false });
|
|
163
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
164
|
+
const update = {
|
|
165
|
+
jobId: 'job-1',
|
|
166
|
+
completedTests: 3,
|
|
167
|
+
currentTest: 'login-test',
|
|
168
|
+
perTestStatus: [
|
|
169
|
+
{ testCaseId: 'tc-1', testName: 'login-test', status: 'passed' },
|
|
170
|
+
{ testCaseId: 'tc-2', testName: 'dashboard-test', status: 'pending' },
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
174
|
+
const result = await client.reportProgress(update);
|
|
175
|
+
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/agent/progress', {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: {
|
|
178
|
+
Authorization: 'Bearer tbat_test-token-abc',
|
|
179
|
+
'Content-Type': 'application/json',
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify(update),
|
|
182
|
+
});
|
|
183
|
+
(0, vitest_1.expect)(result).toEqual({ cancelled: false });
|
|
184
|
+
});
|
|
185
|
+
(0, vitest_1.it)('returns cancelled: true when server signals cancellation', async () => {
|
|
186
|
+
const fetchMock = mockFetchResponse({ cancelled: true });
|
|
187
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
188
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
189
|
+
const result = await client.reportProgress({
|
|
190
|
+
jobId: 'job-1',
|
|
191
|
+
completedTests: 1,
|
|
192
|
+
currentTest: null,
|
|
193
|
+
perTestStatus: [],
|
|
194
|
+
});
|
|
195
|
+
(0, vitest_1.expect)(result.cancelled).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
(0, vitest_1.it)('throws on error response', async () => {
|
|
198
|
+
const fetchMock = mockFetchErrorResponse({ error: 'Job not found' }, 404, 'Not Found');
|
|
199
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
200
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
201
|
+
await (0, vitest_1.expect)(client.reportProgress({
|
|
202
|
+
jobId: 'nonexistent',
|
|
203
|
+
completedTests: 0,
|
|
204
|
+
currentTest: null,
|
|
205
|
+
perTestStatus: [],
|
|
206
|
+
})).rejects.toThrow('API 404: Job not found');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
(0, vitest_1.describe)('reportResults', () => {
|
|
210
|
+
(0, vitest_1.it)('sends POST to /api/agent/results with results payload', async () => {
|
|
211
|
+
const fetchMock = mockFetchResponse({ ok: true });
|
|
212
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
213
|
+
const payload = {
|
|
214
|
+
jobId: 'job-1',
|
|
215
|
+
testRunId: 'run-1',
|
|
216
|
+
results: [
|
|
217
|
+
{
|
|
218
|
+
testCaseId: 'tc-1',
|
|
219
|
+
status: 'passed',
|
|
220
|
+
durationMs: 1500,
|
|
221
|
+
errorMessage: null,
|
|
222
|
+
retryCount: 0,
|
|
223
|
+
artifacts: null,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
testCaseId: 'tc-2',
|
|
227
|
+
status: 'failed',
|
|
228
|
+
durationMs: 3000,
|
|
229
|
+
errorMessage: 'Element not found',
|
|
230
|
+
retryCount: 1,
|
|
231
|
+
artifacts: {
|
|
232
|
+
screenshots: ['screenshots/tc2-1.png'],
|
|
233
|
+
video: null,
|
|
234
|
+
trace: null,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
};
|
|
239
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
240
|
+
await client.reportResults(payload);
|
|
241
|
+
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/agent/results', {
|
|
242
|
+
method: 'POST',
|
|
243
|
+
headers: {
|
|
244
|
+
Authorization: 'Bearer tbat_test-token-abc',
|
|
245
|
+
'Content-Type': 'application/json',
|
|
246
|
+
},
|
|
247
|
+
body: JSON.stringify(payload),
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
(0, vitest_1.it)('returns void on success', async () => {
|
|
251
|
+
const fetchMock = mockFetchResponse({ ok: true });
|
|
252
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
253
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
254
|
+
const result = await client.reportResults({
|
|
255
|
+
jobId: 'job-1',
|
|
256
|
+
testRunId: 'run-1',
|
|
257
|
+
results: [],
|
|
258
|
+
});
|
|
259
|
+
// reportResults calls await request but doesn't explicitly return,
|
|
260
|
+
// so the return value is the parsed JSON (from request method)
|
|
261
|
+
// but the method signature is Promise<void> so we just check it resolves
|
|
262
|
+
(0, vitest_1.expect)(result).toBeUndefined();
|
|
263
|
+
});
|
|
264
|
+
(0, vitest_1.it)('includes optional status field in payload', async () => {
|
|
265
|
+
const fetchMock = mockFetchResponse({ ok: true });
|
|
266
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
267
|
+
const payload = {
|
|
268
|
+
jobId: 'job-1',
|
|
269
|
+
testRunId: 'run-1',
|
|
270
|
+
results: [],
|
|
271
|
+
status: 'cancelled',
|
|
272
|
+
};
|
|
273
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
274
|
+
await client.reportResults(payload);
|
|
275
|
+
const sentBody = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
276
|
+
(0, vitest_1.expect)(sentBody.status).toBe('cancelled');
|
|
277
|
+
});
|
|
278
|
+
(0, vitest_1.it)('throws on error response', async () => {
|
|
279
|
+
const fetchMock = mockFetchErrorResponse({ error: 'Internal error' }, 500, 'Internal Server Error');
|
|
280
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
281
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
282
|
+
await (0, vitest_1.expect)(client.reportResults({
|
|
283
|
+
jobId: 'job-1',
|
|
284
|
+
testRunId: 'run-1',
|
|
285
|
+
results: [],
|
|
286
|
+
})).rejects.toThrow('API 500: Internal error');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
(0, vitest_1.describe)('uploadArtifact', () => {
|
|
290
|
+
(0, vitest_1.it)('sends POST to /api/agent/artifacts/{runId} with binary body', async () => {
|
|
291
|
+
const fetchMock = vitest_1.vi.fn().mockResolvedValue({
|
|
292
|
+
ok: true,
|
|
293
|
+
status: 200,
|
|
294
|
+
});
|
|
295
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
296
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
297
|
+
const fileData = Buffer.from('fake image data');
|
|
298
|
+
await client.uploadArtifact('run-1', 'screenshots/test.png', fileData);
|
|
299
|
+
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledTimes(1);
|
|
300
|
+
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/agent/artifacts/run-1', {
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: {
|
|
303
|
+
Authorization: 'Bearer tbat_test-token-abc',
|
|
304
|
+
'Content-Type': 'application/octet-stream',
|
|
305
|
+
'X-Artifact-Path': 'screenshots/test.png',
|
|
306
|
+
},
|
|
307
|
+
body: vitest_1.expect.any(Uint8Array),
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
(0, vitest_1.it)('sends file data as Uint8Array', async () => {
|
|
311
|
+
const fetchMock = vitest_1.vi.fn().mockResolvedValue({
|
|
312
|
+
ok: true,
|
|
313
|
+
status: 200,
|
|
314
|
+
});
|
|
315
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
316
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
317
|
+
const fileData = Buffer.from([0x89, 0x50, 0x4e, 0x47]); // PNG header bytes
|
|
318
|
+
await client.uploadArtifact('run-1', 'test.png', fileData);
|
|
319
|
+
const sentBody = fetchMock.mock.calls[0][1].body;
|
|
320
|
+
(0, vitest_1.expect)(sentBody).toBeInstanceOf(Uint8Array);
|
|
321
|
+
(0, vitest_1.expect)(sentBody[0]).toBe(0x89);
|
|
322
|
+
(0, vitest_1.expect)(sentBody[1]).toBe(0x50);
|
|
323
|
+
});
|
|
324
|
+
(0, vitest_1.it)('includes X-Artifact-Path header with file path', async () => {
|
|
325
|
+
const fetchMock = vitest_1.vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
326
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
327
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
328
|
+
await client.uploadArtifact('run-1', 'nested/dir/trace.zip', Buffer.from('data'));
|
|
329
|
+
const headers = fetchMock.mock.calls[0][1].headers;
|
|
330
|
+
(0, vitest_1.expect)(headers['X-Artifact-Path']).toBe('nested/dir/trace.zip');
|
|
331
|
+
});
|
|
332
|
+
(0, vitest_1.it)('throws on upload failure with status code', async () => {
|
|
333
|
+
const fetchMock = vitest_1.vi.fn().mockResolvedValue({
|
|
334
|
+
ok: false,
|
|
335
|
+
status: 413,
|
|
336
|
+
statusText: 'Payload Too Large',
|
|
337
|
+
});
|
|
338
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
339
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
340
|
+
await (0, vitest_1.expect)(client.uploadArtifact('run-1', 'big-file.mp4', Buffer.from('data'))).rejects.toThrow('Upload failed: 413');
|
|
341
|
+
});
|
|
342
|
+
(0, vitest_1.it)('throws on server error', async () => {
|
|
343
|
+
const fetchMock = vitest_1.vi.fn().mockResolvedValue({
|
|
344
|
+
ok: false,
|
|
345
|
+
status: 500,
|
|
346
|
+
statusText: 'Internal Server Error',
|
|
347
|
+
});
|
|
348
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
349
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
350
|
+
await (0, vitest_1.expect)(client.uploadArtifact('run-1', 'test.png', Buffer.from('data'))).rejects.toThrow('Upload failed: 500');
|
|
351
|
+
});
|
|
352
|
+
(0, vitest_1.it)('resolves on success (returns void)', async () => {
|
|
353
|
+
const fetchMock = vitest_1.vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
354
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
355
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
356
|
+
const result = await client.uploadArtifact('run-1', 'test.png', Buffer.from('data'));
|
|
357
|
+
(0, vitest_1.expect)(result).toBeUndefined();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
(0, vitest_1.describe)('auth header', () => {
|
|
361
|
+
(0, vitest_1.it)('includes Bearer token in all requests', async () => {
|
|
362
|
+
const fetchMock = mockFetchResponse({});
|
|
363
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
364
|
+
const client = new client_1.AgentClient({
|
|
365
|
+
serverUrl: 'http://test.com',
|
|
366
|
+
token: 'my-secret-token',
|
|
367
|
+
projectId: 'proj',
|
|
368
|
+
});
|
|
369
|
+
await client.validate();
|
|
370
|
+
const headers = fetchMock.mock.calls[0][1].headers;
|
|
371
|
+
(0, vitest_1.expect)(headers.Authorization).toBe('Bearer my-secret-token');
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
(0, vitest_1.describe)('URL construction', () => {
|
|
375
|
+
(0, vitest_1.it)('constructs URL from serverUrl + /api/agent + path', async () => {
|
|
376
|
+
const fetchMock = mockFetchResponse({});
|
|
377
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
378
|
+
const client = new client_1.AgentClient({
|
|
379
|
+
serverUrl: 'https://my-server.example.com',
|
|
380
|
+
token: 'tok',
|
|
381
|
+
projectId: 'proj',
|
|
382
|
+
});
|
|
383
|
+
await client.validate();
|
|
384
|
+
(0, vitest_1.expect)(fetchMock.mock.calls[0][0]).toBe('https://my-server.example.com/api/agent/validate');
|
|
385
|
+
});
|
|
386
|
+
(0, vitest_1.it)('does not double-slash when serverUrl has no trailing slash', async () => {
|
|
387
|
+
const fetchMock = mockFetchResponse({ job: null });
|
|
388
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
389
|
+
const client = new client_1.AgentClient({
|
|
390
|
+
serverUrl: 'http://localhost:3000',
|
|
391
|
+
token: 'tok',
|
|
392
|
+
projectId: 'proj',
|
|
393
|
+
});
|
|
394
|
+
await client.poll();
|
|
395
|
+
(0, vitest_1.expect)(fetchMock.mock.calls[0][0]).toBe('http://localhost:3000/api/agent/poll');
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
(0, vitest_1.describe)('request method Content-Type', () => {
|
|
399
|
+
(0, vitest_1.it)('sets Content-Type for POST requests with body', async () => {
|
|
400
|
+
const fetchMock = mockFetchResponse({ cancelled: false });
|
|
401
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
402
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
403
|
+
await client.reportProgress({
|
|
404
|
+
jobId: 'j1',
|
|
405
|
+
completedTests: 0,
|
|
406
|
+
currentTest: null,
|
|
407
|
+
perTestStatus: [],
|
|
408
|
+
});
|
|
409
|
+
const headers = fetchMock.mock.calls[0][1].headers;
|
|
410
|
+
(0, vitest_1.expect)(headers['Content-Type']).toBe('application/json');
|
|
411
|
+
});
|
|
412
|
+
(0, vitest_1.it)('does not set Content-Type for GET requests', async () => {
|
|
413
|
+
const fetchMock = mockFetchResponse({ job: null });
|
|
414
|
+
vitest_1.vi.stubGlobal('fetch', fetchMock);
|
|
415
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
416
|
+
await client.poll();
|
|
417
|
+
const headers = fetchMock.mock.calls[0][1].headers;
|
|
418
|
+
(0, vitest_1.expect)(headers['Content-Type']).toBeUndefined();
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
(0, vitest_1.describe)('network errors', () => {
|
|
422
|
+
(0, vitest_1.it)('propagates fetch rejection (e.g. ECONNREFUSED)', async () => {
|
|
423
|
+
vitest_1.vi.stubGlobal('fetch', vitest_1.vi.fn().mockRejectedValue(new TypeError('fetch failed')));
|
|
424
|
+
const client = new client_1.AgentClient(baseConfig);
|
|
425
|
+
await (0, vitest_1.expect)(client.poll()).rejects.toThrow('fetch failed');
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
//# sourceMappingURL=client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/client.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|