@gcoredev/fastedge-test 0.0.1-beta.4 → 0.1.0-beta.3

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 (41) hide show
  1. package/README.md +6 -6
  2. package/dist/fastedge-cli/METADATA.json +1 -3
  3. package/dist/fastedge-cli/{fastedge-run-linux-x64-unkown → fastedge-run-darwin-arm64} +0 -0
  4. package/dist/fastedge-cli/fastedge-run-linux-x64 +0 -0
  5. package/dist/fastedge-cli/fastedge-run.exe +0 -0
  6. package/dist/frontend/assets/index-CEFjsU8e.js +35 -0
  7. package/dist/frontend/assets/index-DdlINQc_.css +1 -0
  8. package/dist/frontend/index.html +2 -2
  9. package/dist/lib/index.cjs +299 -107
  10. package/dist/lib/index.js +301 -110
  11. package/dist/lib/runner/HostFunctions.d.ts +8 -0
  12. package/dist/lib/runner/HttpWasmRunner.d.ts +34 -14
  13. package/dist/lib/runner/IStateManager.d.ts +3 -2
  14. package/dist/lib/runner/IWasmRunner.d.ts +16 -1
  15. package/dist/lib/runner/NullStateManager.d.ts +1 -0
  16. package/dist/lib/runner/PortManager.d.ts +17 -19
  17. package/dist/lib/runner/ProxyWasmRunner.d.ts +7 -0
  18. package/dist/lib/schemas/api.d.ts +8 -2
  19. package/dist/lib/schemas/config.d.ts +4 -1
  20. package/dist/lib/test-framework/index.cjs +301 -108
  21. package/dist/lib/test-framework/index.js +303 -111
  22. package/dist/lib/test-framework/suite-runner.d.ts +1 -1
  23. package/dist/server.js +30 -29
  24. package/docs/API.md +758 -360
  25. package/docs/DEBUGGER.md +151 -0
  26. package/docs/INDEX.md +111 -0
  27. package/docs/RUNNER.md +582 -0
  28. package/docs/TEST_CONFIG.md +242 -0
  29. package/docs/TEST_FRAMEWORK.md +384 -284
  30. package/docs/WEBSOCKET.md +499 -0
  31. package/docs/quickstart.md +171 -0
  32. package/llms.txt +72 -14
  33. package/package.json +15 -5
  34. package/schemas/api-config.schema.json +12 -5
  35. package/schemas/api-load.schema.json +11 -6
  36. package/schemas/{test-config.schema.json → fastedge-config.test.schema.json} +12 -5
  37. package/dist/fastedge-cli/.gitkeep +0 -0
  38. package/dist/frontend/assets/index-CnXStFTd.css +0 -1
  39. package/dist/frontend/assets/index-FR9Oqsow.js +0 -37
  40. package/docs/HYBRID_LOADING.md +0 -546
  41. package/docs/LOCAL_SERVER.md +0 -153
