@overkill-dev/simulation 0.0.1 → 0.0.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/LICENSE +21 -0
- package/package.json +34 -6
- package/packages/simulation/http.entry-point.d.ts +3 -0
- package/packages/simulation/http.entry-point.d.ts.map +1 -0
- package/packages/simulation/http.entry-point.js +2 -0
- package/packages/simulation/http.entry-point.js.map +1 -0
- package/packages/simulation/simulation.entry-point.d.ts +3 -0
- package/packages/simulation/simulation.entry-point.d.ts.map +1 -0
- package/packages/simulation/simulation.entry-point.js +2 -0
- package/packages/simulation/simulation.entry-point.js.map +1 -0
- package/readme.md +25 -5
- package/sbom.cdx.json +30 -0
- package/simulation/simulated-http-server.d.ts +40 -0
- package/simulation/simulated-http-server.d.ts.map +1 -0
- package/simulation/simulated-http-server.js +235 -0
- package/simulation/simulated-http-server.js.map +1 -0
- package/simulation/simulation.d.ts +42 -0
- package/simulation/simulation.d.ts.map +1 -0
- package/simulation/simulation.js +74 -0
- package/simulation/simulation.js.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Overkill contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"author": "Christian Rackerseder <git@echooff.de>",
|
|
3
|
+
"bugs": {
|
|
4
|
+
"url": "https://github.com/enormora/overkill/issues"
|
|
5
|
+
},
|
|
6
|
+
"description": "Finite simulation descriptors and simulated server launchers for Overkill.",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "^26.9.0"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./packages/simulation/simulation.entry-point.js",
|
|
13
|
+
"types": "./packages/simulation/simulation.entry-point.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./http": {
|
|
16
|
+
"import": "./packages/simulation/http.entry-point.js",
|
|
17
|
+
"types": "./packages/simulation/http.entry-point.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/enormora/overkill#readme",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"tdd",
|
|
23
|
+
"test",
|
|
24
|
+
"tests"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"name": "@overkill-dev/simulation",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/enormora/overkill.git"
|
|
31
|
+
},
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"type": "module",
|
|
34
|
+
"version": "0.0.3"
|
|
35
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { assertNoSimulatedHttpHandlerErrors, createSimulatedHttpListeningServer, startSimulatedHttpServer } from '../../simulation/simulated-http-server.ts';
|
|
2
|
+
export type { SimulatedHttpListeningServer, SimulatedHttpServerHandle, SimulatedHttpServerOptions } from '../../simulation/simulated-http-server.ts';
|
|
3
|
+
//# sourceMappingURL=http.entry-point.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.entry-point.d.ts","sourceRoot":"","sources":["../../../../../source/packages/simulation/http.entry-point.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,kCAAkC,EAClC,kCAAkC,EAClC,wBAAwB,EAC3B,MAAM,2CAA2C,CAAC;AACnD,YAAY,EACR,4BAA4B,EAC5B,yBAAyB,EACzB,0BAA0B,EAC7B,MAAM,2CAA2C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.entry-point.js","sourceRoot":"","sources":["../../../../../source/packages/simulation/http.entry-point.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,kCAAkC,EAClC,kCAAkC,EAClC,wBAAwB,EAC3B,MAAM,2CAA2C,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { defineSimulatedHttpServer, defineSimulation, isDefinedSimulatedHttpServer, isDefinedSimulation } from '../../simulation/simulation.ts';
|
|
2
|
+
export type { ScenarioKeyOf, SimulatedHttpHandler, SimulatedHttpRequestScenario, SimulatedHttpServerDefinition, SimulatedHttpServerDefinitionInput, SimulationDefinition, SimulationDefinitionInput, SimulationScenarioCatalog, SimulationScenarioDescriptor, SimulationScenarioFor, SimulationScenarioKey } from '../../simulation/simulation.ts';
|
|
3
|
+
//# sourceMappingURL=simulation.entry-point.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulation.entry-point.d.ts","sourceRoot":"","sources":["../../../../../source/packages/simulation/simulation.entry-point.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,yBAAyB,EACzB,gBAAgB,EAChB,4BAA4B,EAC5B,mBAAmB,EACtB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACR,aAAa,EACb,oBAAoB,EACpB,4BAA4B,EAC5B,6BAA6B,EAC7B,kCAAkC,EAClC,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,EACzB,4BAA4B,EAC5B,qBAAqB,EACrB,qBAAqB,EACxB,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulation.entry-point.js","sourceRoot":"","sources":["../../../../../source/packages/simulation/simulation.entry-point.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,yBAAyB,EACzB,gBAAgB,EAChB,4BAA4B,EAC5B,mBAAmB,EACtB,MAAM,gCAAgC,CAAC"}
|
package/readme.md
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@overkill-dev/simulation`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
can subsequently be configured for it. It contains no real package content and is published already
|
|
5
|
-
deprecated.
|
|
3
|
+
Finite simulation descriptors for Overkill resource-backed tests.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
```ts
|
|
6
|
+
import { defineSimulatedHttpServer } from '@overkill-dev/simulation';
|
|
7
|
+
|
|
8
|
+
const api = defineSimulatedHttpServer({
|
|
9
|
+
name: 'api',
|
|
10
|
+
scenarios: {
|
|
11
|
+
default: { title: 'standard responses' },
|
|
12
|
+
'payments-500': { status: 500, title: 'payment service fails' }
|
|
13
|
+
},
|
|
14
|
+
handle(_request, scenario) {
|
|
15
|
+
return Response.json({ scenario: scenario.key });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Every simulation has a `default` scenario. Scenario descriptors require a
|
|
21
|
+
`title` and may carry additional typed data for the simulator.
|
|
22
|
+
|
|
23
|
+
Use `@overkill-dev/simulation/http` to launch a simulated HTTP server manually.
|
|
24
|
+
Use `createSimulatedHttpServerResource(...)` from `@overkill-dev/resources`
|
|
25
|
+
when a test runtime should own the server lifecycle. The resource adapter
|
|
26
|
+
requires an explicit local address request, for example
|
|
27
|
+
`{ kind: 'loopback', port: 0 }`.
|
package/sbom.cdx.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
|
|
3
|
+
"bomFormat": "CycloneDX",
|
|
4
|
+
"specVersion": "1.6",
|
|
5
|
+
"version": 1,
|
|
6
|
+
"metadata": {
|
|
7
|
+
"tools": {
|
|
8
|
+
"components": [
|
|
9
|
+
{
|
|
10
|
+
"type": "application",
|
|
11
|
+
"name": "packtory",
|
|
12
|
+
"version": "0.0.97"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"component": {
|
|
17
|
+
"type": "library",
|
|
18
|
+
"name": "@overkill-dev/simulation",
|
|
19
|
+
"version": "0.0.3",
|
|
20
|
+
"bom-ref": "pkg:npm/@overkill-dev/simulation@0.0.3",
|
|
21
|
+
"purl": "pkg:npm/@overkill-dev/simulation@0.0.3"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"components": [],
|
|
25
|
+
"dependencies": [
|
|
26
|
+
{
|
|
27
|
+
"ref": "pkg:npm/@overkill-dev/simulation@0.0.3"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type SimulatedHttpServerDefinition, type SimulationScenarioCatalog } from './simulation.ts';
|
|
2
|
+
export type SimulatedHttpServerOptions<Name extends string, Scenarios extends SimulationScenarioCatalog> = {
|
|
3
|
+
readonly host?: string;
|
|
4
|
+
readonly port?: number;
|
|
5
|
+
readonly simulation: SimulatedHttpServerDefinition<Name, Scenarios>;
|
|
6
|
+
};
|
|
7
|
+
type SimulationWithScenarios = {
|
|
8
|
+
readonly scenarios: SimulationScenarioCatalog;
|
|
9
|
+
};
|
|
10
|
+
export type SimulatedHttpServerHandle<Simulation extends SimulationWithScenarios> = AsyncDisposable & {
|
|
11
|
+
readonly baseUrl: string;
|
|
12
|
+
readonly dispose: () => Promise<void>;
|
|
13
|
+
readonly scenarioUrl: (scenario: string & keyof Simulation['scenarios'], path: string) => string;
|
|
14
|
+
};
|
|
15
|
+
type ServerAddress = {
|
|
16
|
+
readonly port: number;
|
|
17
|
+
};
|
|
18
|
+
type ServerLifecycle = {
|
|
19
|
+
readonly address: () => ServerAddress | string | null;
|
|
20
|
+
readonly close: (callback: (error?: Error) => void) => unknown;
|
|
21
|
+
readonly listen: (port: number, host: string) => unknown;
|
|
22
|
+
readonly listening: boolean;
|
|
23
|
+
readonly off: {
|
|
24
|
+
(event: 'error', listener: (error: Error) => void): unknown;
|
|
25
|
+
(event: 'listening', listener: () => void): unknown;
|
|
26
|
+
};
|
|
27
|
+
readonly once: {
|
|
28
|
+
(event: 'error', listener: (error: Error) => void): unknown;
|
|
29
|
+
(event: 'listening', listener: () => void): unknown;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export type SimulatedHttpListeningServer = {
|
|
33
|
+
readonly errors: readonly unknown[];
|
|
34
|
+
readonly server: ServerLifecycle;
|
|
35
|
+
};
|
|
36
|
+
export declare function createSimulatedHttpListeningServer<Scenarios extends SimulationScenarioCatalog>(simulation: SimulatedHttpServerDefinition<string, Scenarios>, host: string): SimulatedHttpListeningServer;
|
|
37
|
+
export declare function assertNoSimulatedHttpHandlerErrors(handlerErrors: readonly unknown[]): void;
|
|
38
|
+
export declare function startSimulatedHttpServer<const Name extends string, const Scenarios extends SimulationScenarioCatalog>(options: SimulatedHttpServerOptions<Name, Scenarios>): Promise<SimulatedHttpServerHandle<SimulatedHttpServerDefinition<Name, Scenarios>>>;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=simulated-http-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulated-http-server.d.ts","sourceRoot":"","sources":["../../../../source/simulation/simulated-http-server.ts"],"names":[],"mappings":"AACA,OAAO,EAEH,KAAK,6BAA6B,EAClC,KAAK,yBAAyB,EACjC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,MAAM,0BAA0B,CAClC,IAAI,SAAS,MAAM,EACnB,SAAS,SAAS,yBAAyB,IAC3C;IACA,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,6BAA6B,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;CACvE,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC3B,QAAQ,CAAC,SAAS,EAAE,yBAAyB,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,yBAAyB,CAAC,UAAU,SAAS,uBAAuB,IAAI,eAAe,GAAG;IAClG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACpG,CAAC;AAMF,KAAK,aAAa,GAAG;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB,CAAC;AAIF,KAAK,eAAe,GAAG;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,aAAa,GAAG,MAAM,GAAG,IAAI,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,KAAK,OAAO,CAAC;IAC/D,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACzD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE;QACV,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;QAC5D,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;KACvD,CAAC;IACF,QAAQ,CAAC,IAAI,EAAE;QACX,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;QAC5D,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;KACvD,CAAC;CACL,CAAC;AAkBF,MAAM,MAAM,4BAA4B,GAAG;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;CACpC,CAAC;AAgSF,wBAAgB,kCAAkC,CAAC,SAAS,SAAS,yBAAyB,EAC1F,UAAU,EAAE,6BAA6B,CAAC,MAAM,EAAE,SAAS,CAAC,EAC5D,IAAI,EAAE,MAAM,GACb,4BAA4B,CAQ9B;AAED,wBAAgB,kCAAkC,CAAC,aAAa,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAI1F;AAED,wBAAsB,wBAAwB,CAC1C,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,KAAK,CAAC,SAAS,SAAS,yBAAyB,EAEjD,OAAO,EAAE,0BAA0B,CAAC,IAAI,EAAE,SAAS,CAAC,GACrD,OAAO,CAAC,yBAAyB,CAAC,6BAA6B,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAYpF"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import http, {} from 'node:http';
|
|
2
|
+
import { isDefinedSimulatedHttpServer } from "./simulation.js";
|
|
3
|
+
const scenarioQueryParameterName = '__overkill_scenario';
|
|
4
|
+
const unknownScenarioStatus = 400;
|
|
5
|
+
const handlerFailureStatus = 500;
|
|
6
|
+
function asyncDisposeSymbol() {
|
|
7
|
+
const value = Reflect.get(Symbol, 'asyncDispose');
|
|
8
|
+
if (typeof value !== 'symbol') {
|
|
9
|
+
throw new TypeError('Runtime does not provide Symbol.asyncDispose.');
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
async function collectRequestBody(request) {
|
|
14
|
+
if (request.method === 'GET' || request.method === 'HEAD') {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return await new Promise(function collectBody(resolve, reject) {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
request.on('data', function recordChunk(chunk) {
|
|
20
|
+
chunks.push(chunk);
|
|
21
|
+
});
|
|
22
|
+
request.on('error', reject);
|
|
23
|
+
request.on('end', function resolveBody() {
|
|
24
|
+
resolve(Buffer.concat(chunks));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function requestHeaders(request) {
|
|
29
|
+
const headers = new Headers();
|
|
30
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
for (const item of value) {
|
|
33
|
+
headers.append(name, item);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (value !== undefined) {
|
|
37
|
+
headers.set(name, value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return headers;
|
|
41
|
+
}
|
|
42
|
+
async function responseBody(response) {
|
|
43
|
+
const body = await response.arrayBuffer();
|
|
44
|
+
return Buffer.from(body);
|
|
45
|
+
}
|
|
46
|
+
function scenarioUrl(baseUrl, scenario, path) {
|
|
47
|
+
const url = new URL(path, baseUrl);
|
|
48
|
+
url.searchParams.set(scenarioQueryParameterName, scenario);
|
|
49
|
+
return url.href;
|
|
50
|
+
}
|
|
51
|
+
function readListenAddress(server, host) {
|
|
52
|
+
const address = server.address();
|
|
53
|
+
if (typeof address !== 'object' || address === null) {
|
|
54
|
+
throw new TypeError('Simulated HTTP server did not expose a TCP address.');
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
host,
|
|
58
|
+
port: address.port
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function serverBaseUrl(server, host) {
|
|
62
|
+
const listenAddress = readListenAddress(server, host);
|
|
63
|
+
return `http://${listenAddress.host}:${listenAddress.port}`;
|
|
64
|
+
}
|
|
65
|
+
async function listen(server, host, port) {
|
|
66
|
+
return await new Promise(function startListening(resolve, reject) {
|
|
67
|
+
const listener = {
|
|
68
|
+
reject(error) {
|
|
69
|
+
server.off('listening', listener.resolve);
|
|
70
|
+
reject(error);
|
|
71
|
+
},
|
|
72
|
+
resolve() {
|
|
73
|
+
server.off('error', listener.reject);
|
|
74
|
+
resolve(readListenAddress(server, host));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
server.once('error', listener.reject);
|
|
78
|
+
server.once('listening', listener.resolve);
|
|
79
|
+
server.listen(port, host);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function closeServer(server) {
|
|
83
|
+
await new Promise(function close(resolve, reject) {
|
|
84
|
+
server.close(function finish(error) {
|
|
85
|
+
if (error === undefined) {
|
|
86
|
+
resolve();
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
reject(error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function webResponseHeaders(webResponse) {
|
|
95
|
+
const headers = {};
|
|
96
|
+
webResponse.headers.forEach(function recordHeader(value, name) {
|
|
97
|
+
headers[name] = value;
|
|
98
|
+
});
|
|
99
|
+
return headers;
|
|
100
|
+
}
|
|
101
|
+
async function sendWebResponse(response, webResponse) {
|
|
102
|
+
const body = await responseBody(webResponse);
|
|
103
|
+
response.writeHead(webResponse.status, webResponse.statusText, webResponseHeaders(webResponse));
|
|
104
|
+
response.end(body);
|
|
105
|
+
}
|
|
106
|
+
function sendTextResponse(response, status, body) {
|
|
107
|
+
response.writeHead(status);
|
|
108
|
+
response.end(body);
|
|
109
|
+
}
|
|
110
|
+
async function createWebRequest(request, baseUrl) {
|
|
111
|
+
const requestUrl = new URL(request.url ?? '/', baseUrl);
|
|
112
|
+
const scenario = requestUrl.searchParams.get(scenarioQueryParameterName) ?? 'default';
|
|
113
|
+
requestUrl.searchParams.delete(scenarioQueryParameterName);
|
|
114
|
+
const body = await collectRequestBody(request);
|
|
115
|
+
const init = {
|
|
116
|
+
headers: requestHeaders(request),
|
|
117
|
+
method: request.method ?? 'GET'
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
request: body === null ? new Request(requestUrl, init) : new Request(requestUrl, { ...init, body }),
|
|
121
|
+
scenario
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function disposalError(error) {
|
|
125
|
+
return error instanceof Error
|
|
126
|
+
? error
|
|
127
|
+
: new Error('Simulation handler failed with a non-error value.', { cause: error });
|
|
128
|
+
}
|
|
129
|
+
function isSimulatedHttpServerHandle(handle) {
|
|
130
|
+
return Reflect.has(handle, asyncDisposeSymbol());
|
|
131
|
+
}
|
|
132
|
+
function simulatedHttpServerHandle(baseUrl, disposeServer) {
|
|
133
|
+
const handle = {
|
|
134
|
+
baseUrl,
|
|
135
|
+
scenarioUrl(scenario, path) {
|
|
136
|
+
return scenarioUrl(baseUrl, scenario, path);
|
|
137
|
+
},
|
|
138
|
+
dispose: disposeServer
|
|
139
|
+
};
|
|
140
|
+
Object.defineProperty(handle, asyncDisposeSymbol(), { value: disposeServer });
|
|
141
|
+
const frozenHandle = Object.freeze(handle);
|
|
142
|
+
if (isSimulatedHttpServerHandle(frozenHandle)) {
|
|
143
|
+
return frozenHandle;
|
|
144
|
+
}
|
|
145
|
+
throw new TypeError('Simulated HTTP server handle is incomplete.');
|
|
146
|
+
}
|
|
147
|
+
async function respondToSimulationRequest(response, simulation, request, scenario) {
|
|
148
|
+
if (!Object.hasOwn(simulation.scenarios, scenario)) {
|
|
149
|
+
sendTextResponse(response, unknownScenarioStatus, 'Unknown simulation scenario.');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const scenarioKey = scenario;
|
|
153
|
+
const descriptor = simulation.scenarios[scenarioKey];
|
|
154
|
+
await sendWebResponse(response, await simulation.handle(request, { descriptor, key: scenarioKey }));
|
|
155
|
+
}
|
|
156
|
+
async function routeSimulationRequest(context) {
|
|
157
|
+
try {
|
|
158
|
+
const webRequest = await createWebRequest(context.request, context.baseUrl);
|
|
159
|
+
await respondToSimulationRequest(context.response, context.simulation, webRequest.request, webRequest.scenario);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
try {
|
|
163
|
+
context.recordError(error);
|
|
164
|
+
sendTextResponse(context.response, handlerFailureStatus, 'Simulation handler failed.');
|
|
165
|
+
}
|
|
166
|
+
catch (responseError) {
|
|
167
|
+
context.recordError(responseError);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function startGuardedRoute(routePromise) {
|
|
172
|
+
Reflect.get(routePromise, Symbol.toStringTag);
|
|
173
|
+
}
|
|
174
|
+
function createServer(simulation, host, recordError) {
|
|
175
|
+
const server = http.createServer(function handleRequest(request, response) {
|
|
176
|
+
startGuardedRoute(routeSimulationRequest({
|
|
177
|
+
request,
|
|
178
|
+
response,
|
|
179
|
+
simulation,
|
|
180
|
+
baseUrl: serverBaseUrl(server, host),
|
|
181
|
+
recordError
|
|
182
|
+
}));
|
|
183
|
+
});
|
|
184
|
+
return server;
|
|
185
|
+
}
|
|
186
|
+
async function listenBaseUrl(server, host, port) {
|
|
187
|
+
const listenAddress = await listen(server, host, port);
|
|
188
|
+
return `http://${listenAddress.host}:${listenAddress.port}`;
|
|
189
|
+
}
|
|
190
|
+
function createHandlerErrors() {
|
|
191
|
+
const errors = [];
|
|
192
|
+
return {
|
|
193
|
+
errors,
|
|
194
|
+
record(error) {
|
|
195
|
+
errors.push(error);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function createServerDisposal(server, handlerErrors) {
|
|
200
|
+
let disposal = null;
|
|
201
|
+
return async function disposeServer() {
|
|
202
|
+
if (disposal === null) {
|
|
203
|
+
disposal = closeServer(server);
|
|
204
|
+
}
|
|
205
|
+
await disposal;
|
|
206
|
+
if (handlerErrors[0] !== undefined) {
|
|
207
|
+
throw disposalError(handlerErrors[0]);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
export function createSimulatedHttpListeningServer(simulation, host) {
|
|
212
|
+
const handlerErrors = createHandlerErrors();
|
|
213
|
+
const server = createServer(simulation, host, handlerErrors.record);
|
|
214
|
+
return {
|
|
215
|
+
errors: handlerErrors.errors,
|
|
216
|
+
server
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
export function assertNoSimulatedHttpHandlerErrors(handlerErrors) {
|
|
220
|
+
if (handlerErrors[0] !== undefined) {
|
|
221
|
+
throw disposalError(handlerErrors[0]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
export async function startSimulatedHttpServer(options) {
|
|
225
|
+
if (!isDefinedSimulatedHttpServer(options.simulation)) {
|
|
226
|
+
throw new TypeError('startSimulatedHttpServer() requires a simulated HTTP server definition.');
|
|
227
|
+
}
|
|
228
|
+
const host = options.host ?? '127.0.0.1';
|
|
229
|
+
const port = options.port ?? 0;
|
|
230
|
+
const listeningServer = createSimulatedHttpListeningServer(options.simulation, host);
|
|
231
|
+
const baseUrl = await listenBaseUrl(listeningServer.server, host, port);
|
|
232
|
+
const disposeServer = createServerDisposal(listeningServer.server, listeningServer.errors);
|
|
233
|
+
return simulatedHttpServerHandle(baseUrl, disposeServer);
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=simulated-http-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulated-http-server.js","sourceRoot":"","sources":["../../../../source/simulation/simulated-http-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
declare const simulationDefinitionBrand: unique symbol;
|
|
2
|
+
declare const simulatedHttpServerDefinitionBrand: unique symbol;
|
|
3
|
+
export type SimulationScenarioDescriptor = {
|
|
4
|
+
readonly title: string;
|
|
5
|
+
};
|
|
6
|
+
export type SimulationScenarioCatalog = Readonly<Record<string, SimulationScenarioDescriptor>> & {
|
|
7
|
+
readonly default: SimulationScenarioDescriptor;
|
|
8
|
+
};
|
|
9
|
+
export type SimulationDefinitionInput<Name extends string, Scenarios extends SimulationScenarioCatalog> = {
|
|
10
|
+
readonly name: Name;
|
|
11
|
+
readonly scenarios: Scenarios;
|
|
12
|
+
};
|
|
13
|
+
export type SimulationDefinition<Name extends string = string, Scenarios extends SimulationScenarioCatalog = SimulationScenarioCatalog> = SimulationDefinitionInput<Name, Scenarios> & {
|
|
14
|
+
readonly kind: 'simulation';
|
|
15
|
+
readonly [simulationDefinitionBrand]: true;
|
|
16
|
+
};
|
|
17
|
+
export type SimulationScenarioKey<Simulation> = Simulation extends {
|
|
18
|
+
readonly scenarios: infer Scenarios extends SimulationScenarioCatalog;
|
|
19
|
+
} ? string & keyof Scenarios : never;
|
|
20
|
+
export type ScenarioKeyOf<Simulation> = SimulationScenarioKey<Simulation>;
|
|
21
|
+
export type SimulationScenarioFor<Simulation, Scenario extends SimulationScenarioKey<Simulation>> = Simulation extends {
|
|
22
|
+
readonly scenarios: infer Scenarios extends SimulationScenarioCatalog;
|
|
23
|
+
} ? Scenarios[Scenario] : never;
|
|
24
|
+
export type SimulatedHttpRequestScenario<Scenario extends string, Descriptor extends SimulationScenarioDescriptor> = {
|
|
25
|
+
readonly descriptor: Descriptor;
|
|
26
|
+
readonly key: Scenario;
|
|
27
|
+
};
|
|
28
|
+
export type SimulatedHttpHandler<Scenarios extends SimulationScenarioCatalog> = <Scenario extends string & keyof Scenarios>(request: Request, scenario: SimulatedHttpRequestScenario<Scenario, Scenarios[Scenario]>) => Promise<Response> | Response;
|
|
29
|
+
export type SimulatedHttpServerDefinitionInput<Name extends string, Scenarios extends SimulationScenarioCatalog> = SimulationDefinitionInput<Name, Scenarios> & {
|
|
30
|
+
readonly handle: SimulatedHttpHandler<Scenarios>;
|
|
31
|
+
};
|
|
32
|
+
export type SimulatedHttpServerDefinition<Name extends string = string, Scenarios extends SimulationScenarioCatalog = SimulationScenarioCatalog> = SimulatedHttpServerDefinitionInput<Name, Scenarios> & {
|
|
33
|
+
readonly kind: 'simulated-http-server';
|
|
34
|
+
readonly [simulationDefinitionBrand]: true;
|
|
35
|
+
readonly [simulatedHttpServerDefinitionBrand]: true;
|
|
36
|
+
};
|
|
37
|
+
export declare function defineSimulation<const Name extends string, const Scenarios extends SimulationScenarioCatalog>(definition: SimulationDefinitionInput<Name, Scenarios>): SimulationDefinition<Name, Scenarios>;
|
|
38
|
+
export declare function defineSimulatedHttpServer<const Name extends string, const Scenarios extends SimulationScenarioCatalog>(definition: SimulatedHttpServerDefinitionInput<Name, Scenarios>): SimulatedHttpServerDefinition<Name, Scenarios>;
|
|
39
|
+
export declare function isDefinedSimulation(value: unknown): value is SimulationDefinition;
|
|
40
|
+
export declare function isDefinedSimulatedHttpServer(value: unknown): value is SimulatedHttpServerDefinition;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=simulation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../../../source/simulation/simulation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const simulationDefinitionBrand = Symbol('overkill.simulationDefinition');
|
|
2
|
+
const simulatedHttpServerDefinitionBrand = Symbol('overkill.simulatedHttpServerDefinition');
|
|
3
|
+
const descriptorNamePattern = /^[A-Za-z0-9._-]+$/u;
|
|
4
|
+
function assertDescriptorName(kind, name) {
|
|
5
|
+
if (!descriptorNamePattern.test(name)) {
|
|
6
|
+
throw new TypeError(`${kind} "${name}" must match ${descriptorNamePattern.source}.`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function assertScenarioDescriptor(scenarioKey, descriptor) {
|
|
13
|
+
if (!isRecord(descriptor)) {
|
|
14
|
+
throw new TypeError(`Simulation scenario "${scenarioKey}" must be an object.`);
|
|
15
|
+
}
|
|
16
|
+
if (typeof descriptor.title !== 'string' || descriptor.title.length === 0) {
|
|
17
|
+
throw new TypeError(`Simulation scenario "${scenarioKey}" requires a non-empty title.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function scenarioCatalogEntries(scenarios) {
|
|
21
|
+
if (!isRecord(scenarios)) {
|
|
22
|
+
throw new TypeError('Simulation scenarios must be an object.');
|
|
23
|
+
}
|
|
24
|
+
const scenarioEntries = Object.entries(scenarios);
|
|
25
|
+
if (scenarioEntries.length === 0) {
|
|
26
|
+
throw new TypeError('Simulation scenarios must not be empty.');
|
|
27
|
+
}
|
|
28
|
+
if (!Object.hasOwn(scenarios, 'default')) {
|
|
29
|
+
throw new TypeError('Simulation scenarios must include a "default" scenario.');
|
|
30
|
+
}
|
|
31
|
+
return scenarioEntries;
|
|
32
|
+
}
|
|
33
|
+
function freezeScenarioCatalog(scenarios) {
|
|
34
|
+
const scenarioEntries = scenarioCatalogEntries(scenarios);
|
|
35
|
+
for (const [scenarioKey, descriptor] of scenarioEntries) {
|
|
36
|
+
assertDescriptorName('Simulation scenario', scenarioKey);
|
|
37
|
+
assertScenarioDescriptor(scenarioKey, descriptor);
|
|
38
|
+
Object.freeze(descriptor);
|
|
39
|
+
}
|
|
40
|
+
return Object.freeze(scenarios);
|
|
41
|
+
}
|
|
42
|
+
export function defineSimulation(definition) {
|
|
43
|
+
assertDescriptorName('Simulation', definition.name);
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
name: definition.name,
|
|
46
|
+
scenarios: freezeScenarioCatalog(definition.scenarios),
|
|
47
|
+
kind: 'simulation',
|
|
48
|
+
[simulationDefinitionBrand]: true
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function defineSimulatedHttpServer(definition) {
|
|
52
|
+
if (typeof definition.handle !== 'function') {
|
|
53
|
+
throw new TypeError('Simulated HTTP server requires a handler function.');
|
|
54
|
+
}
|
|
55
|
+
const simulation = defineSimulation(definition);
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
name: simulation.name,
|
|
58
|
+
scenarios: simulation.scenarios,
|
|
59
|
+
handle: definition.handle,
|
|
60
|
+
kind: 'simulated-http-server',
|
|
61
|
+
[simulationDefinitionBrand]: true,
|
|
62
|
+
[simulatedHttpServerDefinitionBrand]: true
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export function isDefinedSimulation(value) {
|
|
66
|
+
return typeof value === 'object' &&
|
|
67
|
+
value !== null &&
|
|
68
|
+
Reflect.get(value, simulationDefinitionBrand) === true;
|
|
69
|
+
}
|
|
70
|
+
export function isDefinedSimulatedHttpServer(value) {
|
|
71
|
+
return isDefinedSimulation(value) &&
|
|
72
|
+
Reflect.get(value, simulatedHttpServerDefinitionBrand) === true;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=simulation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulation.js","sourceRoot":"","sources":["../../../../source/simulation/simulation.ts"],"names":[],"mappings":"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"}
|