@positronic/spec 0.0.55 → 0.0.57
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/dist/{api.d.ts → api/brains.d.ts} +2 -160
- package/dist/api/brains.d.ts.map +1 -0
- package/dist/{api.js → api/brains.js} +18 -1234
- package/dist/api/brains.js.map +1 -0
- package/dist/api/bundle.d.ts +17 -0
- package/dist/api/bundle.d.ts.map +1 -0
- package/dist/api/bundle.js +56 -0
- package/dist/api/bundle.js.map +1 -0
- package/dist/api/index.d.ts +12 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +11 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/pages.d.ts +46 -0
- package/dist/api/pages.d.ts.map +1 -0
- package/dist/api/pages.js +338 -0
- package/dist/api/pages.js.map +1 -0
- package/dist/api/resources.d.ts +28 -0
- package/dist/api/resources.d.ts.map +1 -0
- package/dist/api/resources.js +272 -0
- package/dist/api/resources.js.map +1 -0
- package/dist/api/schedules.d.ts +20 -0
- package/dist/api/schedules.d.ts.map +1 -0
- package/dist/api/schedules.js +159 -0
- package/dist/api/schedules.js.map +1 -0
- package/dist/api/secrets.d.ts +27 -0
- package/dist/api/secrets.d.ts.map +1 -0
- package/dist/api/secrets.js +175 -0
- package/dist/api/secrets.js.map +1 -0
- package/dist/api/signals.d.ts +53 -0
- package/dist/api/signals.d.ts.map +1 -0
- package/dist/api/signals.js +276 -0
- package/dist/api/signals.js.map +1 -0
- package/dist/api/status.d.ts +3 -0
- package/dist/api/status.d.ts.map +1 -0
- package/dist/api/status.js +23 -0
- package/dist/api/status.js.map +1 -0
- package/dist/api/types.d.ts +2 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/users.d.ts +43 -0
- package/dist/api/users.d.ts.map +1 -0
- package/dist/api/users.js +221 -0
- package/dist/api/users.js.map +1 -0
- package/dist/api/webhooks.d.ts +30 -0
- package/dist/api/webhooks.d.ts.map +1 -0
- package/dist/api/webhooks.js +208 -0
- package/dist/api/webhooks.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/{api.js → api/brains.js} +1378 -4108
- package/dist/src/api/bundle.js +228 -0
- package/dist/src/api/index.js +10 -0
- package/dist/src/api/pages.js +840 -0
- package/dist/src/api/resources.js +707 -0
- package/dist/src/api/schedules.js +479 -0
- package/dist/src/api/secrets.js +518 -0
- package/dist/src/api/signals.js +778 -0
- package/dist/src/api/status.js +180 -0
- package/dist/src/api/types.js +1 -0
- package/dist/src/api/users.js +650 -0
- package/dist/src/api/webhooks.js +639 -0
- package/dist/src/index.js +1 -1
- package/package.json +1 -1
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js.map +0 -1
|
@@ -1,297 +1,4 @@
|
|
|
1
1
|
import { STATUS, BRAIN_EVENTS } from '@positronic/core';
|
|
2
|
-
export async function testStatus(fetch) {
|
|
3
|
-
try {
|
|
4
|
-
const request = new Request('http://example.com/status', {
|
|
5
|
-
method: 'GET',
|
|
6
|
-
});
|
|
7
|
-
const response = await fetch(request);
|
|
8
|
-
if (!response.ok) {
|
|
9
|
-
console.error(`Status endpoint returned ${response.status}`);
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
const data = await response.json();
|
|
13
|
-
if (data.ready !== true) {
|
|
14
|
-
console.error(`Expected { ready: true }, got ${JSON.stringify(data)}`);
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
catch (error) {
|
|
20
|
-
console.error(`Failed to test status endpoint:`, error);
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
export const resources = {
|
|
25
|
-
/**
|
|
26
|
-
* Test GET /resources - List all resources
|
|
27
|
-
*/
|
|
28
|
-
async list(fetch) {
|
|
29
|
-
try {
|
|
30
|
-
const request = new Request('http://example.com/resources', {
|
|
31
|
-
method: 'GET',
|
|
32
|
-
});
|
|
33
|
-
const response = await fetch(request);
|
|
34
|
-
if (!response.ok) {
|
|
35
|
-
console.error(`GET /resources returned ${response.status}`);
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
const data = await response.json();
|
|
39
|
-
// Validate response structure
|
|
40
|
-
if (!Array.isArray(data.resources)) {
|
|
41
|
-
console.error(`Expected resources to be an array, got ${typeof data.resources}`);
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
if (typeof data.truncated !== 'boolean') {
|
|
45
|
-
console.error(`Expected truncated to be boolean, got ${typeof data.truncated}`);
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
if (typeof data.count !== 'number') {
|
|
49
|
-
console.error(`Expected count to be number, got ${typeof data.count}`);
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
// Validate each resource has required fields
|
|
53
|
-
for (const resource of data.resources) {
|
|
54
|
-
if (!resource.key ||
|
|
55
|
-
!resource.type ||
|
|
56
|
-
typeof resource.size !== 'number' ||
|
|
57
|
-
!resource.lastModified ||
|
|
58
|
-
typeof resource.local !== 'boolean') {
|
|
59
|
-
console.error(`Resource missing required fields: ${JSON.stringify(resource)}`);
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
if (!['text', 'binary'].includes(resource.type)) {
|
|
63
|
-
console.error(`Invalid resource type: ${resource.type}`);
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
console.error(`Failed to test GET /resources:`, error);
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
/**
|
|
75
|
-
* Test POST /resources - Upload a resource
|
|
76
|
-
*/
|
|
77
|
-
async upload(fetch) {
|
|
78
|
-
try {
|
|
79
|
-
const formData = new FormData();
|
|
80
|
-
formData.append('file', new Blob(['test content'], { type: 'text/plain' }), 'test.txt');
|
|
81
|
-
formData.append('type', 'text');
|
|
82
|
-
formData.append('key', 'test-resource.txt');
|
|
83
|
-
formData.append('local', 'false');
|
|
84
|
-
const request = new Request('http://example.com/resources', {
|
|
85
|
-
method: 'POST',
|
|
86
|
-
body: formData,
|
|
87
|
-
});
|
|
88
|
-
const response = await fetch(request);
|
|
89
|
-
if (response.status !== 201) {
|
|
90
|
-
console.error(`POST /resources returned ${response.status}, expected 201`);
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
const data = await response.json();
|
|
94
|
-
// Validate response has required fields
|
|
95
|
-
if (!data.key ||
|
|
96
|
-
!data.type ||
|
|
97
|
-
typeof data.size !== 'number' ||
|
|
98
|
-
!data.lastModified ||
|
|
99
|
-
typeof data.local !== 'boolean') {
|
|
100
|
-
console.error(`Response missing required fields: ${JSON.stringify(data)}`);
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
if (data.key !== 'test-resource.txt') {
|
|
104
|
-
console.error(`Expected key to be 'test-resource.txt', got ${data.key}`);
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
if (data.type !== 'text') {
|
|
108
|
-
console.error(`Expected type to be 'text', got ${data.type}`);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
if (data.local !== false) {
|
|
112
|
-
console.error(`Expected local to be false, got ${data.local}`);
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
console.error(`Failed to test POST /resources:`, error);
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
/**
|
|
123
|
-
* Test DELETE /resources/:key - Delete a specific resource
|
|
124
|
-
*/
|
|
125
|
-
async delete(fetch, key) {
|
|
126
|
-
try {
|
|
127
|
-
const request = new Request(`http://example.com/resources/${encodeURIComponent(key)}`, {
|
|
128
|
-
method: 'DELETE',
|
|
129
|
-
});
|
|
130
|
-
const response = await fetch(request);
|
|
131
|
-
if (response.status !== 204) {
|
|
132
|
-
console.error(`DELETE /resources/${key} returned ${response.status}, expected 204`);
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
console.error(`Failed to test DELETE /resources/${key}:`, error);
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
/**
|
|
143
|
-
* Test DELETE /resources - Bulk delete all resources (dev mode only)
|
|
144
|
-
*/
|
|
145
|
-
async deleteAll(fetch) {
|
|
146
|
-
try {
|
|
147
|
-
const request = new Request('http://example.com/resources', {
|
|
148
|
-
method: 'DELETE',
|
|
149
|
-
});
|
|
150
|
-
const response = await fetch(request);
|
|
151
|
-
// In production mode, this should return 403
|
|
152
|
-
if (response.status === 403) {
|
|
153
|
-
const data = await response.json();
|
|
154
|
-
if (data.error === 'Bulk delete is only available in development mode') {
|
|
155
|
-
// This is expected behavior in production
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (response.status !== 200) {
|
|
160
|
-
console.error(`DELETE /resources returned ${response.status}, expected 200 or 403`);
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
const data = await response.json();
|
|
164
|
-
if (typeof data.deletedCount !== 'number') {
|
|
165
|
-
console.error(`Expected deletedCount to be number, got ${typeof data.deletedCount}`);
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
console.error(`Failed to test DELETE /resources:`, error);
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
/**
|
|
176
|
-
* Test DELETE /resources preserves pages - bulk delete should not delete pages
|
|
177
|
-
*/
|
|
178
|
-
async deleteAllPreservesPages(fetch) {
|
|
179
|
-
try {
|
|
180
|
-
// First create a page
|
|
181
|
-
const pageSlug = `spec-test-page-${Date.now()}`;
|
|
182
|
-
const pageHtml = '<html><body>Test page for spec</body></html>';
|
|
183
|
-
const brainRunId = 'spec-test-brain-run-id';
|
|
184
|
-
const createPageRequest = new Request('http://example.com/pages', {
|
|
185
|
-
method: 'POST',
|
|
186
|
-
headers: {
|
|
187
|
-
'Content-Type': 'application/json',
|
|
188
|
-
},
|
|
189
|
-
body: JSON.stringify({
|
|
190
|
-
slug: pageSlug,
|
|
191
|
-
html: pageHtml,
|
|
192
|
-
brainRunId,
|
|
193
|
-
}),
|
|
194
|
-
});
|
|
195
|
-
const createPageResponse = await fetch(createPageRequest);
|
|
196
|
-
if (createPageResponse.status !== 201) {
|
|
197
|
-
console.error(`Failed to create test page: ${createPageResponse.status}`);
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
// Now bulk delete all resources
|
|
201
|
-
const deleteRequest = new Request('http://example.com/resources', {
|
|
202
|
-
method: 'DELETE',
|
|
203
|
-
});
|
|
204
|
-
const deleteResponse = await fetch(deleteRequest);
|
|
205
|
-
// Skip test if in production mode (403)
|
|
206
|
-
if (deleteResponse.status === 403) {
|
|
207
|
-
// Clean up the page we created
|
|
208
|
-
await fetch(new Request(`http://example.com/pages/${encodeURIComponent(pageSlug)}`, { method: 'DELETE' }));
|
|
209
|
-
console.log('Skipping deleteAllPreservesPages test - bulk delete not available in production');
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
if (deleteResponse.status !== 200) {
|
|
213
|
-
console.error(`DELETE /resources returned ${deleteResponse.status}, expected 200`);
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
// Verify the page still exists
|
|
217
|
-
const getPageRequest = new Request(`http://example.com/pages/${encodeURIComponent(pageSlug)}`, { method: 'GET' });
|
|
218
|
-
const getPageResponse = await fetch(getPageRequest);
|
|
219
|
-
if (getPageResponse.status === 404) {
|
|
220
|
-
console.error('DELETE /resources incorrectly deleted pages - page not found after bulk delete');
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
if (!getPageResponse.ok) {
|
|
224
|
-
console.error(`GET /pages/${pageSlug} returned ${getPageResponse.status}`);
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
// Clean up: delete the test page
|
|
228
|
-
await fetch(new Request(`http://example.com/pages/${encodeURIComponent(pageSlug)}`, { method: 'DELETE' }));
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
console.error(`Failed to test DELETE /resources preserves pages:`, error);
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
/**
|
|
237
|
-
* Test POST /resources/presigned-link - Generate presigned URL for upload
|
|
238
|
-
*/
|
|
239
|
-
async generatePresignedLink(fetch) {
|
|
240
|
-
try {
|
|
241
|
-
const request = new Request('http://example.com/resources/presigned-link', {
|
|
242
|
-
method: 'POST',
|
|
243
|
-
headers: {
|
|
244
|
-
'Content-Type': 'application/json',
|
|
245
|
-
},
|
|
246
|
-
body: JSON.stringify({
|
|
247
|
-
key: 'test-files/large-video.mp4',
|
|
248
|
-
type: 'binary',
|
|
249
|
-
size: 150 * 1024 * 1024, // 150MB - larger than Worker limit
|
|
250
|
-
}),
|
|
251
|
-
});
|
|
252
|
-
const response = await fetch(request);
|
|
253
|
-
// If credentials are not configured, expect 400
|
|
254
|
-
if (response.status === 400) {
|
|
255
|
-
const data = await response.json();
|
|
256
|
-
// This is acceptable - implementation may not have credentials configured
|
|
257
|
-
console.log('Presigned URL generation not available - this is acceptable');
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
if (response.status !== 200) {
|
|
261
|
-
console.error(`POST /resources/presigned-link returned ${response.status}, expected 200 or 400`);
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
const data = await response.json();
|
|
265
|
-
// Validate response structure (backend-agnostic)
|
|
266
|
-
if (!data.url || typeof data.url !== 'string') {
|
|
267
|
-
console.error(`Expected url to be string, got ${typeof data.url}`);
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
if (!data.method || data.method !== 'PUT') {
|
|
271
|
-
console.error(`Expected method to be 'PUT', got ${data.method}`);
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
if (typeof data.expiresIn !== 'number' || data.expiresIn <= 0) {
|
|
275
|
-
console.error(`Expected expiresIn to be positive number, got ${data.expiresIn}`);
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
// Basic URL validation - just ensure it's a valid URL
|
|
279
|
-
try {
|
|
280
|
-
new URL(data.url);
|
|
281
|
-
console.log('Presigned URL structure validated successfully');
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
console.error(`Invalid URL returned: ${data.url}`);
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
return true;
|
|
288
|
-
}
|
|
289
|
-
catch (error) {
|
|
290
|
-
console.error(`Failed to test POST /resources/presigned-link:`, error);
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
};
|
|
295
2
|
export const brains = {
|
|
296
3
|
/**
|
|
297
4
|
* Test POST /brains/runs - Create a new brain run
|
|
@@ -314,7 +21,7 @@ export const brains = {
|
|
|
314
21
|
console.error(`POST /brains/runs returned ${response.status}, expected 201`);
|
|
315
22
|
return null;
|
|
316
23
|
}
|
|
317
|
-
const data = await response.json();
|
|
24
|
+
const data = (await response.json());
|
|
318
25
|
if (!data.brainRunId || typeof data.brainRunId !== 'string') {
|
|
319
26
|
console.error(`Expected brainRunId to be string, got ${typeof data.brainRunId}`);
|
|
320
27
|
return null;
|
|
@@ -343,7 +50,7 @@ export const brains = {
|
|
|
343
50
|
console.error(`POST /brains/runs with options returned ${response.status}, expected 201`);
|
|
344
51
|
return null;
|
|
345
52
|
}
|
|
346
|
-
const data = await response.json();
|
|
53
|
+
const data = (await response.json());
|
|
347
54
|
if (!data.brainRunId || typeof data.brainRunId !== 'string') {
|
|
348
55
|
console.error(`Expected brainRunId to be string, got ${typeof data.brainRunId}`);
|
|
349
56
|
return null;
|
|
@@ -372,7 +79,7 @@ export const brains = {
|
|
|
372
79
|
console.error(`POST /brains/runs with non-existent brain returned ${response.status}, expected 404`);
|
|
373
80
|
return false;
|
|
374
81
|
}
|
|
375
|
-
const data = await response.json();
|
|
82
|
+
const data = (await response.json());
|
|
376
83
|
if (!data.error || typeof data.error !== 'string') {
|
|
377
84
|
console.error(`Expected error to be string, got ${typeof data.error}`);
|
|
378
85
|
return false;
|
|
@@ -458,7 +165,7 @@ export const brains = {
|
|
|
458
165
|
console.error(`GET /brains/${identifier}/history returned ${response.status}`);
|
|
459
166
|
return false;
|
|
460
167
|
}
|
|
461
|
-
const data = await response.json();
|
|
168
|
+
const data = (await response.json());
|
|
462
169
|
// Validate response structure
|
|
463
170
|
if (!data.runs || !Array.isArray(data.runs)) {
|
|
464
171
|
console.error(`Expected runs to be an array, got ${typeof data.runs}`);
|
|
@@ -541,7 +248,7 @@ export const brains = {
|
|
|
541
248
|
console.error(`GET /brains returned ${response.status}`);
|
|
542
249
|
return false;
|
|
543
250
|
}
|
|
544
|
-
const data = await response.json();
|
|
251
|
+
const data = (await response.json());
|
|
545
252
|
// Validate response structure
|
|
546
253
|
if (!Array.isArray(data.brains)) {
|
|
547
254
|
console.error(`Expected brains to be an array, got ${typeof data.brains}`);
|
|
@@ -588,7 +295,7 @@ export const brains = {
|
|
|
588
295
|
console.error(`GET /brains?q=${query} returned ${response.status}`);
|
|
589
296
|
return null;
|
|
590
297
|
}
|
|
591
|
-
const data = await response.json();
|
|
298
|
+
const data = (await response.json());
|
|
592
299
|
// Validate response structure
|
|
593
300
|
if (!Array.isArray(data.brains)) {
|
|
594
301
|
console.error(`Expected brains to be an array, got ${typeof data.brains}`);
|
|
@@ -634,7 +341,7 @@ export const brains = {
|
|
|
634
341
|
console.error(`GET /brains/${identifier} returned ${response.status}`);
|
|
635
342
|
return false;
|
|
636
343
|
}
|
|
637
|
-
const data = await response.json();
|
|
344
|
+
const data = (await response.json());
|
|
638
345
|
// Validate response structure
|
|
639
346
|
if (!data.title || typeof data.title !== 'string') {
|
|
640
347
|
console.error(`Expected title to be string, got ${typeof data.title}`);
|
|
@@ -687,7 +394,7 @@ export const brains = {
|
|
|
687
394
|
console.error(`GET /brains/${identifier}/active-runs returned ${response.status}`);
|
|
688
395
|
return false;
|
|
689
396
|
}
|
|
690
|
-
const data = await response.json();
|
|
397
|
+
const data = (await response.json());
|
|
691
398
|
// Validate response structure
|
|
692
399
|
if (!data.runs || !Array.isArray(data.runs)) {
|
|
693
400
|
console.error(`Expected runs to be an array, got ${typeof data.runs}`);
|
|
@@ -804,7 +511,7 @@ export const brains = {
|
|
|
804
511
|
console.error(`GET /brains/${ambiguousIdentifier} with ambiguous identifier returned ${response.status}, expected 300`);
|
|
805
512
|
return false;
|
|
806
513
|
}
|
|
807
|
-
const data = await response.json();
|
|
514
|
+
const data = (await response.json());
|
|
808
515
|
if (data.matchType !== 'multiple') {
|
|
809
516
|
console.error(`Expected matchType to be 'multiple', got ${data.matchType}`);
|
|
810
517
|
return false;
|
|
@@ -838,7 +545,7 @@ export const brains = {
|
|
|
838
545
|
console.error(`POST /brains/runs with ambiguous identifier returned ${response.status}, expected 300 or 409`);
|
|
839
546
|
return false;
|
|
840
547
|
}
|
|
841
|
-
const data = await response.json();
|
|
548
|
+
const data = (await response.json());
|
|
842
549
|
if (data.matchType !== 'multiple') {
|
|
843
550
|
console.error(`Expected matchType to be 'multiple', got ${data.matchType}`);
|
|
844
551
|
return false;
|
|
@@ -885,7 +592,7 @@ export const brains = {
|
|
|
885
592
|
console.error(`POST /brains/runs/rerun returned ${response.status}, expected 201`);
|
|
886
593
|
return null;
|
|
887
594
|
}
|
|
888
|
-
const data = await response.json();
|
|
595
|
+
const data = (await response.json());
|
|
889
596
|
if (!data.brainRunId || typeof data.brainRunId !== 'string') {
|
|
890
597
|
console.error(`Expected brainRunId to be string, got ${typeof data.brainRunId}`);
|
|
891
598
|
return null;
|
|
@@ -1128,7 +835,8 @@ export const brains = {
|
|
|
1128
835
|
}
|
|
1129
836
|
// Verify AGENT_START has prompt field
|
|
1130
837
|
const agentStartEvent = events.find((e) => e.type === BRAIN_EVENTS.AGENT_START);
|
|
1131
|
-
if (!agentStartEvent.prompt ||
|
|
838
|
+
if (!agentStartEvent.prompt ||
|
|
839
|
+
typeof agentStartEvent.prompt !== 'string') {
|
|
1132
840
|
console.error('AGENT_START event missing prompt field');
|
|
1133
841
|
return false;
|
|
1134
842
|
}
|
|
@@ -1437,9 +1145,10 @@ export const brains = {
|
|
|
1437
1145
|
console.error(`Run ${brainRunId} not found in history`);
|
|
1438
1146
|
return false;
|
|
1439
1147
|
}
|
|
1440
|
-
// KEY ASSERTION: Status should be
|
|
1441
|
-
|
|
1442
|
-
|
|
1148
|
+
// KEY ASSERTION: Status should be WAITING (for webhook) despite inner brain COMPLETE event
|
|
1149
|
+
// The outer brain emitted a WEBHOOK event, so it should be WAITING, not COMPLETE
|
|
1150
|
+
if (ourRun.status !== STATUS.WAITING) {
|
|
1151
|
+
console.error(`Expected status '${STATUS.WAITING}' but got '${ourRun.status}'. ` +
|
|
1443
1152
|
`Bug: Inner brain COMPLETE event overwrote outer brain status!`);
|
|
1444
1153
|
return false;
|
|
1445
1154
|
}
|
|
@@ -1527,929 +1236,4 @@ export const brains = {
|
|
|
1527
1236
|
}
|
|
1528
1237
|
},
|
|
1529
1238
|
};
|
|
1530
|
-
|
|
1531
|
-
/**
|
|
1532
|
-
* Test POST /brains/schedules - Create a new schedule
|
|
1533
|
-
*/
|
|
1534
|
-
async create(fetch, identifier, cronExpression) {
|
|
1535
|
-
try {
|
|
1536
|
-
const request = new Request('http://example.com/brains/schedules', {
|
|
1537
|
-
method: 'POST',
|
|
1538
|
-
headers: {
|
|
1539
|
-
'Content-Type': 'application/json',
|
|
1540
|
-
},
|
|
1541
|
-
body: JSON.stringify({ identifier, cronExpression }),
|
|
1542
|
-
});
|
|
1543
|
-
const response = await fetch(request);
|
|
1544
|
-
if (response.status !== 201) {
|
|
1545
|
-
console.error(`POST /brains/schedules returned ${response.status}, expected 201`);
|
|
1546
|
-
return null;
|
|
1547
|
-
}
|
|
1548
|
-
const data = await response.json();
|
|
1549
|
-
// Validate response structure
|
|
1550
|
-
if (!data.id || typeof data.id !== 'string') {
|
|
1551
|
-
console.error(`Expected id to be string, got ${typeof data.id}`);
|
|
1552
|
-
return null;
|
|
1553
|
-
}
|
|
1554
|
-
// TODO: Once backend is updated, validate that the returned brain matches the identifier
|
|
1555
|
-
// For now, we accept any valid response
|
|
1556
|
-
if (data.cronExpression !== cronExpression) {
|
|
1557
|
-
console.error(`Expected cronExpression to be '${cronExpression}', got ${data.cronExpression}`);
|
|
1558
|
-
return null;
|
|
1559
|
-
}
|
|
1560
|
-
if (typeof data.enabled !== 'boolean') {
|
|
1561
|
-
console.error(`Expected enabled to be boolean, got ${typeof data.enabled}`);
|
|
1562
|
-
return null;
|
|
1563
|
-
}
|
|
1564
|
-
if (typeof data.createdAt !== 'number') {
|
|
1565
|
-
console.error(`Expected createdAt to be number, got ${typeof data.createdAt}`);
|
|
1566
|
-
return null;
|
|
1567
|
-
}
|
|
1568
|
-
return data.id;
|
|
1569
|
-
}
|
|
1570
|
-
catch (error) {
|
|
1571
|
-
console.error(`Failed to test POST /brains/schedules:`, error);
|
|
1572
|
-
return null;
|
|
1573
|
-
}
|
|
1574
|
-
},
|
|
1575
|
-
/**
|
|
1576
|
-
* Test GET /brains/schedules - List all schedules
|
|
1577
|
-
*/
|
|
1578
|
-
async list(fetch) {
|
|
1579
|
-
try {
|
|
1580
|
-
const request = new Request('http://example.com/brains/schedules', {
|
|
1581
|
-
method: 'GET',
|
|
1582
|
-
});
|
|
1583
|
-
const response = await fetch(request);
|
|
1584
|
-
if (!response.ok) {
|
|
1585
|
-
console.error(`GET /brains/schedules returned ${response.status}`);
|
|
1586
|
-
return false;
|
|
1587
|
-
}
|
|
1588
|
-
const data = await response.json();
|
|
1589
|
-
// Validate response structure
|
|
1590
|
-
if (!Array.isArray(data.schedules)) {
|
|
1591
|
-
console.error(`Expected schedules to be an array, got ${typeof data.schedules}`);
|
|
1592
|
-
return false;
|
|
1593
|
-
}
|
|
1594
|
-
if (typeof data.count !== 'number') {
|
|
1595
|
-
console.error(`Expected count to be number, got ${typeof data.count}`);
|
|
1596
|
-
return false;
|
|
1597
|
-
}
|
|
1598
|
-
// Validate each schedule has required fields
|
|
1599
|
-
for (const schedule of data.schedules) {
|
|
1600
|
-
if (!schedule.id ||
|
|
1601
|
-
!schedule.brainTitle ||
|
|
1602
|
-
!schedule.cronExpression ||
|
|
1603
|
-
typeof schedule.enabled !== 'boolean' ||
|
|
1604
|
-
typeof schedule.createdAt !== 'number') {
|
|
1605
|
-
console.error(`Schedule missing required fields: ${JSON.stringify(schedule)}`);
|
|
1606
|
-
return false;
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
return true;
|
|
1610
|
-
}
|
|
1611
|
-
catch (error) {
|
|
1612
|
-
console.error(`Failed to test GET /brains/schedules:`, error);
|
|
1613
|
-
return false;
|
|
1614
|
-
}
|
|
1615
|
-
},
|
|
1616
|
-
/**
|
|
1617
|
-
* Test DELETE /brains/schedules/:scheduleId - Delete a schedule
|
|
1618
|
-
*/
|
|
1619
|
-
async delete(fetch, scheduleId) {
|
|
1620
|
-
try {
|
|
1621
|
-
const request = new Request(`http://example.com/brains/schedules/${scheduleId}`, {
|
|
1622
|
-
method: 'DELETE',
|
|
1623
|
-
});
|
|
1624
|
-
const response = await fetch(request);
|
|
1625
|
-
if (response.status !== 204) {
|
|
1626
|
-
console.error(`DELETE /brains/schedules/${scheduleId} returned ${response.status}, expected 204`);
|
|
1627
|
-
return false;
|
|
1628
|
-
}
|
|
1629
|
-
return true;
|
|
1630
|
-
}
|
|
1631
|
-
catch (error) {
|
|
1632
|
-
console.error(`Failed to test DELETE /brains/schedules/${scheduleId}:`, error);
|
|
1633
|
-
return false;
|
|
1634
|
-
}
|
|
1635
|
-
},
|
|
1636
|
-
/**
|
|
1637
|
-
* Test GET /brains/schedules/runs - Get history of scheduled runs
|
|
1638
|
-
*/
|
|
1639
|
-
async runs(fetch, scheduleId, limit) {
|
|
1640
|
-
try {
|
|
1641
|
-
const url = new URL('http://example.com/brains/schedules/runs');
|
|
1642
|
-
if (scheduleId !== undefined) {
|
|
1643
|
-
url.searchParams.set('scheduleId', scheduleId);
|
|
1644
|
-
}
|
|
1645
|
-
if (limit !== undefined) {
|
|
1646
|
-
url.searchParams.set('limit', limit.toString());
|
|
1647
|
-
}
|
|
1648
|
-
const request = new Request(url.toString(), {
|
|
1649
|
-
method: 'GET',
|
|
1650
|
-
});
|
|
1651
|
-
const response = await fetch(request);
|
|
1652
|
-
if (!response.ok) {
|
|
1653
|
-
console.error(`GET /brains/schedules/runs returned ${response.status}`);
|
|
1654
|
-
return false;
|
|
1655
|
-
}
|
|
1656
|
-
const data = await response.json();
|
|
1657
|
-
// Validate response structure
|
|
1658
|
-
if (!Array.isArray(data.runs)) {
|
|
1659
|
-
console.error(`Expected runs to be an array, got ${typeof data.runs}`);
|
|
1660
|
-
return false;
|
|
1661
|
-
}
|
|
1662
|
-
if (typeof data.count !== 'number') {
|
|
1663
|
-
console.error(`Expected count to be number, got ${typeof data.count}`);
|
|
1664
|
-
return false;
|
|
1665
|
-
}
|
|
1666
|
-
// Validate each run has required fields
|
|
1667
|
-
for (const run of data.runs) {
|
|
1668
|
-
if (!run.id ||
|
|
1669
|
-
!run.scheduleId ||
|
|
1670
|
-
!run.status ||
|
|
1671
|
-
typeof run.ranAt !== 'number') {
|
|
1672
|
-
console.error(`Scheduled run missing required fields: ${JSON.stringify(run)}`);
|
|
1673
|
-
return false;
|
|
1674
|
-
}
|
|
1675
|
-
if (!['triggered', 'failed'].includes(run.status)) {
|
|
1676
|
-
console.error(`Invalid run status: ${run.status}`);
|
|
1677
|
-
return false;
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
return true;
|
|
1681
|
-
}
|
|
1682
|
-
catch (error) {
|
|
1683
|
-
console.error(`Failed to test GET /brains/schedules/runs:`, error);
|
|
1684
|
-
return false;
|
|
1685
|
-
}
|
|
1686
|
-
},
|
|
1687
|
-
};
|
|
1688
|
-
export const secrets = {
|
|
1689
|
-
/**
|
|
1690
|
-
* Test POST /secrets - Create or update a secret
|
|
1691
|
-
*/
|
|
1692
|
-
async create(fetch, name, value) {
|
|
1693
|
-
try {
|
|
1694
|
-
const request = new Request('http://example.com/secrets', {
|
|
1695
|
-
method: 'POST',
|
|
1696
|
-
headers: {
|
|
1697
|
-
'Content-Type': 'application/json',
|
|
1698
|
-
},
|
|
1699
|
-
body: JSON.stringify({ name, value }),
|
|
1700
|
-
});
|
|
1701
|
-
const response = await fetch(request);
|
|
1702
|
-
if (response.status !== 201) {
|
|
1703
|
-
console.error(`POST /secrets returned ${response.status}, expected 201`);
|
|
1704
|
-
return false;
|
|
1705
|
-
}
|
|
1706
|
-
const data = await response.json();
|
|
1707
|
-
// Validate response structure
|
|
1708
|
-
if (!data.name || typeof data.name !== 'string') {
|
|
1709
|
-
console.error(`Expected name to be string, got ${typeof data.name}`);
|
|
1710
|
-
return false;
|
|
1711
|
-
}
|
|
1712
|
-
if (data.name !== name) {
|
|
1713
|
-
console.error(`Expected name to be '${name}', got ${data.name}`);
|
|
1714
|
-
return false;
|
|
1715
|
-
}
|
|
1716
|
-
if (typeof data.createdAt !== 'string') {
|
|
1717
|
-
console.error(`Expected createdAt to be string, got ${typeof data.createdAt}`);
|
|
1718
|
-
return false;
|
|
1719
|
-
}
|
|
1720
|
-
if (typeof data.updatedAt !== 'string') {
|
|
1721
|
-
console.error(`Expected updatedAt to be string, got ${typeof data.updatedAt}`);
|
|
1722
|
-
return false;
|
|
1723
|
-
}
|
|
1724
|
-
return true;
|
|
1725
|
-
}
|
|
1726
|
-
catch (error) {
|
|
1727
|
-
console.error(`Failed to test POST /secrets:`, error);
|
|
1728
|
-
return false;
|
|
1729
|
-
}
|
|
1730
|
-
},
|
|
1731
|
-
/**
|
|
1732
|
-
* Test GET /secrets - List all secrets (names only, not values)
|
|
1733
|
-
*/
|
|
1734
|
-
async list(fetch) {
|
|
1735
|
-
try {
|
|
1736
|
-
const request = new Request('http://example.com/secrets', {
|
|
1737
|
-
method: 'GET',
|
|
1738
|
-
});
|
|
1739
|
-
const response = await fetch(request);
|
|
1740
|
-
if (!response.ok) {
|
|
1741
|
-
console.error(`GET /secrets returned ${response.status}`);
|
|
1742
|
-
return false;
|
|
1743
|
-
}
|
|
1744
|
-
const data = await response.json();
|
|
1745
|
-
// Validate response structure
|
|
1746
|
-
if (!Array.isArray(data.secrets)) {
|
|
1747
|
-
console.error(`Expected secrets to be an array, got ${typeof data.secrets}`);
|
|
1748
|
-
return false;
|
|
1749
|
-
}
|
|
1750
|
-
if (typeof data.count !== 'number') {
|
|
1751
|
-
console.error(`Expected count to be number, got ${typeof data.count}`);
|
|
1752
|
-
return false;
|
|
1753
|
-
}
|
|
1754
|
-
// Validate each secret has required fields (but NOT the value)
|
|
1755
|
-
for (const secret of data.secrets) {
|
|
1756
|
-
if (!secret.name ||
|
|
1757
|
-
typeof secret.name !== 'string' ||
|
|
1758
|
-
typeof secret.createdAt !== 'string' ||
|
|
1759
|
-
typeof secret.updatedAt !== 'string') {
|
|
1760
|
-
console.error(`Secret missing required fields: ${JSON.stringify(secret)}`);
|
|
1761
|
-
return false;
|
|
1762
|
-
}
|
|
1763
|
-
// Ensure value is NOT included
|
|
1764
|
-
if ('value' in secret) {
|
|
1765
|
-
console.error(`Secret should not include value field: ${JSON.stringify(secret)}`);
|
|
1766
|
-
return false;
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
return true;
|
|
1770
|
-
}
|
|
1771
|
-
catch (error) {
|
|
1772
|
-
console.error(`Failed to test GET /secrets:`, error);
|
|
1773
|
-
return false;
|
|
1774
|
-
}
|
|
1775
|
-
},
|
|
1776
|
-
/**
|
|
1777
|
-
* Test DELETE /secrets/:name - Delete a specific secret
|
|
1778
|
-
*/
|
|
1779
|
-
async delete(fetch, name) {
|
|
1780
|
-
try {
|
|
1781
|
-
const request = new Request(`http://example.com/secrets/${encodeURIComponent(name)}`, {
|
|
1782
|
-
method: 'DELETE',
|
|
1783
|
-
});
|
|
1784
|
-
const response = await fetch(request);
|
|
1785
|
-
if (response.status !== 204) {
|
|
1786
|
-
console.error(`DELETE /secrets/${name} returned ${response.status}, expected 204`);
|
|
1787
|
-
return false;
|
|
1788
|
-
}
|
|
1789
|
-
return true;
|
|
1790
|
-
}
|
|
1791
|
-
catch (error) {
|
|
1792
|
-
console.error(`Failed to test DELETE /secrets/${name}:`, error);
|
|
1793
|
-
return false;
|
|
1794
|
-
}
|
|
1795
|
-
},
|
|
1796
|
-
/**
|
|
1797
|
-
* Test GET /secrets/:name/exists - Check if a secret exists
|
|
1798
|
-
*/
|
|
1799
|
-
async exists(fetch, name) {
|
|
1800
|
-
try {
|
|
1801
|
-
const request = new Request(`http://example.com/secrets/${encodeURIComponent(name)}/exists`, {
|
|
1802
|
-
method: 'GET',
|
|
1803
|
-
});
|
|
1804
|
-
const response = await fetch(request);
|
|
1805
|
-
if (!response.ok) {
|
|
1806
|
-
console.error(`GET /secrets/${name}/exists returned ${response.status}`);
|
|
1807
|
-
return false;
|
|
1808
|
-
}
|
|
1809
|
-
const data = await response.json();
|
|
1810
|
-
// Validate response structure
|
|
1811
|
-
if (typeof data.exists !== 'boolean') {
|
|
1812
|
-
console.error(`Expected exists to be boolean, got ${typeof data.exists}`);
|
|
1813
|
-
return false;
|
|
1814
|
-
}
|
|
1815
|
-
return true;
|
|
1816
|
-
}
|
|
1817
|
-
catch (error) {
|
|
1818
|
-
console.error(`Failed to test GET /secrets/${name}/exists:`, error);
|
|
1819
|
-
return false;
|
|
1820
|
-
}
|
|
1821
|
-
},
|
|
1822
|
-
/**
|
|
1823
|
-
* Test POST /secrets/bulk - Create multiple secrets
|
|
1824
|
-
*/
|
|
1825
|
-
async bulk(fetch, secrets) {
|
|
1826
|
-
try {
|
|
1827
|
-
const request = new Request('http://example.com/secrets/bulk', {
|
|
1828
|
-
method: 'POST',
|
|
1829
|
-
headers: {
|
|
1830
|
-
'Content-Type': 'application/json',
|
|
1831
|
-
},
|
|
1832
|
-
body: JSON.stringify({ secrets }),
|
|
1833
|
-
});
|
|
1834
|
-
const response = await fetch(request);
|
|
1835
|
-
if (response.status !== 201) {
|
|
1836
|
-
console.error(`POST /secrets/bulk returned ${response.status}, expected 201`);
|
|
1837
|
-
return false;
|
|
1838
|
-
}
|
|
1839
|
-
const data = await response.json();
|
|
1840
|
-
// Validate response structure
|
|
1841
|
-
if (typeof data.created !== 'number') {
|
|
1842
|
-
console.error(`Expected created to be number, got ${typeof data.created}`);
|
|
1843
|
-
return false;
|
|
1844
|
-
}
|
|
1845
|
-
if (typeof data.updated !== 'number') {
|
|
1846
|
-
console.error(`Expected updated to be number, got ${typeof data.updated}`);
|
|
1847
|
-
return false;
|
|
1848
|
-
}
|
|
1849
|
-
// Total should match input
|
|
1850
|
-
if (data.created + data.updated !== secrets.length) {
|
|
1851
|
-
console.error(`Expected total (${data.created + data.updated}) to match input length (${secrets.length})`);
|
|
1852
|
-
return false;
|
|
1853
|
-
}
|
|
1854
|
-
return true;
|
|
1855
|
-
}
|
|
1856
|
-
catch (error) {
|
|
1857
|
-
console.error(`Failed to test POST /secrets/bulk:`, error);
|
|
1858
|
-
return false;
|
|
1859
|
-
}
|
|
1860
|
-
},
|
|
1861
|
-
};
|
|
1862
|
-
export const webhooks = {
|
|
1863
|
-
/**
|
|
1864
|
-
* Test GET /webhooks - List all available webhook handlers
|
|
1865
|
-
*/
|
|
1866
|
-
async list(fetch) {
|
|
1867
|
-
try {
|
|
1868
|
-
const request = new Request('http://example.com/webhooks', {
|
|
1869
|
-
method: 'GET',
|
|
1870
|
-
});
|
|
1871
|
-
const response = await fetch(request);
|
|
1872
|
-
if (!response.ok) {
|
|
1873
|
-
console.error(`GET /webhooks returned ${response.status}`);
|
|
1874
|
-
return false;
|
|
1875
|
-
}
|
|
1876
|
-
const data = await response.json();
|
|
1877
|
-
// Validate response structure
|
|
1878
|
-
if (!Array.isArray(data.webhooks)) {
|
|
1879
|
-
console.error(`Expected webhooks to be an array, got ${typeof data.webhooks}`);
|
|
1880
|
-
return false;
|
|
1881
|
-
}
|
|
1882
|
-
if (typeof data.count !== 'number') {
|
|
1883
|
-
console.error(`Expected count to be number, got ${typeof data.count}`);
|
|
1884
|
-
return false;
|
|
1885
|
-
}
|
|
1886
|
-
// Validate each webhook has required fields
|
|
1887
|
-
for (const webhook of data.webhooks) {
|
|
1888
|
-
if (!webhook.slug || typeof webhook.slug !== 'string') {
|
|
1889
|
-
console.error(`Webhook missing slug or has invalid type: ${JSON.stringify(webhook)}`);
|
|
1890
|
-
return false;
|
|
1891
|
-
}
|
|
1892
|
-
// Description is optional
|
|
1893
|
-
if (webhook.description !== undefined &&
|
|
1894
|
-
typeof webhook.description !== 'string') {
|
|
1895
|
-
console.error(`Webhook description has invalid type: ${JSON.stringify(webhook)}`);
|
|
1896
|
-
return false;
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
return true;
|
|
1900
|
-
}
|
|
1901
|
-
catch (error) {
|
|
1902
|
-
console.error(`Failed to test GET /webhooks:`, error);
|
|
1903
|
-
return false;
|
|
1904
|
-
}
|
|
1905
|
-
},
|
|
1906
|
-
/**
|
|
1907
|
-
* Test POST /webhooks/:slug - Receive an incoming webhook from an external service
|
|
1908
|
-
*/
|
|
1909
|
-
async receive(fetch, slug, payload) {
|
|
1910
|
-
try {
|
|
1911
|
-
const request = new Request(`http://example.com/webhooks/${encodeURIComponent(slug)}`, {
|
|
1912
|
-
method: 'POST',
|
|
1913
|
-
headers: {
|
|
1914
|
-
'Content-Type': 'application/json',
|
|
1915
|
-
},
|
|
1916
|
-
body: JSON.stringify(payload),
|
|
1917
|
-
});
|
|
1918
|
-
const response = await fetch(request);
|
|
1919
|
-
// Accept either 200 (OK) or 202 (Accepted)
|
|
1920
|
-
if (response.status !== 200 && response.status !== 202) {
|
|
1921
|
-
console.error(`POST /webhooks/${slug} returned ${response.status}, expected 200 or 202`);
|
|
1922
|
-
return false;
|
|
1923
|
-
}
|
|
1924
|
-
const data = await response.json();
|
|
1925
|
-
// Validate response structure
|
|
1926
|
-
if (typeof data.received !== 'boolean') {
|
|
1927
|
-
console.error(`Expected received to be boolean, got ${typeof data.received}`);
|
|
1928
|
-
return false;
|
|
1929
|
-
}
|
|
1930
|
-
// Action field is optional
|
|
1931
|
-
if (data.action !== undefined &&
|
|
1932
|
-
!['resumed', 'started', 'queued', 'no-match'].includes(data.action)) {
|
|
1933
|
-
console.error(`Invalid action value: ${data.action}`);
|
|
1934
|
-
return false;
|
|
1935
|
-
}
|
|
1936
|
-
return true;
|
|
1937
|
-
}
|
|
1938
|
-
catch (error) {
|
|
1939
|
-
console.error(`Failed to test POST /webhooks/${slug}:`, error);
|
|
1940
|
-
return false;
|
|
1941
|
-
}
|
|
1942
|
-
},
|
|
1943
|
-
/**
|
|
1944
|
-
* Test POST /webhooks/:slug with non-existent webhook - Should return 404
|
|
1945
|
-
*/
|
|
1946
|
-
async notFound(fetch, slug) {
|
|
1947
|
-
try {
|
|
1948
|
-
const request = new Request(`http://example.com/webhooks/${encodeURIComponent(slug)}`, {
|
|
1949
|
-
method: 'POST',
|
|
1950
|
-
headers: {
|
|
1951
|
-
'Content-Type': 'application/json',
|
|
1952
|
-
},
|
|
1953
|
-
body: JSON.stringify({}),
|
|
1954
|
-
});
|
|
1955
|
-
const response = await fetch(request);
|
|
1956
|
-
if (response.status !== 404) {
|
|
1957
|
-
console.error(`POST /webhooks/${slug} with non-existent webhook returned ${response.status}, expected 404`);
|
|
1958
|
-
return false;
|
|
1959
|
-
}
|
|
1960
|
-
const data = await response.json();
|
|
1961
|
-
if (!data.error || typeof data.error !== 'string') {
|
|
1962
|
-
console.error(`Expected error to be string, got ${typeof data.error}`);
|
|
1963
|
-
return false;
|
|
1964
|
-
}
|
|
1965
|
-
// Verify error message mentions the webhook slug
|
|
1966
|
-
if (!data.error.toLowerCase().includes('webhook')) {
|
|
1967
|
-
console.error(`Expected error message to mention webhook, got: ${data.error}`);
|
|
1968
|
-
return false;
|
|
1969
|
-
}
|
|
1970
|
-
return true;
|
|
1971
|
-
}
|
|
1972
|
-
catch (error) {
|
|
1973
|
-
console.error(`Failed to test POST /webhooks/${slug} with non-existent webhook:`, error);
|
|
1974
|
-
return false;
|
|
1975
|
-
}
|
|
1976
|
-
},
|
|
1977
|
-
/**
|
|
1978
|
-
* Test POST /webhooks/system/ui-form - Built-in webhook for UI form submissions.
|
|
1979
|
-
* This is used by pages generated via .ui() steps to submit form data.
|
|
1980
|
-
*
|
|
1981
|
-
* The endpoint:
|
|
1982
|
-
* - Accepts form data (application/x-www-form-urlencoded or multipart/form-data)
|
|
1983
|
-
* - Requires an `identifier` query parameter to match the waiting brain
|
|
1984
|
-
* - Returns { received: true, action: 'resumed' | 'not_found', ... }
|
|
1985
|
-
*/
|
|
1986
|
-
async uiForm(fetch, identifier, formData) {
|
|
1987
|
-
try {
|
|
1988
|
-
// Build URLSearchParams from form data
|
|
1989
|
-
const params = new URLSearchParams();
|
|
1990
|
-
for (const [key, value] of Object.entries(formData)) {
|
|
1991
|
-
if (Array.isArray(value)) {
|
|
1992
|
-
for (const v of value) {
|
|
1993
|
-
params.append(`${key}[]`, v);
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
else {
|
|
1997
|
-
params.append(key, value);
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
const request = new Request(`http://example.com/webhooks/system/ui-form?identifier=${encodeURIComponent(identifier)}`, {
|
|
2001
|
-
method: 'POST',
|
|
2002
|
-
headers: {
|
|
2003
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
2004
|
-
},
|
|
2005
|
-
body: params.toString(),
|
|
2006
|
-
});
|
|
2007
|
-
const response = await fetch(request);
|
|
2008
|
-
// Accept 200 (found and processed) or 404 (no brain waiting)
|
|
2009
|
-
if (response.status !== 200 && response.status !== 404) {
|
|
2010
|
-
console.error(`POST /webhooks/system/ui-form returned ${response.status}, expected 200 or 404`);
|
|
2011
|
-
return false;
|
|
2012
|
-
}
|
|
2013
|
-
const data = (await response.json());
|
|
2014
|
-
// Validate response structure
|
|
2015
|
-
if (typeof data.received !== 'boolean') {
|
|
2016
|
-
console.error(`Expected received to be boolean, got ${typeof data.received}`);
|
|
2017
|
-
return false;
|
|
2018
|
-
}
|
|
2019
|
-
if (!data.action || typeof data.action !== 'string') {
|
|
2020
|
-
console.error(`Expected action to be string, got ${typeof data.action}`);
|
|
2021
|
-
return false;
|
|
2022
|
-
}
|
|
2023
|
-
// Action should be 'resumed' or 'not_found'
|
|
2024
|
-
if (data.action !== 'resumed' && data.action !== 'not_found') {
|
|
2025
|
-
console.error(`Expected action to be 'resumed' or 'not_found', got '${data.action}'`);
|
|
2026
|
-
return false;
|
|
2027
|
-
}
|
|
2028
|
-
return true;
|
|
2029
|
-
}
|
|
2030
|
-
catch (error) {
|
|
2031
|
-
console.error('Failed to test POST /webhooks/system/ui-form:', error);
|
|
2032
|
-
return false;
|
|
2033
|
-
}
|
|
2034
|
-
},
|
|
2035
|
-
/**
|
|
2036
|
-
* Test POST /webhooks/system/ui-form with missing identifier - Should return 400
|
|
2037
|
-
*/
|
|
2038
|
-
async uiFormMissingIdentifier(fetch) {
|
|
2039
|
-
try {
|
|
2040
|
-
const request = new Request('http://example.com/webhooks/system/ui-form', {
|
|
2041
|
-
method: 'POST',
|
|
2042
|
-
headers: {
|
|
2043
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
2044
|
-
},
|
|
2045
|
-
body: 'test=data',
|
|
2046
|
-
});
|
|
2047
|
-
const response = await fetch(request);
|
|
2048
|
-
if (response.status !== 400) {
|
|
2049
|
-
console.error(`POST /webhooks/system/ui-form without identifier returned ${response.status}, expected 400`);
|
|
2050
|
-
return false;
|
|
2051
|
-
}
|
|
2052
|
-
const data = (await response.json());
|
|
2053
|
-
if (!data.error || typeof data.error !== 'string') {
|
|
2054
|
-
console.error(`Expected error to be string, got ${typeof data.error}`);
|
|
2055
|
-
return false;
|
|
2056
|
-
}
|
|
2057
|
-
if (!data.error.toLowerCase().includes('identifier')) {
|
|
2058
|
-
console.error(`Expected error message to mention identifier, got: ${data.error}`);
|
|
2059
|
-
return false;
|
|
2060
|
-
}
|
|
2061
|
-
return true;
|
|
2062
|
-
}
|
|
2063
|
-
catch (error) {
|
|
2064
|
-
console.error('Failed to test POST /webhooks/system/ui-form without identifier:', error);
|
|
2065
|
-
return false;
|
|
2066
|
-
}
|
|
2067
|
-
},
|
|
2068
|
-
};
|
|
2069
|
-
export const pages = {
|
|
2070
|
-
/**
|
|
2071
|
-
* Test GET /pages - List all pages
|
|
2072
|
-
*/
|
|
2073
|
-
async list(fetch) {
|
|
2074
|
-
try {
|
|
2075
|
-
const request = new Request('http://example.com/pages', {
|
|
2076
|
-
method: 'GET',
|
|
2077
|
-
});
|
|
2078
|
-
const response = await fetch(request);
|
|
2079
|
-
if (!response.ok) {
|
|
2080
|
-
console.error(`GET /pages returned ${response.status}`);
|
|
2081
|
-
return false;
|
|
2082
|
-
}
|
|
2083
|
-
const data = (await response.json());
|
|
2084
|
-
// Validate response structure
|
|
2085
|
-
if (!Array.isArray(data.pages)) {
|
|
2086
|
-
console.error(`Expected pages to be an array, got ${typeof data.pages}`);
|
|
2087
|
-
return false;
|
|
2088
|
-
}
|
|
2089
|
-
if (typeof data.count !== 'number') {
|
|
2090
|
-
console.error(`Expected count to be number, got ${typeof data.count}`);
|
|
2091
|
-
return false;
|
|
2092
|
-
}
|
|
2093
|
-
// Validate each page has required fields
|
|
2094
|
-
for (const page of data.pages) {
|
|
2095
|
-
if (!page.slug ||
|
|
2096
|
-
typeof page.slug !== 'string' ||
|
|
2097
|
-
!page.url ||
|
|
2098
|
-
typeof page.url !== 'string' ||
|
|
2099
|
-
!page.brainRunId ||
|
|
2100
|
-
typeof page.brainRunId !== 'string' ||
|
|
2101
|
-
typeof page.persist !== 'boolean' ||
|
|
2102
|
-
!page.createdAt ||
|
|
2103
|
-
typeof page.createdAt !== 'string' ||
|
|
2104
|
-
typeof page.size !== 'number') {
|
|
2105
|
-
console.error(`Page missing required fields or has invalid types: ${JSON.stringify(page)}`);
|
|
2106
|
-
return false;
|
|
2107
|
-
}
|
|
2108
|
-
// ttl is optional
|
|
2109
|
-
if (page.ttl !== undefined && typeof page.ttl !== 'number') {
|
|
2110
|
-
console.error(`Page ttl has invalid type: ${typeof page.ttl}`);
|
|
2111
|
-
return false;
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
return true;
|
|
2115
|
-
}
|
|
2116
|
-
catch (error) {
|
|
2117
|
-
console.error(`Failed to test GET /pages:`, error);
|
|
2118
|
-
return false;
|
|
2119
|
-
}
|
|
2120
|
-
},
|
|
2121
|
-
/**
|
|
2122
|
-
* Test POST /pages - Create a new page
|
|
2123
|
-
*/
|
|
2124
|
-
async create(fetch, slug, html, brainRunId, options) {
|
|
2125
|
-
try {
|
|
2126
|
-
const body = { slug, html, brainRunId };
|
|
2127
|
-
if (options?.persist !== undefined) {
|
|
2128
|
-
body.persist = options.persist;
|
|
2129
|
-
}
|
|
2130
|
-
if (options?.ttl !== undefined) {
|
|
2131
|
-
body.ttl = options.ttl;
|
|
2132
|
-
}
|
|
2133
|
-
const request = new Request('http://example.com/pages', {
|
|
2134
|
-
method: 'POST',
|
|
2135
|
-
headers: {
|
|
2136
|
-
'Content-Type': 'application/json',
|
|
2137
|
-
},
|
|
2138
|
-
body: JSON.stringify(body),
|
|
2139
|
-
});
|
|
2140
|
-
const response = await fetch(request);
|
|
2141
|
-
if (response.status !== 201) {
|
|
2142
|
-
console.error(`POST /pages returned ${response.status}, expected 201`);
|
|
2143
|
-
return null;
|
|
2144
|
-
}
|
|
2145
|
-
const data = (await response.json());
|
|
2146
|
-
// Validate response structure
|
|
2147
|
-
if (!data.slug || typeof data.slug !== 'string') {
|
|
2148
|
-
console.error(`Expected slug to be string, got ${typeof data.slug}`);
|
|
2149
|
-
return null;
|
|
2150
|
-
}
|
|
2151
|
-
if (data.slug !== slug) {
|
|
2152
|
-
console.error(`Expected slug to be '${slug}', got ${data.slug}`);
|
|
2153
|
-
return null;
|
|
2154
|
-
}
|
|
2155
|
-
if (!data.url || typeof data.url !== 'string') {
|
|
2156
|
-
console.error(`Expected url to be string, got ${typeof data.url}`);
|
|
2157
|
-
return null;
|
|
2158
|
-
}
|
|
2159
|
-
if (!data.brainRunId || typeof data.brainRunId !== 'string') {
|
|
2160
|
-
console.error(`Expected brainRunId to be string, got ${typeof data.brainRunId}`);
|
|
2161
|
-
return null;
|
|
2162
|
-
}
|
|
2163
|
-
if (typeof data.persist !== 'boolean') {
|
|
2164
|
-
console.error(`Expected persist to be boolean, got ${typeof data.persist}`);
|
|
2165
|
-
return null;
|
|
2166
|
-
}
|
|
2167
|
-
if (!data.createdAt || typeof data.createdAt !== 'string') {
|
|
2168
|
-
console.error(`Expected createdAt to be string, got ${typeof data.createdAt}`);
|
|
2169
|
-
return null;
|
|
2170
|
-
}
|
|
2171
|
-
return data.slug;
|
|
2172
|
-
}
|
|
2173
|
-
catch (error) {
|
|
2174
|
-
console.error(`Failed to test POST /pages:`, error);
|
|
2175
|
-
return null;
|
|
2176
|
-
}
|
|
2177
|
-
},
|
|
2178
|
-
/**
|
|
2179
|
-
* Test GET /pages/:slug - Get page HTML content
|
|
2180
|
-
*/
|
|
2181
|
-
async get(fetch, slug) {
|
|
2182
|
-
try {
|
|
2183
|
-
const request = new Request(`http://example.com/pages/${encodeURIComponent(slug)}`, {
|
|
2184
|
-
method: 'GET',
|
|
2185
|
-
});
|
|
2186
|
-
const response = await fetch(request);
|
|
2187
|
-
if (response.status === 404) {
|
|
2188
|
-
return null;
|
|
2189
|
-
}
|
|
2190
|
-
if (!response.ok) {
|
|
2191
|
-
console.error(`GET /pages/${slug} returned ${response.status}`);
|
|
2192
|
-
return null;
|
|
2193
|
-
}
|
|
2194
|
-
// Check content type is HTML
|
|
2195
|
-
const contentType = response.headers.get('content-type');
|
|
2196
|
-
if (!contentType || !contentType.includes('text/html')) {
|
|
2197
|
-
console.error(`Expected content-type to be text/html, got ${contentType}`);
|
|
2198
|
-
return null;
|
|
2199
|
-
}
|
|
2200
|
-
const html = await response.text();
|
|
2201
|
-
return html;
|
|
2202
|
-
}
|
|
2203
|
-
catch (error) {
|
|
2204
|
-
console.error(`Failed to test GET /pages/${slug}:`, error);
|
|
2205
|
-
return null;
|
|
2206
|
-
}
|
|
2207
|
-
},
|
|
2208
|
-
/**
|
|
2209
|
-
* Test GET /pages/:slug/meta - Get page metadata
|
|
2210
|
-
*/
|
|
2211
|
-
async getMeta(fetch, slug) {
|
|
2212
|
-
try {
|
|
2213
|
-
const request = new Request(`http://example.com/pages/${encodeURIComponent(slug)}/meta`, {
|
|
2214
|
-
method: 'GET',
|
|
2215
|
-
});
|
|
2216
|
-
const response = await fetch(request);
|
|
2217
|
-
if (response.status === 404) {
|
|
2218
|
-
return null;
|
|
2219
|
-
}
|
|
2220
|
-
if (!response.ok) {
|
|
2221
|
-
console.error(`GET /pages/${slug}/meta returned ${response.status}`);
|
|
2222
|
-
return null;
|
|
2223
|
-
}
|
|
2224
|
-
const data = (await response.json());
|
|
2225
|
-
// Validate response structure
|
|
2226
|
-
if (!data.slug ||
|
|
2227
|
-
typeof data.slug !== 'string' ||
|
|
2228
|
-
!data.brainRunId ||
|
|
2229
|
-
typeof data.brainRunId !== 'string' ||
|
|
2230
|
-
typeof data.persist !== 'boolean' ||
|
|
2231
|
-
!data.createdAt ||
|
|
2232
|
-
typeof data.createdAt !== 'string' ||
|
|
2233
|
-
typeof data.size !== 'number') {
|
|
2234
|
-
console.error(`Page metadata missing required fields: ${JSON.stringify(data)}`);
|
|
2235
|
-
return null;
|
|
2236
|
-
}
|
|
2237
|
-
return data;
|
|
2238
|
-
}
|
|
2239
|
-
catch (error) {
|
|
2240
|
-
console.error(`Failed to test GET /pages/${slug}/meta:`, error);
|
|
2241
|
-
return null;
|
|
2242
|
-
}
|
|
2243
|
-
},
|
|
2244
|
-
/**
|
|
2245
|
-
* Test PUT /pages/:slug - Update page HTML content
|
|
2246
|
-
*/
|
|
2247
|
-
async update(fetch, slug, html) {
|
|
2248
|
-
try {
|
|
2249
|
-
const request = new Request(`http://example.com/pages/${encodeURIComponent(slug)}`, {
|
|
2250
|
-
method: 'PUT',
|
|
2251
|
-
headers: {
|
|
2252
|
-
'Content-Type': 'application/json',
|
|
2253
|
-
},
|
|
2254
|
-
body: JSON.stringify({ html }),
|
|
2255
|
-
});
|
|
2256
|
-
const response = await fetch(request);
|
|
2257
|
-
if (response.status === 404) {
|
|
2258
|
-
console.error(`PUT /pages/${slug} returned 404 - page not found`);
|
|
2259
|
-
return false;
|
|
2260
|
-
}
|
|
2261
|
-
if (!response.ok) {
|
|
2262
|
-
console.error(`PUT /pages/${slug} returned ${response.status}`);
|
|
2263
|
-
return false;
|
|
2264
|
-
}
|
|
2265
|
-
const data = (await response.json());
|
|
2266
|
-
// Validate response structure
|
|
2267
|
-
if (!data.slug || data.slug !== slug) {
|
|
2268
|
-
console.error(`Expected slug to be '${slug}', got ${data.slug}`);
|
|
2269
|
-
return false;
|
|
2270
|
-
}
|
|
2271
|
-
if (!data.url || typeof data.url !== 'string') {
|
|
2272
|
-
console.error(`Expected url to be string, got ${typeof data.url}`);
|
|
2273
|
-
return false;
|
|
2274
|
-
}
|
|
2275
|
-
if (!data.updatedAt || typeof data.updatedAt !== 'string') {
|
|
2276
|
-
console.error(`Expected updatedAt to be string, got ${typeof data.updatedAt}`);
|
|
2277
|
-
return false;
|
|
2278
|
-
}
|
|
2279
|
-
return true;
|
|
2280
|
-
}
|
|
2281
|
-
catch (error) {
|
|
2282
|
-
console.error(`Failed to test PUT /pages/${slug}:`, error);
|
|
2283
|
-
return false;
|
|
2284
|
-
}
|
|
2285
|
-
},
|
|
2286
|
-
/**
|
|
2287
|
-
* Test DELETE /pages/:slug - Delete a page
|
|
2288
|
-
*/
|
|
2289
|
-
async delete(fetch, slug) {
|
|
2290
|
-
try {
|
|
2291
|
-
const request = new Request(`http://example.com/pages/${encodeURIComponent(slug)}`, {
|
|
2292
|
-
method: 'DELETE',
|
|
2293
|
-
});
|
|
2294
|
-
const response = await fetch(request);
|
|
2295
|
-
if (response.status !== 204) {
|
|
2296
|
-
console.error(`DELETE /pages/${slug} returned ${response.status}, expected 204`);
|
|
2297
|
-
return false;
|
|
2298
|
-
}
|
|
2299
|
-
return true;
|
|
2300
|
-
}
|
|
2301
|
-
catch (error) {
|
|
2302
|
-
console.error(`Failed to test DELETE /pages/${slug}:`, error);
|
|
2303
|
-
return false;
|
|
2304
|
-
}
|
|
2305
|
-
},
|
|
2306
|
-
/**
|
|
2307
|
-
* Test GET /pages/:slug with non-existent page - Should return 404
|
|
2308
|
-
*/
|
|
2309
|
-
async notFound(fetch, slug) {
|
|
2310
|
-
try {
|
|
2311
|
-
const request = new Request(`http://example.com/pages/${encodeURIComponent(slug)}`, {
|
|
2312
|
-
method: 'GET',
|
|
2313
|
-
});
|
|
2314
|
-
const response = await fetch(request);
|
|
2315
|
-
if (response.status !== 404) {
|
|
2316
|
-
console.error(`GET /pages/${slug} with non-existent page returned ${response.status}, expected 404`);
|
|
2317
|
-
return false;
|
|
2318
|
-
}
|
|
2319
|
-
return true;
|
|
2320
|
-
}
|
|
2321
|
-
catch (error) {
|
|
2322
|
-
console.error(`Failed to test GET /pages/${slug} with non-existent page:`, error);
|
|
2323
|
-
return false;
|
|
2324
|
-
}
|
|
2325
|
-
},
|
|
2326
|
-
/**
|
|
2327
|
-
* Test DELETE /pages/:slug preserves resources - deleting a page should not delete resources
|
|
2328
|
-
*/
|
|
2329
|
-
async deletePreservesResources(fetch) {
|
|
2330
|
-
try {
|
|
2331
|
-
// First create a resource
|
|
2332
|
-
const formData = new FormData();
|
|
2333
|
-
formData.append('file', new Blob(['test content for spec'], { type: 'text/plain' }), 'spec-test-resource.txt');
|
|
2334
|
-
formData.append('type', 'text');
|
|
2335
|
-
formData.append('key', 'spec-test-resource.txt');
|
|
2336
|
-
formData.append('local', 'false');
|
|
2337
|
-
const createResourceRequest = new Request('http://example.com/resources', {
|
|
2338
|
-
method: 'POST',
|
|
2339
|
-
body: formData,
|
|
2340
|
-
});
|
|
2341
|
-
const createResourceResponse = await fetch(createResourceRequest);
|
|
2342
|
-
if (createResourceResponse.status !== 201) {
|
|
2343
|
-
console.error(`Failed to create test resource: ${createResourceResponse.status}`);
|
|
2344
|
-
return false;
|
|
2345
|
-
}
|
|
2346
|
-
// Create a page to delete
|
|
2347
|
-
const pageSlug = `spec-test-page-${Date.now()}`;
|
|
2348
|
-
const createPageRequest = new Request('http://example.com/pages', {
|
|
2349
|
-
method: 'POST',
|
|
2350
|
-
headers: {
|
|
2351
|
-
'Content-Type': 'application/json',
|
|
2352
|
-
},
|
|
2353
|
-
body: JSON.stringify({
|
|
2354
|
-
slug: pageSlug,
|
|
2355
|
-
html: '<html><body>Test page</body></html>',
|
|
2356
|
-
brainRunId: 'spec-test-brain-run-id',
|
|
2357
|
-
}),
|
|
2358
|
-
});
|
|
2359
|
-
const createPageResponse = await fetch(createPageRequest);
|
|
2360
|
-
if (createPageResponse.status !== 201) {
|
|
2361
|
-
console.error(`Failed to create test page: ${createPageResponse.status}`);
|
|
2362
|
-
// Clean up resource
|
|
2363
|
-
await fetch(new Request('http://example.com/resources/spec-test-resource.txt', { method: 'DELETE' }));
|
|
2364
|
-
return false;
|
|
2365
|
-
}
|
|
2366
|
-
// Delete the page
|
|
2367
|
-
const deletePageRequest = new Request(`http://example.com/pages/${encodeURIComponent(pageSlug)}`, { method: 'DELETE' });
|
|
2368
|
-
const deletePageResponse = await fetch(deletePageRequest);
|
|
2369
|
-
if (deletePageResponse.status !== 204) {
|
|
2370
|
-
console.error(`DELETE /pages/${pageSlug} returned ${deletePageResponse.status}, expected 204`);
|
|
2371
|
-
// Clean up resource
|
|
2372
|
-
await fetch(new Request('http://example.com/resources/spec-test-resource.txt', { method: 'DELETE' }));
|
|
2373
|
-
return false;
|
|
2374
|
-
}
|
|
2375
|
-
// Verify the resource still exists
|
|
2376
|
-
const listResourcesRequest = new Request('http://example.com/resources', {
|
|
2377
|
-
method: 'GET',
|
|
2378
|
-
});
|
|
2379
|
-
const listResourcesResponse = await fetch(listResourcesRequest);
|
|
2380
|
-
if (!listResourcesResponse.ok) {
|
|
2381
|
-
console.error(`GET /resources returned ${listResourcesResponse.status}`);
|
|
2382
|
-
return false;
|
|
2383
|
-
}
|
|
2384
|
-
const resourcesData = (await listResourcesResponse.json());
|
|
2385
|
-
const resourceExists = resourcesData.resources.some((r) => r.key === 'spec-test-resource.txt');
|
|
2386
|
-
if (!resourceExists) {
|
|
2387
|
-
console.error('DELETE /pages incorrectly deleted resources - resource not found after page delete');
|
|
2388
|
-
return false;
|
|
2389
|
-
}
|
|
2390
|
-
// Clean up: delete the test resource
|
|
2391
|
-
await fetch(new Request('http://example.com/resources/spec-test-resource.txt', { method: 'DELETE' }));
|
|
2392
|
-
return true;
|
|
2393
|
-
}
|
|
2394
|
-
catch (error) {
|
|
2395
|
-
console.error(`Failed to test DELETE /pages preserves resources:`, error);
|
|
2396
|
-
return false;
|
|
2397
|
-
}
|
|
2398
|
-
},
|
|
2399
|
-
};
|
|
2400
|
-
/**
|
|
2401
|
-
* Bundle API Tests
|
|
2402
|
-
*
|
|
2403
|
-
* Tests for the /bundle/components.js endpoint which serves the component bundle.
|
|
2404
|
-
*
|
|
2405
|
-
* NOTE: These tests only verify the API endpoint behavior. The bundle build and
|
|
2406
|
-
* upload process is backend-specific and must be tested separately by each
|
|
2407
|
-
* backend implementation.
|
|
2408
|
-
*/
|
|
2409
|
-
export const bundle = {
|
|
2410
|
-
/**
|
|
2411
|
-
* Test GET /bundle/components.js - Serve the component bundle
|
|
2412
|
-
*/
|
|
2413
|
-
async get(fetch) {
|
|
2414
|
-
try {
|
|
2415
|
-
const request = new Request('http://example.com/bundle/components.js', {
|
|
2416
|
-
method: 'GET',
|
|
2417
|
-
});
|
|
2418
|
-
const response = await fetch(request);
|
|
2419
|
-
// Bundle may or may not exist depending on project setup
|
|
2420
|
-
// 200 = bundle exists and served correctly
|
|
2421
|
-
// 404 = bundle not found (expected if no components/ directory)
|
|
2422
|
-
if (response.status !== 200 && response.status !== 404) {
|
|
2423
|
-
console.error(`GET /bundle/components.js returned unexpected status ${response.status}`);
|
|
2424
|
-
return false;
|
|
2425
|
-
}
|
|
2426
|
-
const contentType = response.headers.get('Content-Type');
|
|
2427
|
-
if (!contentType || !contentType.includes('application/javascript')) {
|
|
2428
|
-
console.error(`Expected Content-Type application/javascript, got ${contentType}`);
|
|
2429
|
-
return false;
|
|
2430
|
-
}
|
|
2431
|
-
// If 200, verify we got some content
|
|
2432
|
-
if (response.status === 200) {
|
|
2433
|
-
const content = await response.text();
|
|
2434
|
-
if (!content || content.length === 0) {
|
|
2435
|
-
console.error('Bundle endpoint returned 200 but empty content');
|
|
2436
|
-
return false;
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
// If 404, verify we got the helpful error message
|
|
2440
|
-
if (response.status === 404) {
|
|
2441
|
-
const content = await response.text();
|
|
2442
|
-
if (!content.includes('Bundle not found')) {
|
|
2443
|
-
console.error('Bundle 404 response missing helpful error message');
|
|
2444
|
-
return false;
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
return true;
|
|
2448
|
-
}
|
|
2449
|
-
catch (error) {
|
|
2450
|
-
console.error(`Failed to test GET /bundle/components.js:`, error);
|
|
2451
|
-
return false;
|
|
2452
|
-
}
|
|
2453
|
-
},
|
|
2454
|
-
};
|
|
2455
|
-
//# sourceMappingURL=api.js.map
|
|
1239
|
+
//# sourceMappingURL=brains.js.map
|