@gcoredev/fastedge-test 0.0.1-beta.0 → 0.1.0-beta.2

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.
Files changed (43) hide show
  1. package/README.md +6 -6
  2. package/bin/fastedge-debug.js +2 -0
  3. package/dist/fastedge-cli/METADATA.json +1 -3
  4. package/dist/fastedge-cli/{fastedge-run-linux-x64-unkown → fastedge-run-darwin-arm64} +0 -0
  5. package/dist/fastedge-cli/fastedge-run-linux-x64 +0 -0
  6. package/dist/fastedge-cli/fastedge-run.exe +0 -0
  7. package/dist/frontend/assets/index-CEFjsU8e.js +35 -0
  8. package/dist/frontend/assets/index-DdlINQc_.css +1 -0
  9. package/dist/frontend/index.html +2 -2
  10. package/dist/lib/index.cjs +329 -112
  11. package/dist/lib/index.js +331 -115
  12. package/dist/lib/runner/HostFunctions.d.ts +8 -0
  13. package/dist/lib/runner/HttpWasmRunner.d.ts +34 -14
  14. package/dist/lib/runner/IStateManager.d.ts +3 -2
  15. package/dist/lib/runner/IWasmRunner.d.ts +18 -1
  16. package/dist/lib/runner/NullStateManager.d.ts +1 -0
  17. package/dist/lib/runner/PortManager.d.ts +17 -19
  18. package/dist/lib/runner/ProxyWasmRunner.d.ts +7 -0
  19. package/dist/lib/runner/standalone.d.ts +1 -1
  20. package/dist/lib/schemas/api.d.ts +8 -2
  21. package/dist/lib/schemas/config.d.ts +4 -1
  22. package/dist/lib/test-framework/index.cjs +330 -114
  23. package/dist/lib/test-framework/index.js +332 -117
  24. package/dist/lib/test-framework/suite-runner.d.ts +1 -1
  25. package/dist/server.js +30 -30
  26. package/docs/API.md +758 -360
  27. package/docs/DEBUGGER.md +151 -0
  28. package/docs/INDEX.md +111 -0
  29. package/docs/RUNNER.md +582 -0
  30. package/docs/TEST_CONFIG.md +242 -0
  31. package/docs/TEST_FRAMEWORK.md +384 -284
  32. package/docs/WEBSOCKET.md +499 -0
  33. package/docs/quickstart.md +171 -0
  34. package/llms.txt +72 -14
  35. package/package.json +17 -6
  36. package/schemas/api-config.schema.json +12 -5
  37. package/schemas/api-load.schema.json +11 -6
  38. package/schemas/{test-config.schema.json → fastedge-config.test.schema.json} +12 -5
  39. package/dist/fastedge-cli/.gitkeep +0 -0
  40. package/dist/frontend/assets/index-CnXStFTd.css +0 -1
  41. package/dist/frontend/assets/index-FR9Oqsow.js +0 -37
  42. package/docs/HYBRID_LOADING.md +0 -546
  43. package/docs/LOCAL_SERVER.md +0 -153
