@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/dist/frontend/assets/{index-BpdzhbRl.js → index-BCXfEMSq.js} +18 -18
- package/dist/frontend/index.html +1 -1
- package/dist/lib/index.cjs +34 -4
- package/dist/lib/index.js +34 -4
- 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 +41 -4
- package/dist/lib/test-framework/index.js +41 -4
- 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 +1 -1
- 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/dist/frontend/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Proxy Runner</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-BCXfEMSq.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-DdlINQc_.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/dist/lib/index.cjs
CHANGED
|
@@ -2861,6 +2861,8 @@ var HttpWasmRunner = class {
|
|
|
2861
2861
|
this.stateManager = null;
|
|
2862
2862
|
this.dotenvEnabled = true;
|
|
2863
2863
|
this.dotenvPath = null;
|
|
2864
|
+
/** Pinned ports bypass PortManager allocation and must not be released back to it. */
|
|
2865
|
+
this.isPinnedPort = false;
|
|
2864
2866
|
/** @deprecated Legacy sync support — remove when #[fastedge::http] is retired */
|
|
2865
2867
|
this.isLegacySync = false;
|
|
2866
2868
|
this.portManager = portManager;
|
|
@@ -2887,7 +2889,19 @@ var HttpWasmRunner = class {
|
|
|
2887
2889
|
this.tempWasmPath = wasmPath;
|
|
2888
2890
|
}
|
|
2889
2891
|
this.isLegacySync = await isLegacySyncWasm(bufferOrPath);
|
|
2890
|
-
|
|
2892
|
+
if (config?.httpPort !== void 0) {
|
|
2893
|
+
const pinned = config.httpPort;
|
|
2894
|
+
if (!await this.portManager.isPortFree(pinned)) {
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
`fastedge-run port ${pinned} is not available \u2014 release it or choose a different httpPort in fastedge-config.test.json`
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
this.port = pinned;
|
|
2900
|
+
this.isPinnedPort = true;
|
|
2901
|
+
} else {
|
|
2902
|
+
this.port = await this.portManager.allocate();
|
|
2903
|
+
this.isPinnedPort = false;
|
|
2904
|
+
}
|
|
2891
2905
|
const wasi_http = !this.isLegacySync;
|
|
2892
2906
|
const args = [
|
|
2893
2907
|
"http",
|
|
@@ -2919,7 +2933,13 @@ var HttpWasmRunner = class {
|
|
|
2919
2933
|
await this.waitForServerReady(this.port, timeout);
|
|
2920
2934
|
}
|
|
2921
2935
|
/**
|
|
2922
|
-
* Execute an HTTP request through the WASM module
|
|
2936
|
+
* Execute an HTTP request through the WASM module.
|
|
2937
|
+
*
|
|
2938
|
+
* Redirects are surfaced verbatim — `fetch` is called with
|
|
2939
|
+
* `redirect: "manual"` so 3xx responses (status + `Location`) reach the
|
|
2940
|
+
* caller intact. This matches FastEdge edge behaviour, which returns
|
|
2941
|
+
* redirects to the client rather than following them server-side. See
|
|
2942
|
+
* `IWasmRunner.execute` for the public contract.
|
|
2923
2943
|
*/
|
|
2924
2944
|
async execute(request) {
|
|
2925
2945
|
if (!this.port || !this.process) {
|
|
@@ -2932,8 +2952,12 @@ var HttpWasmRunner = class {
|
|
|
2932
2952
|
method: request.method,
|
|
2933
2953
|
headers: request.headers,
|
|
2934
2954
|
body: request.body || void 0,
|
|
2935
|
-
signal: AbortSignal.timeout(3e4)
|
|
2955
|
+
signal: AbortSignal.timeout(3e4),
|
|
2936
2956
|
// 30 second timeout
|
|
2957
|
+
// Surface 3xx responses verbatim so tests can assert on status/Location.
|
|
2958
|
+
// A FastEdge edge returns redirects to the client rather than following
|
|
2959
|
+
// them server-side; production parity requires the same here.
|
|
2960
|
+
redirect: "manual"
|
|
2937
2961
|
});
|
|
2938
2962
|
const arrayBuffer = await response.arrayBuffer();
|
|
2939
2963
|
const bodyBuffer = Buffer.from(arrayBuffer);
|
|
@@ -3024,8 +3048,11 @@ var HttpWasmRunner = class {
|
|
|
3024
3048
|
this.process = null;
|
|
3025
3049
|
}
|
|
3026
3050
|
if (this.port !== null) {
|
|
3027
|
-
|
|
3051
|
+
if (!this.isPinnedPort) {
|
|
3052
|
+
this.portManager.release(this.port);
|
|
3053
|
+
}
|
|
3028
3054
|
this.port = null;
|
|
3055
|
+
this.isPinnedPort = false;
|
|
3029
3056
|
}
|
|
3030
3057
|
if (this.tempWasmPath) {
|
|
3031
3058
|
await removeTempWasmFile(this.tempWasmPath);
|
|
@@ -3242,6 +3269,9 @@ var PortManager = class {
|
|
|
3242
3269
|
* This is necessary when multiple server processes run simultaneously —
|
|
3243
3270
|
* each has its own PortManager with independent in-memory state, so
|
|
3244
3271
|
* in-memory tracking alone is not enough to prevent cross-process conflicts.
|
|
3272
|
+
*
|
|
3273
|
+
* Public so pinned-port callers (HttpWasmRunner with RunnerConfig.httpPort)
|
|
3274
|
+
* can reuse the same OS-level check without going through allocate().
|
|
3245
3275
|
*/
|
|
3246
3276
|
isPortFree(port) {
|
|
3247
3277
|
return new Promise((resolve) => {
|
package/dist/lib/index.js
CHANGED
|
@@ -2817,6 +2817,8 @@ var HttpWasmRunner = class {
|
|
|
2817
2817
|
this.stateManager = null;
|
|
2818
2818
|
this.dotenvEnabled = true;
|
|
2819
2819
|
this.dotenvPath = null;
|
|
2820
|
+
/** Pinned ports bypass PortManager allocation and must not be released back to it. */
|
|
2821
|
+
this.isPinnedPort = false;
|
|
2820
2822
|
/** @deprecated Legacy sync support — remove when #[fastedge::http] is retired */
|
|
2821
2823
|
this.isLegacySync = false;
|
|
2822
2824
|
this.portManager = portManager;
|
|
@@ -2843,7 +2845,19 @@ var HttpWasmRunner = class {
|
|
|
2843
2845
|
this.tempWasmPath = wasmPath;
|
|
2844
2846
|
}
|
|
2845
2847
|
this.isLegacySync = await isLegacySyncWasm(bufferOrPath);
|
|
2846
|
-
|
|
2848
|
+
if (config?.httpPort !== void 0) {
|
|
2849
|
+
const pinned = config.httpPort;
|
|
2850
|
+
if (!await this.portManager.isPortFree(pinned)) {
|
|
2851
|
+
throw new Error(
|
|
2852
|
+
`fastedge-run port ${pinned} is not available \u2014 release it or choose a different httpPort in fastedge-config.test.json`
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
this.port = pinned;
|
|
2856
|
+
this.isPinnedPort = true;
|
|
2857
|
+
} else {
|
|
2858
|
+
this.port = await this.portManager.allocate();
|
|
2859
|
+
this.isPinnedPort = false;
|
|
2860
|
+
}
|
|
2847
2861
|
const wasi_http = !this.isLegacySync;
|
|
2848
2862
|
const args = [
|
|
2849
2863
|
"http",
|
|
@@ -2875,7 +2889,13 @@ var HttpWasmRunner = class {
|
|
|
2875
2889
|
await this.waitForServerReady(this.port, timeout);
|
|
2876
2890
|
}
|
|
2877
2891
|
/**
|
|
2878
|
-
* Execute an HTTP request through the WASM module
|
|
2892
|
+
* Execute an HTTP request through the WASM module.
|
|
2893
|
+
*
|
|
2894
|
+
* Redirects are surfaced verbatim — `fetch` is called with
|
|
2895
|
+
* `redirect: "manual"` so 3xx responses (status + `Location`) reach the
|
|
2896
|
+
* caller intact. This matches FastEdge edge behaviour, which returns
|
|
2897
|
+
* redirects to the client rather than following them server-side. See
|
|
2898
|
+
* `IWasmRunner.execute` for the public contract.
|
|
2879
2899
|
*/
|
|
2880
2900
|
async execute(request) {
|
|
2881
2901
|
if (!this.port || !this.process) {
|
|
@@ -2888,8 +2908,12 @@ var HttpWasmRunner = class {
|
|
|
2888
2908
|
method: request.method,
|
|
2889
2909
|
headers: request.headers,
|
|
2890
2910
|
body: request.body || void 0,
|
|
2891
|
-
signal: AbortSignal.timeout(3e4)
|
|
2911
|
+
signal: AbortSignal.timeout(3e4),
|
|
2892
2912
|
// 30 second timeout
|
|
2913
|
+
// Surface 3xx responses verbatim so tests can assert on status/Location.
|
|
2914
|
+
// A FastEdge edge returns redirects to the client rather than following
|
|
2915
|
+
// them server-side; production parity requires the same here.
|
|
2916
|
+
redirect: "manual"
|
|
2893
2917
|
});
|
|
2894
2918
|
const arrayBuffer = await response.arrayBuffer();
|
|
2895
2919
|
const bodyBuffer = Buffer.from(arrayBuffer);
|
|
@@ -2980,8 +3004,11 @@ var HttpWasmRunner = class {
|
|
|
2980
3004
|
this.process = null;
|
|
2981
3005
|
}
|
|
2982
3006
|
if (this.port !== null) {
|
|
2983
|
-
|
|
3007
|
+
if (!this.isPinnedPort) {
|
|
3008
|
+
this.portManager.release(this.port);
|
|
3009
|
+
}
|
|
2984
3010
|
this.port = null;
|
|
3011
|
+
this.isPinnedPort = false;
|
|
2985
3012
|
}
|
|
2986
3013
|
if (this.tempWasmPath) {
|
|
2987
3014
|
await removeTempWasmFile(this.tempWasmPath);
|
|
@@ -3198,6 +3225,9 @@ var PortManager = class {
|
|
|
3198
3225
|
* This is necessary when multiple server processes run simultaneously —
|
|
3199
3226
|
* each has its own PortManager with independent in-memory state, so
|
|
3200
3227
|
* in-memory tracking alone is not enough to prevent cross-process conflicts.
|
|
3228
|
+
*
|
|
3229
|
+
* Public so pinned-port callers (HttpWasmRunner with RunnerConfig.httpPort)
|
|
3230
|
+
* can reuse the same OS-level check without going through allocate().
|
|
3201
3231
|
*/
|
|
3202
3232
|
isPortFree(port) {
|
|
3203
3233
|
return new Promise((resolve) => {
|
|
@@ -24,6 +24,8 @@ export declare class HttpWasmRunner implements IWasmRunner {
|
|
|
24
24
|
private portManager;
|
|
25
25
|
private dotenvEnabled;
|
|
26
26
|
private dotenvPath;
|
|
27
|
+
/** Pinned ports bypass PortManager allocation and must not be released back to it. */
|
|
28
|
+
private isPinnedPort;
|
|
27
29
|
/** @deprecated Legacy sync support — remove when #[fastedge::http] is retired */
|
|
28
30
|
private isLegacySync;
|
|
29
31
|
constructor(portManager: PortManager, dotenvEnabled?: boolean);
|
|
@@ -32,7 +34,13 @@ export declare class HttpWasmRunner implements IWasmRunner {
|
|
|
32
34
|
*/
|
|
33
35
|
load(bufferOrPath: Buffer | string, config?: RunnerConfig): Promise<void>;
|
|
34
36
|
/**
|
|
35
|
-
* Execute an HTTP request through the WASM module
|
|
37
|
+
* Execute an HTTP request through the WASM module.
|
|
38
|
+
*
|
|
39
|
+
* Redirects are surfaced verbatim — `fetch` is called with
|
|
40
|
+
* `redirect: "manual"` so 3xx responses (status + `Location`) reach the
|
|
41
|
+
* caller intact. This matches FastEdge edge behaviour, which returns
|
|
42
|
+
* redirects to the client rather than following them server-side. See
|
|
43
|
+
* `IWasmRunner.execute` for the public contract.
|
|
36
44
|
*/
|
|
37
45
|
execute(request: HttpRequest): Promise<HttpResponse>;
|
|
38
46
|
/**
|
|
@@ -18,6 +18,15 @@ export interface RunnerConfig {
|
|
|
18
18
|
enforceProductionPropertyRules?: boolean;
|
|
19
19
|
/** Override automatic WASM type detection. Use when detection produces wrong results. */
|
|
20
20
|
runnerType?: WasmType;
|
|
21
|
+
/**
|
|
22
|
+
* HTTP-WASM only. Pin the spawned `fastedge-run` HTTP server to a specific
|
|
23
|
+
* port instead of allocating from the dynamic pool (8100-8199). Intended for
|
|
24
|
+
* Codespaces/Docker port-forwarding setups, stable live-preview URLs, or any
|
|
25
|
+
* external tooling that requires a fixed target. `load()` throws if the port
|
|
26
|
+
* is already in use — there is no fallback to dynamic allocation. Ignored
|
|
27
|
+
* for proxy-wasm runners.
|
|
28
|
+
*/
|
|
29
|
+
httpPort?: number;
|
|
21
30
|
}
|
|
22
31
|
/**
|
|
23
32
|
* HTTP Request type for HTTP WASM runner
|
|
@@ -54,7 +63,22 @@ export interface IWasmRunner {
|
|
|
54
63
|
*/
|
|
55
64
|
load(bufferOrPath: Buffer | string, config?: RunnerConfig): Promise<void>;
|
|
56
65
|
/**
|
|
57
|
-
* Execute a request through the WASM module (HTTP WASM only)
|
|
66
|
+
* Execute a request through the WASM module (HTTP WASM only).
|
|
67
|
+
*
|
|
68
|
+
* Redirects are surfaced verbatim — the underlying fetch uses
|
|
69
|
+
* `redirect: "manual"` so tests can assert on 3xx status and the `Location`
|
|
70
|
+
* header. This matches how a FastEdge edge deployment returns redirects to
|
|
71
|
+
* the client rather than following them server-side.
|
|
72
|
+
*
|
|
73
|
+
* `execute` only hits the WASM app under test — `request.path` is a path on
|
|
74
|
+
* the spawned `fastedge-run` server, not a full URL. To follow a redirect
|
|
75
|
+
* the caller must inspect `response.headers.location`:
|
|
76
|
+
* - Relative Location (`/foo`) — reuse as `request.path` directly.
|
|
77
|
+
* - Absolute same-host Location — extract `pathname + search` via `new URL()`
|
|
78
|
+
* and re-issue with that path.
|
|
79
|
+
* - Absolute cross-host Location — cannot be followed through the runner;
|
|
80
|
+
* the 302 is the terminal state for the test.
|
|
81
|
+
*
|
|
58
82
|
* @param request The HTTP request to execute
|
|
59
83
|
* @returns The HTTP response
|
|
60
84
|
*/
|
|
@@ -18,8 +18,11 @@ export declare class PortManager {
|
|
|
18
18
|
* This is necessary when multiple server processes run simultaneously —
|
|
19
19
|
* each has its own PortManager with independent in-memory state, so
|
|
20
20
|
* in-memory tracking alone is not enough to prevent cross-process conflicts.
|
|
21
|
+
*
|
|
22
|
+
* Public so pinned-port callers (HttpWasmRunner with RunnerConfig.httpPort)
|
|
23
|
+
* can reuse the same OS-level check without going through allocate().
|
|
21
24
|
*/
|
|
22
|
-
|
|
25
|
+
isPortFree(port: number): Promise<boolean>;
|
|
23
26
|
/**
|
|
24
27
|
* Allocate an available port from the pool.
|
|
25
28
|
* Combines in-memory tracking (avoids TCP TIME_WAIT reuse within this process)
|
|
@@ -6,6 +6,7 @@ export declare const ApiLoadBodySchema: z.ZodObject<{
|
|
|
6
6
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
7
7
|
path: z.ZodOptional<z.ZodString>;
|
|
8
8
|
}, z.core.$strip>>;
|
|
9
|
+
httpPort: z.ZodOptional<z.ZodNumber>;
|
|
9
10
|
}, z.core.$strip>;
|
|
10
11
|
export declare const ApiSendBodySchema: z.ZodObject<{
|
|
11
12
|
url: z.ZodUnion<readonly [z.ZodLiteral<"built-in">, z.ZodString]>;
|
|
@@ -52,6 +53,7 @@ export declare const ApiConfigBodySchema: z.ZodObject<{
|
|
|
52
53
|
path: z.ZodOptional<z.ZodString>;
|
|
53
54
|
}, z.core.$strip>>;
|
|
54
55
|
appType: z.ZodLiteral<"http-wasm">;
|
|
56
|
+
httpPort: z.ZodOptional<z.ZodNumber>;
|
|
55
57
|
request: z.ZodObject<{
|
|
56
58
|
method: z.ZodDefault<z.ZodString>;
|
|
57
59
|
path: z.ZodString;
|
|
@@ -56,6 +56,7 @@ declare const HttpConfigSchema: z.ZodObject<{
|
|
|
56
56
|
path: z.ZodOptional<z.ZodString>;
|
|
57
57
|
}, z.core.$strip>>;
|
|
58
58
|
appType: z.ZodLiteral<"http-wasm">;
|
|
59
|
+
httpPort: z.ZodOptional<z.ZodNumber>;
|
|
59
60
|
request: z.ZodObject<{
|
|
60
61
|
method: z.ZodDefault<z.ZodString>;
|
|
61
62
|
path: z.ZodString;
|
|
@@ -76,6 +77,7 @@ export declare const TestConfigSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
76
77
|
path: z.ZodOptional<z.ZodString>;
|
|
77
78
|
}, z.core.$strip>>;
|
|
78
79
|
appType: z.ZodLiteral<"http-wasm">;
|
|
80
|
+
httpPort: z.ZodOptional<z.ZodNumber>;
|
|
79
81
|
request: z.ZodObject<{
|
|
80
82
|
method: z.ZodDefault<z.ZodString>;
|
|
81
83
|
path: z.ZodString;
|
|
@@ -2888,6 +2888,8 @@ var HttpWasmRunner = class {
|
|
|
2888
2888
|
this.stateManager = null;
|
|
2889
2889
|
this.dotenvEnabled = true;
|
|
2890
2890
|
this.dotenvPath = null;
|
|
2891
|
+
/** Pinned ports bypass PortManager allocation and must not be released back to it. */
|
|
2892
|
+
this.isPinnedPort = false;
|
|
2891
2893
|
/** @deprecated Legacy sync support — remove when #[fastedge::http] is retired */
|
|
2892
2894
|
this.isLegacySync = false;
|
|
2893
2895
|
this.portManager = portManager;
|
|
@@ -2914,7 +2916,19 @@ var HttpWasmRunner = class {
|
|
|
2914
2916
|
this.tempWasmPath = wasmPath;
|
|
2915
2917
|
}
|
|
2916
2918
|
this.isLegacySync = await isLegacySyncWasm(bufferOrPath);
|
|
2917
|
-
|
|
2919
|
+
if (config?.httpPort !== void 0) {
|
|
2920
|
+
const pinned = config.httpPort;
|
|
2921
|
+
if (!await this.portManager.isPortFree(pinned)) {
|
|
2922
|
+
throw new Error(
|
|
2923
|
+
`fastedge-run port ${pinned} is not available \u2014 release it or choose a different httpPort in fastedge-config.test.json`
|
|
2924
|
+
);
|
|
2925
|
+
}
|
|
2926
|
+
this.port = pinned;
|
|
2927
|
+
this.isPinnedPort = true;
|
|
2928
|
+
} else {
|
|
2929
|
+
this.port = await this.portManager.allocate();
|
|
2930
|
+
this.isPinnedPort = false;
|
|
2931
|
+
}
|
|
2918
2932
|
const wasi_http = !this.isLegacySync;
|
|
2919
2933
|
const args = [
|
|
2920
2934
|
"http",
|
|
@@ -2946,7 +2960,13 @@ var HttpWasmRunner = class {
|
|
|
2946
2960
|
await this.waitForServerReady(this.port, timeout);
|
|
2947
2961
|
}
|
|
2948
2962
|
/**
|
|
2949
|
-
* Execute an HTTP request through the WASM module
|
|
2963
|
+
* Execute an HTTP request through the WASM module.
|
|
2964
|
+
*
|
|
2965
|
+
* Redirects are surfaced verbatim — `fetch` is called with
|
|
2966
|
+
* `redirect: "manual"` so 3xx responses (status + `Location`) reach the
|
|
2967
|
+
* caller intact. This matches FastEdge edge behaviour, which returns
|
|
2968
|
+
* redirects to the client rather than following them server-side. See
|
|
2969
|
+
* `IWasmRunner.execute` for the public contract.
|
|
2950
2970
|
*/
|
|
2951
2971
|
async execute(request) {
|
|
2952
2972
|
if (!this.port || !this.process) {
|
|
@@ -2959,8 +2979,12 @@ var HttpWasmRunner = class {
|
|
|
2959
2979
|
method: request.method,
|
|
2960
2980
|
headers: request.headers,
|
|
2961
2981
|
body: request.body || void 0,
|
|
2962
|
-
signal: AbortSignal.timeout(3e4)
|
|
2982
|
+
signal: AbortSignal.timeout(3e4),
|
|
2963
2983
|
// 30 second timeout
|
|
2984
|
+
// Surface 3xx responses verbatim so tests can assert on status/Location.
|
|
2985
|
+
// A FastEdge edge returns redirects to the client rather than following
|
|
2986
|
+
// them server-side; production parity requires the same here.
|
|
2987
|
+
redirect: "manual"
|
|
2964
2988
|
});
|
|
2965
2989
|
const arrayBuffer = await response.arrayBuffer();
|
|
2966
2990
|
const bodyBuffer = Buffer.from(arrayBuffer);
|
|
@@ -3051,8 +3075,11 @@ var HttpWasmRunner = class {
|
|
|
3051
3075
|
this.process = null;
|
|
3052
3076
|
}
|
|
3053
3077
|
if (this.port !== null) {
|
|
3054
|
-
|
|
3078
|
+
if (!this.isPinnedPort) {
|
|
3079
|
+
this.portManager.release(this.port);
|
|
3080
|
+
}
|
|
3055
3081
|
this.port = null;
|
|
3082
|
+
this.isPinnedPort = false;
|
|
3056
3083
|
}
|
|
3057
3084
|
if (this.tempWasmPath) {
|
|
3058
3085
|
await removeTempWasmFile(this.tempWasmPath);
|
|
@@ -3269,6 +3296,9 @@ var PortManager = class {
|
|
|
3269
3296
|
* This is necessary when multiple server processes run simultaneously —
|
|
3270
3297
|
* each has its own PortManager with independent in-memory state, so
|
|
3271
3298
|
* in-memory tracking alone is not enough to prevent cross-process conflicts.
|
|
3299
|
+
*
|
|
3300
|
+
* Public so pinned-port callers (HttpWasmRunner with RunnerConfig.httpPort)
|
|
3301
|
+
* can reuse the same OS-level check without going through allocate().
|
|
3272
3302
|
*/
|
|
3273
3303
|
isPortFree(port) {
|
|
3274
3304
|
return new Promise((resolve) => {
|
|
@@ -3412,6 +3442,13 @@ var CdnConfigSchema = BaseConfigSchema.extend({
|
|
|
3412
3442
|
});
|
|
3413
3443
|
var HttpConfigSchema = BaseConfigSchema.extend({
|
|
3414
3444
|
appType: import_zod.z.literal("http-wasm"),
|
|
3445
|
+
/**
|
|
3446
|
+
* Pin the fastedge-run subprocess to a specific port instead of allocating
|
|
3447
|
+
* from the dynamic pool (8100-8199). Use for Codespaces/Docker port-forwarding,
|
|
3448
|
+
* stable live-preview URLs, or tooling that needs a fixed target. Load fails
|
|
3449
|
+
* fast if the port is already in use.
|
|
3450
|
+
*/
|
|
3451
|
+
httpPort: import_zod.z.number().int().min(1024).max(65535).optional(),
|
|
3415
3452
|
request: HttpRequestConfigSchema
|
|
3416
3453
|
});
|
|
3417
3454
|
var TestConfigSchema = import_zod.z.union([HttpConfigSchema, CdnConfigSchema]);
|
|
@@ -2824,6 +2824,8 @@ var HttpWasmRunner = class {
|
|
|
2824
2824
|
this.stateManager = null;
|
|
2825
2825
|
this.dotenvEnabled = true;
|
|
2826
2826
|
this.dotenvPath = null;
|
|
2827
|
+
/** Pinned ports bypass PortManager allocation and must not be released back to it. */
|
|
2828
|
+
this.isPinnedPort = false;
|
|
2827
2829
|
/** @deprecated Legacy sync support — remove when #[fastedge::http] is retired */
|
|
2828
2830
|
this.isLegacySync = false;
|
|
2829
2831
|
this.portManager = portManager;
|
|
@@ -2850,7 +2852,19 @@ var HttpWasmRunner = class {
|
|
|
2850
2852
|
this.tempWasmPath = wasmPath;
|
|
2851
2853
|
}
|
|
2852
2854
|
this.isLegacySync = await isLegacySyncWasm(bufferOrPath);
|
|
2853
|
-
|
|
2855
|
+
if (config?.httpPort !== void 0) {
|
|
2856
|
+
const pinned = config.httpPort;
|
|
2857
|
+
if (!await this.portManager.isPortFree(pinned)) {
|
|
2858
|
+
throw new Error(
|
|
2859
|
+
`fastedge-run port ${pinned} is not available \u2014 release it or choose a different httpPort in fastedge-config.test.json`
|
|
2860
|
+
);
|
|
2861
|
+
}
|
|
2862
|
+
this.port = pinned;
|
|
2863
|
+
this.isPinnedPort = true;
|
|
2864
|
+
} else {
|
|
2865
|
+
this.port = await this.portManager.allocate();
|
|
2866
|
+
this.isPinnedPort = false;
|
|
2867
|
+
}
|
|
2854
2868
|
const wasi_http = !this.isLegacySync;
|
|
2855
2869
|
const args = [
|
|
2856
2870
|
"http",
|
|
@@ -2882,7 +2896,13 @@ var HttpWasmRunner = class {
|
|
|
2882
2896
|
await this.waitForServerReady(this.port, timeout);
|
|
2883
2897
|
}
|
|
2884
2898
|
/**
|
|
2885
|
-
* Execute an HTTP request through the WASM module
|
|
2899
|
+
* Execute an HTTP request through the WASM module.
|
|
2900
|
+
*
|
|
2901
|
+
* Redirects are surfaced verbatim — `fetch` is called with
|
|
2902
|
+
* `redirect: "manual"` so 3xx responses (status + `Location`) reach the
|
|
2903
|
+
* caller intact. This matches FastEdge edge behaviour, which returns
|
|
2904
|
+
* redirects to the client rather than following them server-side. See
|
|
2905
|
+
* `IWasmRunner.execute` for the public contract.
|
|
2886
2906
|
*/
|
|
2887
2907
|
async execute(request) {
|
|
2888
2908
|
if (!this.port || !this.process) {
|
|
@@ -2895,8 +2915,12 @@ var HttpWasmRunner = class {
|
|
|
2895
2915
|
method: request.method,
|
|
2896
2916
|
headers: request.headers,
|
|
2897
2917
|
body: request.body || void 0,
|
|
2898
|
-
signal: AbortSignal.timeout(3e4)
|
|
2918
|
+
signal: AbortSignal.timeout(3e4),
|
|
2899
2919
|
// 30 second timeout
|
|
2920
|
+
// Surface 3xx responses verbatim so tests can assert on status/Location.
|
|
2921
|
+
// A FastEdge edge returns redirects to the client rather than following
|
|
2922
|
+
// them server-side; production parity requires the same here.
|
|
2923
|
+
redirect: "manual"
|
|
2900
2924
|
});
|
|
2901
2925
|
const arrayBuffer = await response.arrayBuffer();
|
|
2902
2926
|
const bodyBuffer = Buffer.from(arrayBuffer);
|
|
@@ -2987,8 +3011,11 @@ var HttpWasmRunner = class {
|
|
|
2987
3011
|
this.process = null;
|
|
2988
3012
|
}
|
|
2989
3013
|
if (this.port !== null) {
|
|
2990
|
-
|
|
3014
|
+
if (!this.isPinnedPort) {
|
|
3015
|
+
this.portManager.release(this.port);
|
|
3016
|
+
}
|
|
2991
3017
|
this.port = null;
|
|
3018
|
+
this.isPinnedPort = false;
|
|
2992
3019
|
}
|
|
2993
3020
|
if (this.tempWasmPath) {
|
|
2994
3021
|
await removeTempWasmFile(this.tempWasmPath);
|
|
@@ -3205,6 +3232,9 @@ var PortManager = class {
|
|
|
3205
3232
|
* This is necessary when multiple server processes run simultaneously —
|
|
3206
3233
|
* each has its own PortManager with independent in-memory state, so
|
|
3207
3234
|
* in-memory tracking alone is not enough to prevent cross-process conflicts.
|
|
3235
|
+
*
|
|
3236
|
+
* Public so pinned-port callers (HttpWasmRunner with RunnerConfig.httpPort)
|
|
3237
|
+
* can reuse the same OS-level check without going through allocate().
|
|
3208
3238
|
*/
|
|
3209
3239
|
isPortFree(port) {
|
|
3210
3240
|
return new Promise((resolve) => {
|
|
@@ -3348,6 +3378,13 @@ var CdnConfigSchema = BaseConfigSchema.extend({
|
|
|
3348
3378
|
});
|
|
3349
3379
|
var HttpConfigSchema = BaseConfigSchema.extend({
|
|
3350
3380
|
appType: z.literal("http-wasm"),
|
|
3381
|
+
/**
|
|
3382
|
+
* Pin the fastedge-run subprocess to a specific port instead of allocating
|
|
3383
|
+
* from the dynamic pool (8100-8199). Use for Codespaces/Docker port-forwarding,
|
|
3384
|
+
* stable live-preview URLs, or tooling that needs a fixed target. Load fails
|
|
3385
|
+
* fast if the port is already in use.
|
|
3386
|
+
*/
|
|
3387
|
+
httpPort: z.number().int().min(1024).max(65535).optional(),
|
|
3351
3388
|
request: HttpRequestConfigSchema
|
|
3352
3389
|
});
|
|
3353
3390
|
var TestConfigSchema = z.union([HttpConfigSchema, CdnConfigSchema]);
|
|
@@ -36,6 +36,22 @@ export declare function runFlow(runner: IWasmRunner, options: FlowOptions): Prom
|
|
|
36
36
|
* - method defaults to "GET"
|
|
37
37
|
* - headers defaults to {}
|
|
38
38
|
* - body defaults to ""
|
|
39
|
+
*
|
|
40
|
+
* Redirects are surfaced verbatim — a 302 from the WASM is returned to the
|
|
41
|
+
* caller with its `Location` header preserved, matching FastEdge edge
|
|
42
|
+
* behaviour.
|
|
43
|
+
*
|
|
44
|
+
* `runHttpRequest` targets the WASM app under test only (`options.path` is a
|
|
45
|
+
* path on the local `fastedge-run` server, not a full URL). Following a
|
|
46
|
+
* redirect therefore depends on the shape of `response.headers.location`:
|
|
47
|
+
*
|
|
48
|
+
* - Relative (e.g. `/auth/complete`) — pass it directly as `path` in a second
|
|
49
|
+
* `runHttpRequest` call.
|
|
50
|
+
* - Absolute with the app's own host — parse with `new URL(...)`, then
|
|
51
|
+
* re-issue against `url.pathname + url.search`.
|
|
52
|
+
* - Absolute with an external host — cannot be followed through the runner;
|
|
53
|
+
* that redirect is the end of the test, assert on status + Location and
|
|
54
|
+
* stop there.
|
|
39
55
|
*/
|
|
40
56
|
export declare function runHttpRequest(runner: IWasmRunner, options: HttpRequestOptions): Promise<HttpResponse>;
|
|
41
57
|
/**
|