@gcoredev/fastedge-test 0.1.6 → 0.1.7

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/docs/API.md CHANGED
@@ -93,16 +93,19 @@ Loads a WASM binary into the runner. Accepts either a file path or a base64-enco
93
93
 
94
94
  **Request Body**
95
95
 
96
- Exactly one of `wasmPath` or `wasmBase64` must be provided.
96
+ Exactly one of `wasmPath` or `wasmBase64` must be provided; providing both is an error.
97
97
 
98
98
  ```typescript
99
99
  {
100
- wasmPath?: string; // Absolute path to a .wasm file on the server filesystem
101
- wasmBase64?: string; // Base64-encoded WASM binary
100
+ wasmPath?: string; // Absolute path to a .wasm file on the server filesystem
101
+ wasmBase64?: string; // Base64-encoded WASM binary; mutually exclusive with wasmPath
102
102
  dotenv?: {
103
- enabled: boolean; // Whether to load .env files for this module
104
- path?: string; // Directory containing .env files (defaults to server CWD)
103
+ enabled?: boolean; // Whether to load .env files for this module
104
+ path?: string; // Directory containing .env files (defaults to server CWD)
105
105
  };
106
+ httpPort?: number; // HTTP-WASM only. Pin the runner subprocess to this port (1024–65535).
107
+ // Load fails immediately if the port is already in use.
108
+ // Ignored for proxy-wasm modules.
106
109
  }
107
110
  ```
108
111
 
@@ -153,11 +156,31 @@ curl -X POST http://localhost:5179/api/load \
153
156
  }
154
157
  ```
155
158
 
159
+ **Example — pin HTTP-WASM to a specific port**
160
+
161
+ ```bash
162
+ curl -X POST http://localhost:5179/api/load \
163
+ -H "Content-Type: application/json" \
164
+ -d '{
165
+ "wasmPath": "/home/user/project/build/app.wasm",
166
+ "httpPort": 8100
167
+ }'
168
+ ```
169
+
170
+ ```json
171
+ {
172
+ "ok": true,
173
+ "wasmType": "http-wasm",
174
+ "resolvedPath": "/home/user/project/build/app.wasm"
175
+ }
176
+ ```
177
+
156
178
  **Error Responses**
157
179
 
158
180
  | Status | Condition |
159
181
  | ------ | ----------------------------------------------------------------------------------------------------------- |
160
182
  | `400` | Validation failed, missing both `wasmPath` and `wasmBase64`, invalid path, or path does not end in `.wasm` |
183
+ | `400` | `httpPort` is specified and already in use (HTTP-WASM only) |
161
184
  | `500` | WASM load failed or runner initialization error |
162
185
 
163
186
  ---
@@ -703,6 +726,7 @@ type HttpWasmConfig = {
703
726
  description?: string;
704
727
  appType: "http-wasm";
705
728
  wasm?: { path: string; description?: string };
729
+ httpPort?: number; // Pin the runner subprocess to this port (1024–65535)
706
730
  request: {
707
731
  method: string;
708
732
  path: string;
package/docs/DEBUGGER.md CHANGED
@@ -71,7 +71,7 @@ process.kill(process.pid, "SIGTERM");
71
71
  PORT=8080 npx fastedge-debug
72
72
  ```
73
73
 
74
- If the preferred port is already in use, the server tries the next port sequentially, repeating up to 10 times (for example, `5179` through `5188` by default). If no free port is found in that range, the server exits with an error. Set `PORT` to a specific value to bypass auto-increment when a predictable port is required.
74
+ If the preferred port is already in use, the server tries the next port sequentially, up to 50 ports (for example, `5179` through `5228` by default). If no free port is found in that range, the server exits with an error. Set `PORT` to a specific value to bypass auto-increment when a predictable port is required.
75
75
 
76
76
  The server writes the bound port number to `.fastedge-debug/.debug-port` under `WORKSPACE_PATH` (if set) or the current working directory, and deletes the file on shutdown. Use this file for programmatic port discovery when starting the server as a subprocess.
77
77
 
package/docs/RUNNER.md CHANGED
@@ -126,6 +126,14 @@ load(bufferOrPath: Buffer | string, config?: RunnerConfig): Promise<void>
126
126
 
