@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.
- package/README.md +6 -6
- package/dist/fastedge-cli/METADATA.json +1 -3
- package/dist/fastedge-cli/{fastedge-run-linux-x64-unkown → fastedge-run-darwin-arm64} +0 -0
- package/dist/fastedge-cli/fastedge-run-linux-x64 +0 -0
- package/dist/fastedge-cli/fastedge-run.exe +0 -0
- package/dist/frontend/assets/index-CEFjsU8e.js +35 -0
- package/dist/frontend/assets/index-DdlINQc_.css +1 -0
- package/dist/frontend/index.html +2 -2
- package/dist/lib/index.cjs +299 -107
- package/dist/lib/index.js +301 -110
- package/dist/lib/runner/HostFunctions.d.ts +8 -0
- package/dist/lib/runner/HttpWasmRunner.d.ts +34 -14
- package/dist/lib/runner/IStateManager.d.ts +3 -2
- package/dist/lib/runner/IWasmRunner.d.ts +16 -1
- package/dist/lib/runner/NullStateManager.d.ts +1 -0
- package/dist/lib/runner/PortManager.d.ts +17 -19
- package/dist/lib/runner/ProxyWasmRunner.d.ts +7 -0
- package/dist/lib/schemas/api.d.ts +8 -2
- package/dist/lib/schemas/config.d.ts +4 -1
- package/dist/lib/test-framework/index.cjs +301 -108
- package/dist/lib/test-framework/index.js +303 -111
- package/dist/lib/test-framework/suite-runner.d.ts +1 -1
- package/dist/server.js +30 -29
- package/docs/API.md +758 -360
- package/docs/DEBUGGER.md +151 -0
- package/docs/INDEX.md +111 -0
- package/docs/RUNNER.md +582 -0
- package/docs/TEST_CONFIG.md +242 -0
- package/docs/TEST_FRAMEWORK.md +384 -284
- package/docs/WEBSOCKET.md +499 -0
- package/docs/quickstart.md +171 -0
- package/llms.txt +72 -14
- package/package.json +15 -5
- package/schemas/api-config.schema.json +12 -5
- package/schemas/api-load.schema.json +11 -6
- package/schemas/{test-config.schema.json → fastedge-config.test.schema.json} +12 -5
- package/dist/fastedge-cli/.gitkeep +0 -0
- package/dist/frontend/assets/index-CnXStFTd.css +0 -1
- package/dist/frontend/assets/index-FR9Oqsow.js +0 -37
- package/docs/HYBRID_LOADING.md +0 -546
- 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
|