package/docs/RUNNER.md ADDED
@@ -0,0 +1,582 @@
1
+ # Runner API
2
+
3
+ Low-level programmatic API for executing WASM binaries. Use this when you need direct control over runner lifecycle, hook execution, or headless test automation outside the test framework.
4
+
5
+ ## Import
6
+
7
+ ```typescript
8
+ import {
9
+ createRunner,
10
+ createRunnerFromBuffer,
11
+ ProxyWasmRunner,
12
+ HttpWasmRunner,
13
+ WasmRunnerFactory,
14
+ NullStateManager,
15
+ } from '@gcoredev/fastedge-test';
16
+
17
+ import type {
18
+ IWasmRunner,
19
+ WasmType,
20
+ RunnerConfig,
21
+ HttpRequest,
22
+ HttpResponse,
23
+ IStateManager,
24
+ HookResult,
25
+ FullFlowResult,
26
+ HookCall,
27
+ } from '@gcoredev/fastedge-test';
28
+ ```
29
+
30
+ ## Creating a Runner
31
+
32
+ ### createRunner
33
+
34
+ Creates a fully loaded runner from a WASM file on disk. Detects the WASM type automatically unless overridden via `config.runnerType`.
35
+
36
+ ```typescript
37
+ function createRunner(
38
+ wasmPath: string,
39
+ config?: RunnerConfig
40
+ ): Promise<IWasmRunner>
41
+ ```
42
+
43
+ **Parameters**
44
+
45
+ | Parameter | Type | Description |
46
+ | ---------- | -------------- | ---------------------------------------------- |
47
+ | `wasmPath` | `string` | Absolute or relative path to the `.wasm` file |
48
+ | `config` | `RunnerConfig` | Optional configuration (dotenv, type override) |
49
+
50
+ **Returns** `Promise<IWasmRunner>` — a loaded runner ready for execution.
51
+
52
+ ```typescript
53
+ import { createRunner } from '@gcoredev/fastedge-test';
54
+
55
+ const runner = await createRunner('./my-app.wasm');
56
+ // runner is loaded and ready
57
+ await runner.cleanup();
58
+ ```
59
+
60
+ ### createRunnerFromBuffer
61
+
62
+ Creates a fully loaded runner from an in-memory `Buffer`. Useful when you have already read the WASM binary (e.g. from a test fixture or download).
63
+
64
+ ```typescript
65
+ function createRunnerFromBuffer(
66
+ buffer: Buffer,
67
+ config?: RunnerConfig
68
+ ): Promise<IWasmRunner>
69
+ ```
70
+
71
+ **Parameters**
72
+
73
+ | Parameter | Type | Description |
74
+ | --------- | -------------- | ---------------------- |
75
+ | `buffer` | `Buffer` | WASM binary content |
76
+ | `config` | `RunnerConfig` | Optional configuration |
77
+
78
+ **Returns** `Promise<IWasmRunner>` — a loaded runner ready for execution.
79
+
80
+ ```typescript
81
+ import { createRunnerFromBuffer } from '@gcoredev/fastedge-test';
82
+ import { readFile } from 'fs/promises';
83
+
84
+ const buffer = await readFile('./my-app.wasm');
85
+ const runner = await createRunnerFromBuffer(buffer, {
86
+ runnerType: 'proxy-wasm',
87
+ });
88
+ await runner.cleanup();
89
+ ```
90
+
91
+ ## IWasmRunner Interface
92
+
93
+ ```typescript
94
+ interface IWasmRunner {
95
+ load(bufferOrPath: Buffer | string, config?: RunnerConfig): Promise<void>;
96
+ execute(request: HttpRequest): Promise<HttpResponse>;
97
+ callHook(hookCall: HookCall): Promise<HookResult>;
98
+ callFullFlow(
99
+ url: string,
100
+ method: string,
101
+ headers: Record<string, string>,
102
+ body: string,
103
+ responseHeaders: Record<string, string>,
104
+ responseBody: string,
105
+ responseStatus: number,
106
+ responseStatusText: string,
107
+ properties: Record<string, unknown>,
108
+ enforceProductionPropertyRules: boolean
109
+ ): Promise<FullFlowResult>;
110
+ applyDotenv(enabled: boolean, path?: string): Promise<void>;
111
+ cleanup(): Promise<void>;
112
+ getType(): WasmType;
113
+ setStateManager(stateManager: IStateManager): void;
114
+ }
115
+ ```
116
+
117
+ ### load
118
+
119
+ Loads a WASM binary into the runner. For `ProxyWasmRunner`, compiles the module and loads dotenv files. For `HttpWasmRunner`, writes the binary to a temp file and spawns a `fastedge-run` process.
120
+
121
+ ```typescript
122
+ load(bufferOrPath: Buffer | string, config?: RunnerConfig): Promise<void>
123
+ ```
124
+
125
+ Calling `load()` again on the same runner replaces the current module and restarts any underlying process.
126
+
127
+ ### execute (HTTP-WASM)
128
+
129
+ Executes an HTTP request through the WASM module. Only available on `HttpWasmRunner` (http-wasm). Calling this on a `ProxyWasmRunner` throws.
130
+
131
+ ```typescript
132
+ execute(request: HttpRequest): Promise<HttpResponse>
133
+ ```
134
+
135
+ The runner forwards the request to the locally spawned `fastedge-run` process and returns the response including any logs captured from the process.
136
+
137
+ ### callHook (Proxy-WASM)
138
+
139
+ Executes a single proxy-wasm hook in isolation. Only available on `ProxyWasmRunner` (proxy-wasm). Calling this on an `HttpWasmRunner` throws.
140
+
141
+ ```typescript
142
+ callHook(hookCall: HookCall): Promise<HookResult>
143
+ ```
144
+
145
+ Each call creates a fresh WASM instance with the request/response state from `hookCall`, invokes the appropriate hook export, and returns the resulting state diff and logs.
146
+
147
+ Valid hook names: `"onRequestHeaders"`, `"onRequestBody"`, `"onResponseHeaders"`, `"onResponseBody"`.
148
+
149
+ ### callFullFlow (Proxy-WASM)
150
+
151
+ Executes the complete CDN request/response lifecycle for a proxy-wasm module: runs all four hooks in sequence, performs a real HTTP fetch between request and response phases, and returns the aggregated results.
152
+
153
+ ```typescript
154
+ callFullFlow(
155
+ url: string,
156
+ method: string,
157
+ headers: Record<string, string>,
158
+ body: string,
159
+ responseHeaders: Record<string, string>,
160
+ responseBody: string,
161
+ responseStatus: number,
162
+ responseStatusText: string,
163
+ properties: Record<string, unknown>,
164
+ enforceProductionPropertyRules: boolean
165
+ ): Promise<FullFlowResult>
166
+ ```
167
+
168
+ **Parameters**
169
+
170
+ | Parameter | Type | Description |
171
+ | ---------------------------------- | ------------------------- | ----------------------------------------------------------------------- |
172
+ | `url` | `string` | Full request URL (e.g. `https://example.com/path`) |
173
+ | `method` | `string` | HTTP method |
174
+ | `headers` | `Record<string, string>` | Request headers |
175
+ | `body` | `string` | Request body |
176
+ | `responseHeaders` | `Record<string, string>` | Upstream response headers (used as initial state for response hooks) |
177
+ | `responseBody` | `string` | Upstream response body |
178
+ | `responseStatus` | `number` | Upstream response status code |
179
+ | `responseStatusText` | `string` | Upstream response status text |
180
+ | `properties` | `Record<string, unknown>` | Shared properties passed to all hooks |
181
+ | `enforceProductionPropertyRules` | `boolean` | When `true`, restricts property access to match CDN production behavior |
182
+
183
+ Hook execution order: `onRequestHeaders` → `onRequestBody` → *(real HTTP fetch)* → `onResponseHeaders` → `onResponseBody`.
184
+
185
+ **Local response short-circuit:** If a WASM module calls `send_http_response` (proxy-wasm: `proxy_send_local_response`) during `onRequestHeaders` or `onRequestBody` and returns `StopIteration` (return code `1`), the remaining hooks and origin fetch are **skipped**. The `finalResponse` in the result is built from the locally-sent status, headers, and body — matching CDN production behavior. This is how redirect modules (e.g., geo-redirect) and early error responses work.
186
+
187
+ Only available on `ProxyWasmRunner`. Calling on `HttpWasmRunner` throws.
188
+
189
+ ### applyDotenv
190
+
191
+ Updates dotenv settings on a loaded runner without reloading the WASM binary.
192
+
193
+ ```typescript
194
+ applyDotenv(enabled: boolean, path?: string): Promise<void>
195
+ ```
196
+
197
+ **Behavior by runner type:**
198
+
199
+ - **ProxyWasmRunner**: Resets `SecretStore` and `Dictionary` to empty, then re-reads dotenv files from `path` (or the current path if omitted). The compiled WASM module is not recompiled.
200
+ - **HttpWasmRunner**: Kills the current `fastedge-run` process and restarts it with the updated `--dotenv` flag.
201
+
202
+ ### cleanup
203
+
204
+ Releases all resources held by the runner.
205
+
206
+ ```typescript
207
+ cleanup(): Promise<void>
208
+ ```
209
+
210
+ - **ProxyWasmRunner**: No-op (no long-running processes). State is reset on the next `load()` call.
211
+ - **HttpWasmRunner**: Kills the `fastedge-run` process, releases the allocated port, and deletes any temporary WASM file written to disk.
212
+
213
+ Always call `cleanup()` when done with a runner, especially in test teardown.
214
+
215
+ ### getType
216
+
217
+ Returns the WASM type this runner handles.
218
+
219
+ ```typescript
220
+ getType(): WasmType // "http-wasm" | "proxy-wasm"
221
+ ```
222
+
223
+ ### setStateManager
224
+
225
+ Attaches a state manager for event emission. Called internally by the server; in headless use, pass `new NullStateManager()` (a no-op implementation) or omit entirely (the runner defaults to no-op behavior).
226
+
227
+ ```typescript
228
+ setStateManager(stateManager: IStateManager): void
229
+ ```
230
+
231
+ ## Runner Types
232
+
233
+ ### Proxy-WASM (CDN)
234
+
235
+ `ProxyWasmRunner` handles proxy-wasm binaries — the standard format for FastEdge CDN applications. These modules implement the proxy-wasm ABI and hook into the request/response lifecycle.
236
+
237
+ **Available methods**: `load`, `callHook`, `callFullFlow`, `applyDotenv`, `cleanup`, `getType`, `setStateManager`
238
+
239
+ **Not available**: `execute` (throws `Error`)
240
+
241
+ The runner compiles the WASM module once on `load()` and creates a fresh `WebAssembly.Instance` for each hook call, providing isolation between hook executions.
242
+
243
+ ### HTTP-WASM
244
+
245
+ `HttpWasmRunner` handles http-wasm (WASI component model) binaries. These modules implement the `wasi-http` interface and run as a standard HTTP server.
246
+
247
+ **Available methods**: `load`, `execute`, `applyDotenv`, `cleanup`, `getType`, `setStateManager`
248
+
249
+ **Not available**: `callHook`, `callFullFlow` (both throw `Error`)
250
+
251
+ The runner spawns a `fastedge-run` process on `load()` and forwards HTTP requests to it via localhost. The process is kept alive between requests.
252
+
253
+ ### Auto-Detection
254
+
255
+ Both factory functions (`createRunner`, `createRunnerFromBuffer`) automatically detect the WASM type by inspecting the binary. Detection examines the WASM module's imports and exports to determine whether it implements the proxy-wasm ABI or the wasi-http interface.
256
+
257
+ ### runnerType Override
258
+
259
+ If auto-detection produces incorrect results, use `RunnerConfig.runnerType` to force a specific type:
260
+
261
+ ```typescript
262
+ const runner = await createRunner('./my-app.wasm', {
263
+ runnerType: 'proxy-wasm',
264
+ });
265
+ ```
266
+
267
+ ## Type Definitions
268
+
269
+ ### RunnerConfig
270
+
271
+ ```typescript
272
+ interface RunnerConfig {
273
+ dotenv?: {
274
+ enabled?: boolean;
275
+ path?: string;
276
+ };
277
+ enforceProductionPropertyRules?: boolean;
278
+ runnerType?: WasmType;
279
+ }
280
+ ```
281
+
282
+ | Field | Type | Default | Description |
283
+ | ---------------------------------- | ---------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
284
+ | `dotenv.enabled` | `boolean` | `false` | Whether to load `.env` files |
285
+ | `dotenv.path` | `string` | `undefined` | Directory to load dotenv files from. When omitted, `fastedge-run` uses the process CWD — correct for most npm package users whose `.env` files live at the project root. Only set this when your dotenv files are in a non-standard location (e.g. test fixture directories). |
286
+ | `enforceProductionPropertyRules` | `boolean` | `true` | Restrict property access to match CDN production behavior |
287
+ | `runnerType` | `WasmType` | auto-detected | Override WASM type detection |
288
+
289
+ ### HttpRequest & HttpResponse
290
+
291
+ ```typescript
292
+ interface HttpRequest {
293
+ path: string;
294
+ method: string;
295
+ headers: Record<string, string>;
296
+ body?: string;
297
+ }
298
+
299
+ interface HttpResponse {
300
+ status: number;
301
+ statusText: string;
302
+ headers: Record<string, string>;
303
+ body: string;
304
+ contentType: string | null;
305
+ isBase64?: boolean;
306
+ logs: Array<{ level: number; message: string }>;
307
+ }
308
+ ```
309
+
310
+ `HttpRequest` and `HttpResponse` are used exclusively with `execute()` on `HttpWasmRunner`.
311
+
312
+ `isBase64` is `true` when the response body is binary content (images, audio, video, PDF, ZIP) encoded as base64.
313
+
314
+ `HttpResponse.logs` contains log entries captured from the `fastedge-run` process stdout/stderr during the request.
315
+
316
+ ### HookCall
317
+
318
+ ```typescript
319
+ type HookCall = {
320
+ hook: string;
321
+ request: {
322
+ headers: HeaderMap;
323
+ body: string;
324
+ method?: string;
325
+ path?: string;
326
+ scheme?: string;
327
+ };
328
+ response: {
329
+ headers: HeaderMap;
330
+ body: string;
331
+ status?: number;
332
+ statusText?: string;
333
+ };
334
+ properties: Record<string, unknown>;
335
+ dotenvEnabled?: boolean;
336
+ enforceProductionPropertyRules?: boolean;
337
+ };
338
+ ```
339
+
340
+ | Field | Description |
341
+ | ---------------------------------- | --------------------------------------------------------------------------------------------------- |
342
+ | `hook` | Hook name: `"onRequestHeaders"`, `"onRequestBody"`, `"onResponseHeaders"`, `"onResponseBody"` |
343
+ | `request` | Request state passed to the hook |
344
+ | `response` | Response state passed to the hook |
345
+ | `properties` | Shared properties (e.g. `request.path`, `vm_config`, `plugin_config`) |
346
+ | `dotenvEnabled` | Optional per-call dotenv override. Use `applyDotenv()` for persistent changes. |
347
+ | `enforceProductionPropertyRules` | Defaults to `true`. Set to `false` to allow property reads that would be blocked on production CDN. |
348
+
349
+ ### HookResult
350
+
351
+ ```typescript
352
+ type HookResult = {
353
+ returnCode: number | null;
354
+ logs: { level: number; message: string }[];
355
+ input: {
356
+ request: { headers: HeaderMap; body: string };
357
+ response: { headers: HeaderMap; body: string };
358
+ properties?: Record<string, unknown>;
359
+ };
360
+ output: {
361
+ request: { headers: HeaderMap; body: string };
362
+ response: { headers: HeaderMap; body: string };
363
+ properties?: Record<string, unknown>;
364
+ };
365
+ properties: Record<string, unknown>;
366
+ };
367
+ ```
368
+
369
+ | Field | Description |
370
+ | ------------ | ------------------------------------------------------------------------------------------- |
371
+ | `returnCode` | The numeric value returned by the WASM hook export, or `null` if the export was not found |
372
+ | `logs` | Log entries emitted via `proxy_log` during hook execution |
373
+ | `input` | Request/response state as seen by the hook before execution |
374
+ | `output` | Request/response state after hook execution (reflects WASM mutations) |
375
+ | `properties` | All shared properties after hook execution |
376
+
377
+ ### FullFlowResult
378
+
379
+ ```typescript
380
+ type FullFlowResult = {
381
+ hookResults: Record<string, HookResult>;
382
+ finalResponse: {
383
+ status: number;
384
+ statusText: string;
385
+ headers: HeaderMap;
386
+ body: string;
387
+ contentType: string;
388
+ isBase64?: boolean;
389
+ };
390
+ calculatedProperties?: Record<string, unknown>;
391
+ };
392
+ ```
393
+
394
+ | Field | Description |
395
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
396
+ | `hookResults` | A `Record` keyed by hook name (`"onRequestHeaders"`, `"onRequestBody"`, `"onResponseHeaders"`, `"onResponseBody"`), each containing a `HookResult` |
397
+ | `finalResponse` | The final response after all hooks have executed, or the local response if a hook short-circuited (see `callFullFlow`). `body` is base64-encoded when `isBase64` is `true`. |
398
+ | `calculatedProperties` | Runtime properties computed from the request URL (e.g. `request.path`, `request.host`) |
399
+
400
+ ### Supporting Types
401
+
402
+ ```typescript
403
+ type WasmType = 'http-wasm' | 'proxy-wasm';
404
+
405
+ type HeaderMap = Record<string, string>;
406
+
407
+ type LogEntry = {
408
+ level: number;
409
+ message: string;
410
+ };
411
+
412
+ enum ProxyStatus {
413
+ Ok = 0,
414
+ NotFound = 1,
415
+ BadArgument = 2,
416
+ }
417
+ ```
418
+
419
+ Log levels follow the proxy-wasm convention: `0` = Trace, `1` = Debug, `2` = Info, `3` = Warn, `4` = Error.
420
+
421
+ `ProxyStatus` represents return values from proxy-wasm host function calls.
422
+
423
+ ## IStateManager
424
+
425
+ `IStateManager` is the event emission interface used by runners to broadcast lifecycle events. In headless/standalone usage, pass `new NullStateManager()` (a no-op implementation) or omit `setStateManager` entirely.
426
+
427
+ ```typescript
428
+ interface IStateManager {
429
+ emitRequestStarted(url, method, headers, source?): void;
430
+ emitHookExecuted(hook, returnCode, logCount, input, output, source?): void;
431
+ emitRequestCompleted(hookResults, finalResponse, calculatedProperties?, source?): void;
432
+ emitRequestFailed(error, details?, source?): void;
433
+ emitWasmLoaded(filename, size, source?): void;
434
+ emitPropertiesUpdated(properties, source?): void;
435
+ emitHttpWasmRequestCompleted(response, source?): void;
436
+ emitHttpWasmLog(log, source?): void;
437
+ emitReloadWorkspaceWasm(path, source?): void;
438
+ }
439
+ ```
440
+
441
+ `EventSource` is `"ui" | "ai_agent" | "api" | "system"`.
442
+
443
+ ## Complete Example
444
+
445
+ ### Proxy-WASM (CDN application)
446
+
447
+ ```typescript
448
+ import { createRunner } from '@gcoredev/fastedge-test';
449
+ import type { FullFlowResult, HookResult } from '@gcoredev/fastedge-test';
450
+
451
+ async function testCdnApp() {
452
+ const runner = await createRunner('./my-cdn-app.wasm', {
453
+ dotenv: { enabled: true },
454
+ enforceProductionPropertyRules: true,
455
+ });
456
+
457
+ try {
458
+ // Execute the full CDN request/response lifecycle
459
+ const result: FullFlowResult = await runner.callFullFlow(
460
+ 'https://example.com/api/data', // request URL
461
+ 'GET', // method
462
+ { 'accept': 'application/json' }, // request headers
463
+ '', // request body
464
+ { 'content-type': 'application/json' }, // upstream response headers
465
+ '{"key":"value"}', // upstream response body
466
+ 200, // upstream response status
467
+ 'OK', // upstream response status text
468
+ {
469
+ 'request.path': '/api/data',
470
+ 'request.host': 'example.com',
471
+ }, // shared properties
472
+ true, // enforce production property rules
473
+ );
474
+
475
+ // Inspect hook results
476
+ const requestHeaders: HookResult = result.hookResults['onRequestHeaders'];
477
+ console.log('onRequestHeaders returnCode:', requestHeaders.returnCode);
478
+ console.log('Logs:', requestHeaders.logs);
479
+
480
+ // Inspect final response
481
+ console.log('Final status:', result.finalResponse.status);
482
+ console.log('Final body:', result.finalResponse.body);
483
+ console.log('Calculated properties:', result.calculatedProperties);
484
+
485
+ // Test a single hook in isolation
486
+ const hookResult = await runner.callHook({
487
+ hook: 'onRequestHeaders',
488
+ request: {
489
+ headers: { 'x-custom': 'value' },
490
+ body: '',
491
+ method: 'POST',
492
+ path: '/api/submit',
493
+ scheme: 'https',
494
+ },
495
+ response: {
496
+ headers: {},
497
+ body: '',
498
+ status: 200,
499
+ statusText: 'OK',
500
+ },
501
+ properties: {
502
+ 'request.host': 'example.com',
503
+ },
504
+ enforceProductionPropertyRules: false,
505
+ });
506
+
507
+ console.log('Hook output headers:', hookResult.output.request.headers);
508
+ } finally {
509
+ await runner.cleanup();
510
+ }
511
+ }
512
+
513
+ testCdnApp();
514
+ ```
515
+
516
+ ### HTTP-WASM (standard HTTP application)
517
+
518
+ ```typescript
519
+ import { createRunner } from '@gcoredev/fastedge-test';
520
+ import type { HttpResponse } from '@gcoredev/fastedge-test';
521
+
522
+ async function testHttpApp() {
523
+ const runner = await createRunner('./my-http-app.wasm', {
524
+ dotenv: { enabled: true },
525
+ });
526
+
527
+ try {
528
+ const response: HttpResponse = await runner.execute({
529
+ path: '/api/hello',
530
+ method: 'GET',
531
+ headers: {
532
+ 'accept': 'application/json',
533
+ 'x-request-id': 'test-123',
534
+ },
535
+ });
536
+
537
+ console.log('Status:', response.status, response.statusText);
538
+ console.log('Content-Type:', response.contentType);
539
+ console.log('Body:', response.body);
540
+ console.log('Process logs:', response.logs);
541
+
542
+ // POST request with body
543
+ const postResponse = await runner.execute({
544
+ path: '/api/data',
545
+ method: 'POST',
546
+ headers: { 'content-type': 'application/json' },
547
+ body: JSON.stringify({ name: 'test' }),
548
+ });
549
+
550
+ console.log('POST status:', postResponse.status);
551
+ } finally {
552
+ await runner.cleanup();
553
+ }
554
+ }
555
+
556
+ testHttpApp();
557
+ ```
558
+
559
+ ### Using a buffer and runtime type detection
560
+
561
+ ```typescript
562
+ import { createRunnerFromBuffer } from '@gcoredev/fastedge-test';
563
+ import { readFile } from 'fs/promises';
564
+
565
+ async function runFromBuffer() {
566
+ const buffer = await readFile('./app.wasm');
567
+
568
+ // Auto-detect type
569
+ const runner = await createRunnerFromBuffer(buffer);
570
+ console.log('Detected type:', runner.getType()); // "proxy-wasm" or "http-wasm"
571
+
572
+ await runner.cleanup();
573
+ }
574
+
575
+ runFromBuffer();
576
+ ```
577
+
578
+ ## See Also
579
+
580
+ - [TEST_FRAMEWORK.md](TEST_FRAMEWORK.md) — High-level test framework built on top of this runner API
581
+ - [API.md](API.md) — REST endpoints for running tests via HTTP
582
+ - [DEBUGGER.md](DEBUGGER.md) — Debugger server and WebSocket protocol