127
127
  Calling `load()` again on the same runner replaces the current module and restarts any underlying process.
128
128
 
129
+ **`httpPort` pinning (HTTP-WASM only).** When `config.httpPort` is set, the spawned `fastedge-run` process is bound to that specific port instead of allocating from the dynamic pool (8100–8199). If the port is already in use, `load()` throws:
130
+
131
+ ```
132
+ fastedge-run port <N> is not available — release it or choose a different httpPort in fastedge-config.test.json
133
+ ```
134
+
135
+ There is no fallback to dynamic allocation — pinning is only useful if the address is stable. Intended for Codespaces/Docker port-forwarding setups, live-preview URLs, or external tooling that requires a fixed target. For proxy-wasm runners, `httpPort` is ignored.
136
+
129
137
  ### execute (HTTP-WASM)
130
138
 
131
139
  Executes an HTTP request through the WASM module. Only available on `HttpWasmRunner` (http-wasm). Calling this on a `ProxyWasmRunner` throws.
@@ -134,7 +142,33 @@ Executes an HTTP request through the WASM module. Only available on `HttpWasmRun
134
142
  execute(request: HttpRequest): Promise<HttpResponse>
135
143
  ```
136
144
 
137
- The runner forwards the request to the locally spawned `fastedge-run` process and returns the response including any logs captured from the process.
145
+ `request.path` is a path on the locally spawned `fastedge-run` server, not a full URL. The runner forwards the request to that local process and returns the response including logs captured from the process stdout/stderr.
146
+
147
+ **Redirects are not followed.** The underlying fetch uses `redirect: "manual"`, so 3xx responses reach the caller intact — status code and `Location` header — rather than being transparently followed. This matches FastEdge edge behavior, where redirects are returned to the client rather than followed server-side, and lets tests assert on redirect status and `Location`.
148
+
149
+ To follow a redirect, inspect `response.headers.location` and handle by `Location` shape:
150
+
151
+ - **Relative Location** (e.g. `/new-path`) — reuse directly as `request.path`.
152
+ - **Absolute same-host Location** (e.g. `http://localhost:8100/new-path`) — parse via `new URL()` and re-issue with `pathname + search` as `request.path`.
153
+ - **Absolute cross-host Location** (e.g. `https://other.example.com/`) — cannot be followed through the runner; the 3xx response is the terminal state for the test.
154
+
155
+ ```typescript
156
+ import { createRunner } from '@gcoredev/fastedge-test';
157
+
158
+ const runner = await createRunner('./my-http-app.wasm');
159
+
160
+ // Relative Location: reuse directly as path
161
+ let response = await runner.execute({ path: '/moved', method: 'GET', headers: {} });
162
+ if (response.status >= 300 && response.status < 400 && response.headers['location']) {
163
+ response = await runner.execute({
164
+ path: response.headers['location'], // e.g. "/new-location"
165
+ method: 'GET',
166
+ headers: {},
167
+ });
168
+ }
169
+
170
+ await runner.cleanup();
171
+ ```
138
172
 
139
173
  ### callHook (Proxy-WASM)
140
174
 
