@gcoredev/fastedge-test 0.1.5 → 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/dist/frontend/assets/{index-BpdzhbRl.js → index-BCXfEMSq.js} +18 -18
- package/dist/frontend/index.html +1 -1
- package/dist/lib/index.cjs +35 -5
- package/dist/lib/index.js +35 -5
- package/dist/lib/runner/HttpWasmRunner.d.ts +9 -1
- package/dist/lib/runner/IWasmRunner.d.ts +25 -1
- package/dist/lib/runner/PortManager.d.ts +4 -1
- package/dist/lib/schemas/api.d.ts +2 -0
- package/dist/lib/schemas/config.d.ts +2 -0
- package/dist/lib/test-framework/index.cjs +42 -5
- package/dist/lib/test-framework/index.js +42 -5
- package/dist/lib/test-framework/suite-runner.d.ts +16 -0
- package/dist/server.js +13 -13
- package/docs/API.md +29 -5
- package/docs/DEBUGGER.md +1 -1
- package/docs/RUNNER.md +69 -32
- package/docs/TEST_CONFIG.md +73 -21
- package/docs/TEST_FRAMEWORK.md +42 -18
- package/package.json +3 -3
- package/schemas/api-config.schema.json +5 -0
- package/schemas/api-load.schema.json +5 -0
- package/schemas/fastedge-config.test.schema.json +5 -0
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;
|
|
101
|
-
wasmBase64?: string;
|
|
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
|
|
104
|
-
path?: string;
|
|
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,
|
|
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
|
-
|
|
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
|
|
313
|
-
| -------------------------------- | ---------- |
|
|
314
|
-
| `dotenv.enabled` | `boolean` | `false`
|
|
315
|
-
| `dotenv.path` | `string` | `undefined`
|
|
316
|
-
| `enforceProductionPropertyRules` | `boolean` | `true`
|
|
317
|
-
| `runnerType` | `WasmType` | auto-detected
|
|
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',
|
|
542
|
-
'GET',
|
|
543
|
-
{ 'accept': 'application/json' },
|
|
544
|
-
'',
|
|
545
|
-
{ 'content-type': 'application/json' },
|
|
546
|
-
'{"key":"value"}',
|
|
547
|
-
200,
|
|
548
|
-
'OK',
|
|
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
|
-
},
|
|
553
|
-
true,
|
|
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,
|
|
646
|
+
BUILTIN_SHORTHAND, // generates a local response instead of fetching
|
|
610
647
|
'GET',
|
|
611
648
|
{ 'accept': 'application/json' },
|
|
612
649
|
'',
|
package/docs/TEST_CONFIG.md
CHANGED
|
@@ -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
|
|
26
|
-
| -------------------- | --------- | -------------------------------------- |
|
|
27
|
-
| `$schema` | `string` | No | —
|
|
28
|
-
| `description` | `string` | No | —
|
|
29
|
-
| `wasm` | `object` | No | —
|
|
30
|
-
| `wasm.path` | `string` | Yes (if `wasm` present) | —
|
|
31
|
-
| `wasm.description` | `string` | No | —
|
|
32
|
-
| `appType` | `string` | Yes (schema) / CDN has runtime default | `"proxy-wasm"`
|
|
33
|
-
| `request` | `object` | **Yes** | —
|
|
34
|
-
| `request.method` | `string` | Yes (schema) / runtime default | `"GET"`
|
|
35
|
-
| `request.url` | `string` | **Yes** (CDN only) | —
|
|
36
|
-
| `request.path` | `string` | **Yes** (HTTP-WASM only) | —
|
|
37
|
-
| `request.headers` | `object` | Yes (schema) / runtime default | `{}`
|
|
38
|
-
| `request.body` | `string` | Yes (schema) / runtime default | `""`
|
|
39
|
-
| `response` | `object` | No | —
|
|
40
|
-
| `response.headers` | `object` | Yes (if `response` present) | `{}`
|
|
41
|
-
| `response.body` | `string` | Yes (if `response` present) | `""`
|
|
42
|
-
| `properties` | `object` | **Yes** (schema) / runtime default | `{}`
|
|
43
|
-
| `dotenv` | `object` | No | —
|
|
44
|
-
| `dotenv.enabled` | `boolean` | No | —
|
|
45
|
-
| `dotenv.path` | `string` | No | —
|
|
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
|
|
package/docs/TEST_FRAMEWORK.md
CHANGED
|
@@ -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;
|
|
111
|
+
method?: string; // Default: "GET"
|
|
112
112
|
requestHeaders?: Record<string, string>;
|
|
113
|
-
requestBody?: string;
|
|
114
|
-
responseStatus?: number;
|
|
115
|
-
responseStatusText?: string;
|
|
116
|
-
responseHeaders?: Record<string, string>;
|
|
117
|
-
responseBody?: string;
|
|
118
|
-
properties?: Record<string, unknown>;
|
|
119
|
-
enforceProductionPropertyRules?: boolean;
|
|
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
|
-
|
|
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
|
|
259
|
-
|
|
|
260
|
-
| `onRequestHeaders`
|
|
261
|
-
| `onRequestBody`
|
|
262
|
-
| `onResponseHeaders`
|
|
263
|
-
| `onResponseBody`
|
|
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);
|
|
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.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@assemblyscript/wasi-shim": "^0.1.0",
|
|
79
|
-
"@gcoredev/fastedge-sdk-js": "^2.2.
|
|
80
|
-
"@gcoredev/proxy-wasm-sdk-as": "^1.2.
|
|
79
|
+
"@gcoredev/fastedge-sdk-js": "^2.2.2",
|
|
80
|
+
"@gcoredev/proxy-wasm-sdk-as": "^1.2.3",
|
|
81
81
|
"@types/ws": "^8.18.1",
|
|
82
82
|
"express": "^4.19.2",
|
|
83
83
|
"immer": "^11.1.3",
|