@@ -0,0 +1,499 @@
1
+ # WebSocket API
2
+
3
+ Real-time event stream from the `@gcoredev/fastedge-test` server to connected clients.
4
+
5
+ ## Connection
6
+
7
+ Connect to the WebSocket server at:
8
+
9
+ ```
10
+ ws://localhost:{port}/ws
11
+ ```
12
+
13
+ where `{port}` is the port the server is running on (default `5179`).
14
+
15
+ ```javascript
16
+ const ws = new WebSocket("ws://localhost:5179/ws");
17
+
18
+ ws.addEventListener("message", (event) => {
19
+ const msg = JSON.parse(event.data);
20
+ console.log(msg.type, msg.data);
21
+ });
22
+ ```
23
+
24
+ ### Lifecycle
25
+
26
+ 1. **Connect** — the server accepts all connections and immediately sends a `connection_status` event confirming the connection and the current client count.
27
+ 2. **Ping / pong** — the server sends WebSocket `ping` frames every 15 seconds. Clients that have not responded within 30 seconds are terminated. Standard WebSocket clients handle pong automatically.
28
+ 3. **Disconnect** — when a client disconnects, the server broadcasts an updated `connection_status` to remaining clients.
29
+
30
+ ## Event Format
31
+
32
+ Every event shares a common envelope:
33
+
34
+ ```typescript
35
+ interface BaseEvent {
36
+ type: string; // event discriminant
37
+ timestamp: number; // Unix ms
38
+ source: "ui" | "ai_agent" | "api" | "system";
39
+ data: object; // event-specific payload
40
+ }
41
+ ```
42
+
43
+ | Field | Type | Description |
44
+ | ----------- | ----------------------------------------- | --------------------------------------------------- |
45
+ | `type` | `string` | Event discriminant — one of the values listed below |
46
+ | `timestamp` | `number` | Unix epoch in milliseconds |
47
+ | `source` | `'ui' \| 'ai_agent' \| 'api' \| 'system'` | What triggered the event |
48
+ | `data` | `object` | Event-specific payload |
49
+
50
+ ## Event Types
51
+
52
+ ### wasm_loaded
53
+
54
+ Fired when a WASM binary has been loaded and is ready to handle requests.
55
+
56
+ ```typescript
57
+ interface WasmLoadedEvent {
58
+ type: "wasm_loaded";
59
+ timestamp: number;
60
+ source: "ui" | "ai_agent" | "api" | "system";
61
+ data: {
62
+ filename: string;
63
+ size: number;
64
+ runnerPort?: number | null;
65
+ wasmType: "proxy-wasm" | "http-wasm";
66
+ resolvedPath?: string | null;
67
+ };
68
+ }
69
+ ```
70
+
71
+ | Field | Type | Description |
72
+ | --------------- | ----------------------------- | -------------------------------------------------------------------- |
73
+ | `filename` | `string` | Name of the loaded WASM file |
74
+ | `size` | `number` | File size in bytes |
75
+ | `runnerPort?` | `number \| null ` | Port the runner is listening on, if applicable. Omitted when not set |
76
+ | `wasmType` | `'proxy-wasm' \| 'http-wasm'` | The WASM filter type |
77
+ | `resolvedPath?` | `string \| null ` | Absolute filesystem path to the loaded binary. Omitted when not set |
78
+
79
+ **Example:**
80
+
81
+ ```json
82
+ {
83
+ "type": "wasm_loaded",
84
+ "timestamp": 1742734800000,
85
+ "source": "api",
86
+ "data": {
87
+ "filename": "filter.wasm",
88
+ "size": 204800,
89
+ "runnerPort": 8081,
90
+ "wasmType": "proxy-wasm",
91
+ "resolvedPath": "/workspace/filter.wasm"
92
+ }
93
+ }
94
+ ```
95
+
96
+ ---
97
+
98
+ ### request_started
99
+
100
+ Fired when the server begins processing an incoming request through the WASM filter.
101
+
102
+ ```typescript
103
+ interface RequestStartedEvent {
104
+ type: "request_started";
105
+ timestamp: number;
106
+ source: "ui" | "ai_agent" | "api" | "system";
107
+ data: {
108
+ url: string;
109
+ method: string;
110
+ headers: Record<string, string>;
111
+ };
112
+ }
113
+ ```
114
+
115
+ | Field | Type | Description |
116
+ | --------- | ------------------------ | --------------------------------- |
117
+ | `url` | `string` | Full request URL |
118
+ | `method` | `string` | HTTP method (`GET`, `POST`, etc.) |
119
+ | `headers` | `Record<string, string>` | Request headers |
120
+
121
+ **Example:**
122
+
123
+ ```json
124
+ {
125
+ "type": "request_started",
126
+ "timestamp": 1742734800100,
127
+ "source": "api",
128
+ "data": {
129
+ "url": "https://example.com/api/resource",
130
+ "method": "GET",
131
+ "headers": {
132
+ "host": "example.com",
133
+ "user-agent": "curl/8.0"
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ### hook_executed
142
+
143
+ Fired after each individual proxy-wasm hook completes. Multiple `hook_executed` events are emitted per request — one per hook phase that runs.
144
+
145
+ Hook names are camelCase: `onRequestHeaders`, `onRequestBody`, `onResponseHeaders`, `onResponseBody`.
146
+
147
+ ```typescript
148
+ interface HookExecutedEvent {
149
+ type: "hook_executed";
150
+ timestamp: number;
151
+ source: "ui" | "ai_agent" | "api" | "system";
152
+ data: {
153
+ hook: string;
154
+ returnCode: number | null;
155
+ logCount: number;
156
+ input: {
157
+ request: { headers: Record<string, string>; body: string };
158
+ response: { headers: Record<string, string>; body: string };
159
+ };
160
+ output: {
161
+ request: { headers: Record<string, string>; body: string };
162
+ response: { headers: Record<string, string>; body: string };
163
+ };
164
+ };
165
+ }
166
+ ```
167
+
168
+ | Field | Type | Description |
169
+ | ------------ | ---------------- | ---------------------------------------------------------- |
170
+ | `hook` | `string` | Hook name (e.g. `onRequestHeaders`) |
171
+ | `returnCode` | `number \| null` | Return code from the WASM filter, or `null` if unavailable |
172
+ | `logCount` | `number` | Number of log lines emitted during this hook |
173
+ | `input` | `object` | Request and response state passed into the hook |
174
+ | `output` | `object` | Request and response state after the hook ran |
175
+
176
+ **Example:**
177
+
178
+ ```json
179
+ {
180
+ "type": "hook_executed",
181
+ "timestamp": 1742734800200,
182
+ "source": "api",
183
+ "data": {
184
+ "hook": "onRequestHeaders",
185
+ "returnCode": 0,
186
+ "logCount": 2,
187
+ "input": {
188
+ "request": {
189
+ "headers": { "host": "example.com" },
190
+ "body": ""
191
+ },
192
+ "response": {
193
+ "headers": {},
194
+ "body": ""
195
+ }
196
+ },
197
+ "output": {
198
+ "request": {
199
+ "headers": { "host": "example.com", "x-injected": "true" },
200
+ "body": ""
201
+ },
202
+ "response": {
203
+ "headers": {},
204
+ "body": ""
205
+ }
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ### request_completed
214
+
215
+ Fired when all hook phases have completed and a final response is available.
216
+
217
+ `hookResults` keys are camelCase hook names (`onRequestHeaders`, `onRequestBody`, `onResponseHeaders`, `onResponseBody`).
218
+
219
+ ```typescript
220
+ interface RequestCompletedEvent {
221
+ type: "request_completed";
222
+ timestamp: number;
223
+ source: "ui" | "ai_agent" | "api" | "system";
224
+ data: {
225
+ hookResults: Record<string, any>;
226
+ finalResponse: {
227
+ status: number;
228
+ statusText: string;
229
+ headers: Record<string, string>;
230
+ body: string;
231
+ contentType: string;
232
+ isBase64?: boolean;
233
+ };
234
+ calculatedProperties?: Record<string, unknown>;
235
+ };
236
+ }
237
+ ```
238
+
239
+ | Field | Type | Description |
240
+ | --------------------------- | -------------------------------------- | ----------------------------------------------------- |
241
+ | `hookResults` | `Record<string, any>` | Per-hook execution results, keyed by hook name |
242
+ | `finalResponse.status` | `number` | HTTP status code |
243
+ | `finalResponse.statusText` | `string` | HTTP status text |
244
+ | `finalResponse.headers` | `Record<string, string>` | Response headers |
245
+ | `finalResponse.body` | `string` | Response body (may be base64 if `isBase64` is `true`) |
246
+ | `finalResponse.contentType` | `string` | Content-Type of the response |
247
+ | `finalResponse.isBase64` | `boolean \| undefined` | Whether `body` is base64-encoded |
248
+ | `calculatedProperties` | `Record<string, unknown> \| undefined` | Properties computed during execution, if any |
249
+
250
+ **Example:**
251
+
252
+ ```json
253
+ {
254
+ "type": "request_completed",
255
+ "timestamp": 1742734800500,
256
+ "source": "api",
257
+ "data": {
258
+ "hookResults": {
259
+ "onRequestHeaders": { "returnCode": 0 },
260
+ "onResponseHeaders": { "returnCode": 0 }
261
+ },
262
+ "finalResponse": {
263
+ "status": 200,
264
+ "statusText": "OK",
265
+ "headers": { "content-type": "application/json" },
266
+ "body": "{\"ok\":true}",
267
+ "contentType": "application/json",
268
+ "isBase64": false
269
+ },
270
+ "calculatedProperties": {}
271
+ }
272
+ }
273
+ ```
274
+
275
+ ---
276
+
277
+ ### request_failed
278
+
279
+ Fired when request processing fails before a response can be produced.
280
+
281
+ ```typescript
282
+ interface RequestFailedEvent {
283
+ type: "request_failed";
284
+ timestamp: number;
285
+ source: "ui" | "ai_agent" | "api" | "system";
286
+ data: {
287
+ error: string;
288
+ details?: string;
289
+ };
290
+ }
291
+ ```
292
+
293
+ | Field | Type | Description |
294
+ | --------- | --------------------- | -------------------------------------------------- |
295
+ | `error` | `string` | Short error message |
296
+ | `details` | `string \| undefined` | Extended error detail or stack trace, if available |
297
+
298
+ **Example:**
299
+
300
+ ```json
301
+ {
302
+ "type": "request_failed",
303
+ "timestamp": 1742734800300,
304
+ "source": "api",
305
+ "data": {
306
+ "error": "WASM execution error",
307
+ "details": "RuntimeError: memory access out of bounds"
308
+ }
309
+ }
310
+ ```
311
+
312
+ ---
313
+
314
+ ### properties_updated
315
+
316
+ Fired when the set of active properties changes (e.g. after a properties configuration update).
317
+
318
+ ```typescript
319
+ interface PropertiesUpdatedEvent {
320
+ type: "properties_updated";
321
+ timestamp: number;
322
+ source: "ui" | "ai_agent" | "api" | "system";
323
+ data: {
324
+ properties: Record<string, string>;
325
+ };
326
+ }
327
+ ```
328
+
329
+ | Field | Type | Description |
330
+ | ------------ | ------------------------ | ------------------------------------------ |
331
+ | `properties` | `Record<string, string>` | Full current property map after the update |
332
+
333
+ **Example:**
334
+
335
+ ```json
336
+ {
337
+ "type": "properties_updated",
338
+ "timestamp": 1742734800050,
339
+ "source": "ui",
340
+ "data": {
341
+ "properties": {
342
+ "plugin.name": "my-filter",
343
+ "plugin.version": "1.0.0"
344
+ }
345
+ }
346
+ }
347
+ ```
348
+
349
+ ---
350
+
351
+ ### http_wasm_request_completed
352
+
353
+ Fired when an http-wasm filter finishes processing a request and a response is available.
354
+
355
+ ```typescript
356
+ interface HttpWasmRequestCompletedEvent {
357
+ type: "http_wasm_request_completed";
358
+ timestamp: number;
359
+ source: "ui" | "ai_agent" | "api" | "system";
360
+ data: {
361
+ response: {
362
+ status: number;
363
+ statusText: string;
364
+ headers: Record<string, string>;
365
+ body: string;
366
+ contentType: string | null;
367
+ isBase64?: boolean;
368
+ };
369
+ };
370
+ }
371
+ ```
372
+
373
+ | Field | Type | Description |
374
+ | ---------------------- | ------------------------ | ----------------------------------------------------- |
375
+ | `response.status` | `number` | HTTP status code |
376
+ | `response.statusText` | `string` | HTTP status text |
377
+ | `response.headers` | `Record<string, string>` | Response headers |
378
+ | `response.body` | `string` | Response body (may be base64 if `isBase64` is `true`) |
379
+ | `response.contentType` | `string \| null` | Content-Type, or `null` if absent |
380
+ | `response.isBase64` | `boolean \| undefined` | Whether `body` is base64-encoded |
381
+
382
+ **Example:**
383
+
384
+ ```json
385
+ {
386
+ "type": "http_wasm_request_completed",
387
+ "timestamp": 1742734800600,
388
+ "source": "api",
389
+ "data": {
390
+ "response": {
391
+ "status": 200,
392
+ "statusText": "OK",
393
+ "headers": { "content-type": "text/plain" },
394
+ "body": "Hello, world!",
395
+ "contentType": "text/plain",
396
+ "isBase64": false
397
+ }
398
+ }
399
+ }
400
+ ```
401
+
402
+ ---
403
+
404
+ ### http_wasm_log
405
+
406
+ Fired in real-time as the http-wasm filter emits log lines during both execute and live modes. One event is emitted per log line.
407
+
408
+ ```typescript
409
+ interface HttpWasmLogEvent {
410
+ type: "http_wasm_log";
411
+ timestamp: number;
412
+ source: "ui" | "ai_agent" | "api" | "system";
413
+ data: {
414
+ level: number;
415
+ message: string;
416
+ };
417
+ }
418
+ ```
419
+
420
+ | Field | Type | Description |
421
+ | --------- | -------- | ---------------------------------------------------- |
422
+ | `level` | `number` | Log level (follows proxy-wasm log level conventions) |
423
+ | `message` | `string` | Log message text |
424
+
425
+ **Example:**
426
+
427
+ ```json
428
+ {
429
+ "type": "http_wasm_log",
430
+ "timestamp": 1742734800250,
431
+ "source": "api",
432
+ "data": {
433
+ "level": 2,
434
+ "message": "processing request to /api/resource"
435
+ }
436
+ }
437
+ ```
438
+
439
+ ---
440
+
441
+ ### connection_status
442
+
443
+ Fired by the server in three situations:
444
+
445
+ 1. Immediately after a client connects (sent only to the connecting client).
446
+ 2. When any client connects or disconnects (broadcast to all clients).
447
+ 3. In response to a client `ping` message.
448
+
449
+ ```typescript
450
+ interface ConnectionStatusEvent {
451
+ type: "connection_status";
452
+ timestamp: number;
453
+ source: "system";
454
+ data: {
455
+ connected: boolean;
456
+ clientCount: number;
457
+ };
458
+ }
459
+ ```
460
+
461
+ | Field | Type | Description |
462
+ | ------------- | --------- | ---------------------------------------------------------------- |
463
+ | `connected` | `boolean` | Always `true` when received (indicates this client is connected) |
464
+ | `clientCount` | `number` | Total number of currently connected clients including this one |
465
+
466
+ **Example:**
467
+
468
+ ```json
469
+ {
470
+ "type": "connection_status",
471
+ "timestamp": 1742734800010,
472
+ "source": "system",
473
+ "data": {
474
+ "connected": true,
475
+ "clientCount": 1
476
+ }
477
+ }
478
+ ```
479
+
480
+ ## Client Messages
481
+
482
+ The server accepts one client-to-server message type over WebSocket.
483
+
484
+ ### ping
485
+
486
+ Requests a `connection_status` response from the server. Useful for verifying the connection is alive at the application level, independent of the underlying WebSocket ping/pong frames.
487
+
488
+ ```json
489
+ { "type": "ping" }
490
+ ```
491
+
492
+ The server responds by sending a `connection_status` event to the requesting client.
493
+
494
+ All other commands (loading WASM, triggering requests, updating properties) are submitted via the REST API, not over WebSocket.
495
+
496
+ ## See Also
497
+
498
+ - `API.md` — REST endpoints for triggering requests, loading WASM, and updating properties
499
+ - `TEST_FRAMEWORK.md` — Using WebSocket events in automated tests
@@ -0,0 +1,171 @@
1
+ # Quickstart — @gcoredev/fastedge-test
2
+
3
+ A local test runner and debugger for FastEdge WASM modules.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install --save-dev @gcoredev/fastedge-test
9
+ ```
10
+
11
+ ```bash
12
+ pnpm add -D @gcoredev/fastedge-test
13
+ ```
14
+
15
+ Requires Node.js >= 22.12.
16
+
17
+ ## Option 1: Interactive Debugger
18
+
19
+ Launch the interactive debugger UI to inspect your WASM module's request/response behavior in a browser.
20
+
21
+ ```bash
22
+ npx fastedge-debug
23
+ ```
24
+
25
+ The server starts on port `5179` by default. Open `http://localhost:5179` in your browser to use the debugger interface.
26
+
27
+ Use the `PORT` environment variable to override the default port:
28
+
29
+ ```bash
30
+ PORT=8080 npx fastedge-debug
31
+ ```
32
+
33
+ See [DEBUGGER.md](DEBUGGER.md) for full usage details.
34
+
35
+ ## Option 2: Programmatic Test Suite
36
+
37
+ Use `defineTestSuite` and `runAndExit` to write test scripts that run in CI or as standalone Node.js programs.
38
+
39
+ ```typescript
40
+ import { defineTestSuite, runAndExit, runFlow } from "@gcoredev/fastedge-test/test";
41
+
42
+ const suite = defineTestSuite({
43
+ wasmPath: "./dist/my-module.wasm",
44
+ tests: [
45
+ {
46
+ name: "redirects /old-path to /new-path",
47
+ async run(runner) {
48
+ const result = await runFlow(runner, {
49
+ url: "https://example.com/old-path",
50
+ method: "GET",
51
+ });
52
+
53
+ const location = result.finalResponse.headers["location"];
54
+ if (location !== "/new-path") {
55
+ throw new Error(`Expected location "/new-path", got "${location}"`);
56
+ }
57
+ },
58
+ },
59
+ {
60
+ name: "adds X-Custom-Header to response",
61
+ async run(runner) {
62
+ const result = await runFlow(runner, {
63
+ url: "https://example.com/",
64
+ method: "GET",
65
+ responseStatus: 200,
66
+ responseBody: "hello",
67
+ });
68
+
69
+ const header = result.finalResponse.headers["x-custom-header"];
70
+ if (!header) {
71
+ throw new Error("Expected x-custom-header to be present");
72
+ }
73
+ },
74
+ },
75
+ ],
76
+ });
77
+
78
+ await runAndExit(suite);
79
+ ```
80
+
81
+ `runAndExit` prints a test summary and exits with code `0` (all pass) or `1` (any fail), making it suitable for CI pipelines.
82
+
83
+ See [TEST_FRAMEWORK.md](TEST_FRAMEWORK.md) for the full API reference including `runTestSuite`, `loadConfigFile`, and all `FlowOptions` fields.
84
+
85
+ ## Option 3: Low-Level Runner
86
+
87
+ Use `createRunner` directly when you need full control over the runner lifecycle, or when integrating with an existing test framework such as Vitest or Jest.
88
+
89
+ ```typescript
90
+ import { createRunner } from "@gcoredev/fastedge-test";
91
+
92
+ const runner = await createRunner("./dist/my-module.wasm");
93
+
94
+ try {
95
+ const result = await runner.callFullFlow(
96
+ "https://example.com/", // url
97
+ "GET", // method
98
+ { // request headers
99
+ ":method": "GET",
100
+ ":path": "/",
101
+ ":authority": "example.com",
102
+ ":scheme": "https",
103
+ },
104
+ "", // request body
105
+ {}, // response headers
106
+ "", // response body
107
+ 200, // response status
108
+ "OK", // response status text
109
+ {}, // properties
110
+ true, // enforceProductionPropertyRules
111
+ );
112
+
113
+ console.log(result.finalResponse);
114
+ } finally {
115
+ await runner.cleanup();
116
+ }
117
+ ```
118
+
119
+ You can also load from an in-memory buffer using `createRunnerFromBuffer`:
120
+
121
+ ```typescript
122
+ import { createRunnerFromBuffer } from "@gcoredev/fastedge-test";
123
+ import { readFile } from "fs/promises";
124
+
125
+ const buffer = await readFile("./dist/my-module.wasm");
126
+ const runner = await createRunnerFromBuffer(buffer);
127
+ ```
128
+
129
+ See [RUNNER.md](RUNNER.md) for the full `IWasmRunner` interface, `RunnerConfig` options, and `FullFlowResult` shape.
130
+
131
+ ## Configuration
132
+
133
+ Place a `fastedge-config.test.json` file in your project root to define default request parameters, response stubs, and WASM properties for the interactive debugger.
134
+
135
+ ```json
136
+ {
137
+ "$schema": "./node_modules/@gcoredev/fastedge-test/schemas/fastedge-config.test.schema.json",
138
+ "wasm": {
139
+ "path": "./dist/my-module.wasm"
140
+ },
141
+ "request": {
142
+ "method": "GET",
143
+ "url": "https://example.com/",
144
+ "headers": {
145
+ "accept": "text/html"
146
+ },
147
+ "body": ""
148
+ },
149
+ "response": {
150
+ "headers": {
151
+ "content-type": "text/html"
152
+ },
153
+ "body": "<html><body>Hello</body></html>"
154
+ },
155
+ "properties": {
156
+ "my-property": "value"
157
+ }
158
+ }
159
+ ```
160
+
161
+ The `$schema` field enables editor autocompletion and inline validation.
162
+
163
+ See [TEST_CONFIG.md](TEST_CONFIG.md) for all configuration fields and their defaults.
164
+
165
+ ## Next Steps
166
+
167
+ - [RUNNER.md](RUNNER.md) — `createRunner`, `createRunnerFromBuffer`, `IWasmRunner` interface, `RunnerConfig`
168
+ - [TEST_FRAMEWORK.md](TEST_FRAMEWORK.md) — `defineTestSuite`, `runAndExit`, `runFlow`, `runTestSuite`, `loadConfigFile`
169
+ - [TEST_CONFIG.md](TEST_CONFIG.md) — `fastedge-config.test.json` schema reference
170
+ - [DEBUGGER.md](DEBUGGER.md) — Interactive debugger usage and configuration
171
+ - [API.md](API.md) — REST API reference for programmatic server integration