@@ -169,18 +203,18 @@ callFullFlow(
169
203
 
170
204
  **Parameters**
171
205
 
172
- | Parameter | Type | Description |
173
- | -------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------- |
174
- | `url` | `string` | Full request URL, or `BUILTIN_SHORTHAND` (`"built-in"`) to use the built-in responder instead of a real origin fetch |
175
- | `method` | `string` | HTTP method |
176
- | `headers` | `Record<string, string>` | Request headers |
177
- | `body` | `string` | Request body |
178
- | `responseHeaders` | `Record<string, string>` | Upstream response headers (used as initial state for response hooks) |
179
- | `responseBody` | `string` | Upstream response body |
180
- | `responseStatus` | `number` | Upstream response status code |
181
- | `responseStatusText` | `string` | Upstream response status text |
182
- | `properties` | `Record<string, unknown>` | Shared properties passed to all hooks |
183
- | `enforceProductionPropertyRules` | `boolean` | When `true`, restricts property access to match CDN production behavior |
206
+ | Parameter | Type | Description |
207
+ | -------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
208
+ | `url` | `string` | Full request URL, or `BUILTIN_SHORTHAND` (`"built-in"`) to use the built-in responder instead of a real origin fetch |
209
+ | `method` | `string` | HTTP method |
210
+ | `headers` | `Record<string, string>` | Request headers |
211
+ | `body` | `string` | Request body |
212
+ | `responseHeaders` | `Record<string, string>` | Upstream response headers (used as initial state for response hooks) |
213
+ | `responseBody` | `string` | Upstream response body |
214
+ | `responseStatus` | `number` | Upstream response status code |
215
+ | `responseStatusText` | `string` | Upstream response status text |
216
+ | `properties` | `Record<string, unknown>` | Shared properties passed to all hooks |
217
+ | `enforceProductionPropertyRules` | `boolean` | When `true`, restricts property access to match CDN production behavior |
184
218
 
185
219
  Hook execution order: `onRequestHeaders` → `onRequestBody` → *(real HTTP fetch or built-in responder)* → `onResponseHeaders` → `onResponseBody`.
186
220
 
@@ -272,14 +306,15 @@ When testing proxy-wasm modules without a real origin server, pass `BUILTIN_SHOR
272
306
 
273
307
  ```typescript
274
308
  import { createRunner, BUILTIN_SHORTHAND } from '@gcoredev/fastedge-test';
309
+ import type { FullFlowResult } from '@gcoredev/fastedge-test';
275
310
 
276
311
  const runner = await createRunner('./my-cdn-app.wasm');
277
- const result = await runner.callFullFlow(
312
+ const result: FullFlowResult = await runner.callFullFlow(
278
313
  BUILTIN_SHORTHAND, // no origin fetch
279
314
  'GET',
280
315
  { 'accept': 'application/json' },
281
316
  '',
282
- {}, '', 200, 'OK', {}, true
317
+ {}, '', 200, 'OK', {}, true,
283
318
  );
284
319
  ```
285
320
 
@@ -306,15 +341,17 @@ interface RunnerConfig {
306
341
  };
307
342
  enforceProductionPropertyRules?: boolean;
308
343
  runnerType?: WasmType;
344
+ httpPort?: number;
309
345
  }
310
346
  ```
311
347
 
312
- | Field | Type | Default | Description |
313
- | -------------------------------- | ---------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
314
- | `dotenv.enabled` | `boolean` | `false` | Whether to load `.env` files |
315
- | `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. a test fixture directory). |
316
- | `enforceProductionPropertyRules` | `boolean` | `true` | Restrict property access to match CDN production behavior |
317
- | `runnerType` | `WasmType` | auto-detected | Override WASM type detection |
348
+ | Field | Type | Default | Description |
349
+ | -------------------------------- | ---------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
350
+ | `dotenv.enabled` | `boolean` | `false` | Whether to load `.env` files |
351
+ | `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. a test fixture directory). |
352
+ | `enforceProductionPropertyRules` | `boolean` | `true` | Restrict property access to match CDN production behavior |
353
+ | `runnerType` | `WasmType` | auto-detected | Override WASM type detection |
354
+ | `httpPort` | `number` | `undefined` | HTTP-WASM only. Pin the spawned `fastedge-run` subprocess to a specific port instead of allocating from the dynamic pool (8100–8199). `load()` throws if the port is busy — there is no fallback to dynamic allocation. Intended for Codespaces/Docker port-forwarding or external tooling requiring a fixed address. Ignored for proxy-wasm runners. |
318
355
 
319
356
  ### HttpRequest & HttpResponse
320
357
 
@@ -538,19 +575,19 @@ async function testCdnApp() {
538
575
  try {
539
576
  // Execute the full CDN request/response lifecycle
540
577
  const result: FullFlowResult = await runner.callFullFlow(
541
- 'https://example.com/api/data', // request URL
542
- 'GET', // method
543
- { 'accept': 'application/json' }, // request headers
544
- '', // request body
545
- { 'content-type': 'application/json' }, // upstream response headers
546
- '{"key":"value"}', // upstream response body
547
- 200, // upstream response status
548
- 'OK', // upstream response status text
578
+ 'https://example.com/api/data', // request URL
579
+ 'GET', // method
580
+ { 'accept': 'application/json' }, // request headers
581
+ '', // request body
582
+ { 'content-type': 'application/json' }, // upstream response headers
583
+ '{"key":"value"}', // upstream response body
584
+ 200, // upstream response status
585
+ 'OK', // upstream response status text
549
586
  {
550
587
  'request.path': '/api/data',
551
588
  'request.host': 'example.com',
552
- }, // shared properties
553
- true, // enforce production property rules
589
+ }, // shared properties
590
+ true, // enforce production property rules
554
591
  );
555
592
 
556
593
  // Inspect hook results
@@ -606,7 +643,7 @@ async function testCdnAppOffline() {
606
643
  try {
607
644
  // Use built-in responder — no origin server required
608
645
  const result: FullFlowResult = await runner.callFullFlow(
609
- BUILTIN_SHORTHAND, // generates a local response instead of fetching
646
+ BUILTIN_SHORTHAND, // generates a local response instead of fetching
610
647
  'GET',
611
648
  { 'accept': 'application/json' },
612
649
  '',
@@ -22,27 +22,28 @@ The config schema is a union of two variants selected by `appType`:
22
22
 
23
23
  ### Top-Level Fields
24
24
 
25
- | JSON Path | Type | Required (Schema) | Default | Description |
26
- | -------------------- | --------- | -------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- |
27
- | `$schema` | `string` | No | — | URI pointing to the JSON Schema file for IDE autocompletion and validation. |
28
- | `description` | `string` | No | — | Human-readable label for this test scenario. |
29
- | `wasm` | `object` | No | — | WASM binary configuration. Required when running without a programmatic `wasmBuffer`. |
30
- | `wasm.path` | `string` | Yes (if `wasm` present) | — | Path to the compiled `.wasm` binary, relative to the config file or absolute. |
31
- | `wasm.description` | `string` | No | — | Human-readable label for the WASM binary. |
32
- | `appType` | `string` | Yes (schema) / CDN has runtime default | `"proxy-wasm"` | App variant. `"proxy-wasm"` for CDN mode; `"http-wasm"` for HTTP mode. HTTP-WASM has no default. |
33
- | `request` | `object` | **Yes** | — | Incoming HTTP request to simulate. |
34
- | `request.method` | `string` | Yes (schema) / runtime default | `"GET"` | HTTP method (e.g. `"GET"`, `"POST"`). |
35
- | `request.url` | `string` | **Yes** (CDN only) | — | Full URL for the simulated upstream request (e.g. `"https://example.com/api"`). CDN mode only. |
36
- | `request.path` | `string` | **Yes** (HTTP-WASM only) | — | Request path (e.g. `"/api/submit"`). HTTP-WASM mode only. The WASM module acts as the origin server. |
37
- | `request.headers` | `object` | Yes (schema) / runtime default | `{}` | Key/value map of request headers. All keys and values must be strings. |
38
- | `request.body` | `string` | Yes (schema) / runtime default | `""` | Request body as a plain string. Use an empty string for requests with no body. |
39
- | `response` | `object` | No | — | Mock origin response for CDN mode. Not applicable to HTTP-WASM. |
40
- | `response.headers` | `object` | Yes (if `response` present) | `{}` | Key/value map of mock origin response headers. |
41
- | `response.body` | `string` | Yes (if `response` present) | `""` | Mock origin response body as a plain string. |
42
- | `properties` | `object` | **Yes** (schema) / runtime default | `{}` | CDN property key/value pairs passed to the WASM execution context. Values may be any JSON type. |
43
- | `dotenv` | `object` | No | — | Dotenv file loading configuration. |
44
- | `dotenv.enabled` | `boolean` | No | — | Whether to load a `.env` file before execution. |
45
- | `dotenv.path` | `string` | No | — | Path to the `.env` file. If omitted, resolves `.env` relative to the config file directory. |
25
+ | JSON Path | Type | Required (Schema) | Default | Description |
26
+ | -------------------- | --------- | -------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
27
+ | `$schema` | `string` | No | — | URI pointing to the JSON Schema file for IDE autocompletion and validation. |
28
+ | `description` | `string` | No | — | Human-readable label for this test scenario. |
29
+ | `wasm` | `object` | No | — | WASM binary configuration. Required when running without a programmatic `wasmBuffer`. |
30
+ | `wasm.path` | `string` | Yes (if `wasm` present) | — | Path to the compiled `.wasm` binary, relative to the config file or absolute. |
31
+ | `wasm.description` | `string` | No | — | Human-readable label for the WASM binary. |
32
+ | `appType` | `string` | Yes (schema) / CDN has runtime default | `"proxy-wasm"` | App variant. `"proxy-wasm"` for CDN mode; `"http-wasm"` for HTTP mode. HTTP-WASM has no default. |
33
+ | `request` | `object` | **Yes** | — | Incoming HTTP request to simulate. |
34
+ | `request.method` | `string` | Yes (schema) / runtime default | `"GET"` | HTTP method (e.g. `"GET"`, `"POST"`). |
35
+ | `request.url` | `string` | **Yes** (CDN only) | — | Full URL for the simulated upstream request (e.g. `"https://example.com/api"`). CDN mode only. |
36
+ | `request.path` | `string` | **Yes** (HTTP-WASM only) | — | Request path (e.g. `"/api/submit"`). HTTP-WASM mode only. The WASM module acts as the origin server. |
37
+ | `request.headers` | `object` | Yes (schema) / runtime default | `{}` | Key/value map of request headers. All keys and values must be strings. |
38
+ | `request.body` | `string` | Yes (schema) / runtime default | `""` | Request body as a plain string. Use an empty string for requests with no body. |
39
+ | `response` | `object` | No | — | Mock origin response for CDN mode. Not applicable to HTTP-WASM. |
40
+ | `response.headers` | `object` | Yes (if `response` present) | `{}` | Key/value map of mock origin response headers. |
41
+ | `response.body` | `string` | Yes (if `response` present) | `""` | Mock origin response body as a plain string. |
42
+ | `properties` | `object` | **Yes** (schema) / runtime default | `{}` | CDN property key/value pairs passed to the WASM execution context. Values may be any JSON type. |
43
+ | `dotenv` | `object` | No | — | Dotenv file loading configuration. |
44
+ | `dotenv.enabled` | `boolean` | No | — | Whether to load a `.env` file before execution. |
45
+ | `dotenv.path` | `string` | No | — | Path to the `.env` file. If omitted, resolves `.env` relative to the config file directory. |
46
+ | `httpPort` | `integer` | No | — | HTTP-WASM only. Pin the subprocess to a specific port (1024–65535) instead of dynamic allocation from the 8100–8199 pool. Throws if the port is busy. |
46
47
 
47
48
  ### Required vs. Default Distinction
48
49
 
@@ -64,6 +65,55 @@ When `dotenv.enabled` is `true`, the runner loads a `.env` file and merges its c
64
65
 
65
66
  **Security note**: Do not commit `.env` files containing secrets. Add `.env` to `.gitignore` and use `dotenv.enabled: true` with `dotenv.path` pointing to a file that exists only in the local or CI environment.
66
67
 
68
+ ## HTTP Port Pinning
69
+
70
+ `httpPort` is an optional integer field available only in HTTP-WASM configs (`appType: "http-wasm"`). It pins the `fastedge-run` subprocess to a fixed port instead of dynamically allocating one from the pool (8100–8199). The schema rejects `httpPort` on proxy-wasm configs.
71
+
72
+ ### Default Behaviour
73
+
74
+ Without `httpPort`, the runner allocates a port from the 8100–8199 range for each test run. The allocated port is not stable across runs.
75
+
76
+ ### When to Use Port Pinning
77
+
78
+ - **Codespaces / Docker port-forwarding**: Port-forward rules reference a specific port number. Without pinning, the port changes between runs and breaks the forwarding rule.
79
+ - **Stable live-preview URLs**: Browser tabs or external tooling pointing at a fixed URL (e.g. `http://localhost:8250`) require a stable port.
80
+ - **External tooling integration**: Monitoring, load testing, or proxy configurations that hard-code a target address.
81
+
82
+ ### Fail-Fast Semantics
83
+
84
+ If the pinned port is already in use, `HttpWasmRunner.load()` throws immediately:
85
+
86
+ ```
87
+ fastedge-run port 8250 is not available — release it or choose a different httpPort in fastedge-config.test.json
88
+ ```
89
+
90
+ There is no fallback to dynamic allocation. Free the port or choose a different one before running.
91
+
92
+ ### Port Range Considerations
93
+
94
+ Pinning a port inside the 8100–8199 dynamic pool is allowed by the schema but risks collisions with other concurrent debug sessions using dynamic allocation. For production-like setups or shared environments, choose a port outside the pool (e.g. 8250 or any unused port above 8199).
95
+
96
+ ### Example
97
+
98
+ ```json
99
+ {
100
+ "$schema": "./node_modules/@gcoredev/fastedge-test/schemas/fastedge-config.test.schema.json",
101
+ "description": "HTTP-WASM handler with pinned port for Codespaces",
102
+ "appType": "http-wasm",
103
+ "wasm": {
104
+ "path": "./dist/http-handler.wasm"
105
+ },
106
+ "request": {
107
+ "method": "GET",
108
+ "path": "/health",
109
+ "headers": {},
110
+ "body": ""
111
+ },
112
+ "properties": {},
113
+ "httpPort": 8250
114
+ }
115
+ ```
116
+
67
117
  ## Examples
68
118
 
69
119
  ### Minimal CDN Configuration
@@ -265,6 +315,7 @@ type HttpConfig = {
265
315
  headers: Record<string, string>; // default: {}
266
316
  body: string; // default: ""
267
317
  };
318
+ httpPort?: number; // pin subprocess port (1024–65535); throws if busy
268
319
  properties: Record<string, unknown>; // default: {}
269
320
  dotenv?: {
270
321
  enabled?: boolean;
@@ -285,6 +336,7 @@ if (config.appType === "proxy-wasm") {
285
336
  console.log(config.response); // ResponseConfig | undefined
286
337
  } else {
287
338
  console.log(config.request.path); // string — HTTP-WASM path
339
+ console.log(config.httpPort); // number | undefined
288
340
  }
289
341
  ```
290
342
 
@@ -108,15 +108,15 @@ Object-based options for `runFlow()`. HTTP/2 pseudo-headers (`:method`, `:path`,
108
108
  ```typescript
109
109
  interface FlowOptions {
110
110
  url: string;
111
- method?: string; // Default: "GET"
111
+ method?: string; // Default: "GET"
112
112
  requestHeaders?: Record<string, string>;
113
- requestBody?: string; // Default: ""
114
- responseStatus?: number; // Default: 200
115
- responseStatusText?: string; // Default: "OK"
116
- responseHeaders?: Record<string, string>; // Default: {}
117
- responseBody?: string; // Default: ""
118
- properties?: Record<string, unknown>; // Default: {}
119
- enforceProductionPropertyRules?: boolean; // Default: true
113
+ requestBody?: string; // Default: ""
114
+ responseStatus?: number; // Default: 200
115
+ responseStatusText?: string; // Default: "OK"
116
+ responseHeaders?: Record<string, string>; // Default: {}
117
+ responseBody?: string; // Default: ""
118
+ properties?: Record<string, unknown>; // Default: {}
119
+ enforceProductionPropertyRules?: boolean; // Default: true
120
120
  }
121
121
  ```
122
122
 
@@ -135,7 +135,7 @@ interface FlowOptions {
135
135
 
136
136
  ### HttpRequestOptions
137
137
 
138
- Object-based options for `runHttpRequest()`. Used with HTTP WASM apps (as opposed to CDN proxy-wasm filter apps).
138
+ Object-based options for `runHttpRequest()`. Used with HTTP WASM apps (as opposed to CDN proxy-wasm filter apps tested with `runFlow`).
139
139
 
140
140
  ```typescript
141
141
  interface HttpRequestOptions {
@@ -243,24 +243,28 @@ Executes a complete request/response flow through the WASM filter. Object-based
243
243
  The returned `FullFlowResult` has this shape:
244
244
 
245
245
  ```typescript
246
- interface FullFlowResult {
246
+ type FullFlowResult = {
247
247
  hookResults: Record<string, HookResult>; // keyed by camelCase hook name
248
248
  finalResponse: {
249
249
  status: number;
250
+ statusText: string;
250
251
  headers: Record<string, string>;
251
252
  body: string;
253
+ contentType: string;
254
+ isBase64?: boolean;
252
255
  };
253
- }
256
+ calculatedProperties?: Record<string, unknown>;
257
+ };
254
258
  ```
255
259
 
256
260
  Hook results are accessed by camelCase key:
257
261
 
258
- | Key | Hook |
259
- | ------------------- | -------------------------- |
260
- | `onRequestHeaders` | `on_request_headers` hook |
261
- | `onRequestBody` | `on_request_body` hook |
262
- | `onResponseHeaders` | `on_response_headers` hook |
263
- | `onResponseBody` | `on_response_body` hook |
262
+ | Key | Hook |
263
+ | -------------------- | -------------------------- |
264
+ | `onRequestHeaders` | `on_request_headers` hook |
265
+ | `onRequestBody` | `on_request_body` hook |
266
+ | `onResponseHeaders` | `on_response_headers` hook |
267
+ | `onResponseBody` | `on_response_body` hook |
264
268
 
265
269
  ```typescript
266
270
  const result = await runFlow(runner, {
@@ -277,7 +281,8 @@ const reqHook = result.hookResults.onRequestHeaders;
277
281
  const resHook = result.hookResults.onResponseHeaders;
278
282
 
279
283
  // Access final response
280
- console.log(result.finalResponse.status); // 201
284
+ console.log(result.finalResponse.status); // 201
285
+ console.log(result.finalResponse.contentType); // e.g. "application/json"
281
286
  ```
282
287
 
283
288
  ### runHttpRequest
@@ -288,7 +293,26 @@ function runHttpRequest(runner: IWasmRunner, options: HttpRequestOptions): Promi
288
293
 
289
294
  Executes a single HTTP request through an HTTP WASM app. Object-based wrapper around the runner's `execute` method. Use this for WASM apps that handle HTTP requests directly, as opposed to CDN proxy-wasm filter apps tested with `runFlow`.
290
295
 
296
+ **Redirects are not followed.** The underlying fetch uses `redirect: "manual"`, so a 302 (or any 3xx) returned by the WASM is surfaced verbatim — `response.status` is `302` and `response.headers.location` is preserved. This matches FastEdge edge behaviour, where redirects are returned to the client rather than followed server-side.
297
+
298
+ `runHttpRequest` only targets the WASM app under test. `options.path` is a path on the local `fastedge-run` server, not a full URL. Following a redirect depends on the shape of `response.headers.location`:
299
+
300
+ - **Relative** (e.g. `/auth/complete`) — pass it directly as `path` in a second `runHttpRequest` call.
301
+ - **Absolute, same host** — extract `pathname + search` via `new URL()` and re-issue with that path.
302
+ - **Absolute, external host** — cannot be followed through the runner; assert on status and `Location` and stop there.
303
+
304
+ ```typescript
305
+ // Assert on a 302 redirect and follow it (relative Location)
306
+ const response = await runHttpRequest(runner, { path: "/login" });
307
+ assertHttpStatus(response, 302);
308
+ assertHttpHeader(response, "location", "/dashboard");
309
+
310
+ const redirected = await runHttpRequest(runner, { path: response.headers["location"] });
311
+ assertHttpStatus(redirected, 200);
312
+ ```
313
+
291
314
  ```typescript
315
+ // Standard request
292
316
  const response = await runHttpRequest(runner, {
293
317
  path: "/api/greet",
294
318
  method: "GET",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcoredev/fastedge-test",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,6 +52,11 @@
52
52
  "type": "string",
53
53
  "const": "http-wasm"
54
54
  },
55
+ "httpPort": {
56
+ "type": "integer",
57
+ "minimum": 1024,
58
+ "maximum": 65535
59
+ },
55
60
  "request": {
56
61
  "type": "object",
57
62
  "properties": {
@@ -19,6 +19,11 @@
19
19
  }
20
20
  },
21
21
  "additionalProperties": false
22
+ },
23
+ "httpPort": {
24
+ "type": "integer",
25
+ "minimum": 1024,
26
+ "maximum": 65535
22
27
  }
23
28
  },
24
29
  "additionalProperties": false
@@ -49,6 +49,11 @@
49
49
  "type": "string",
50
50
  "const": "http-wasm"
51
51
  },
52
+ "httpPort": {
53
+ "type": "integer",
54
+ "minimum": 1024,
55
+ "maximum": 65535
56
+ },
52
57
  "request": {
53
58
  "type": "object",
54
59
  "properties": {