@warp-drive/holodeck 0.1.0-alpha.13 → 0.1.0-alpha.15
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/declarations/index.d.ts +37 -2
- package/declarations/mock.d.ts +22 -0
- package/dist/index.js +52 -2
- package/dist/mock.js +27 -0
- package/package.json +8 -8
- package/server/ensure-cert.js +4 -0
- package/server/index.js +11 -0
package/declarations/index.d.ts
CHANGED
|
@@ -3,21 +3,56 @@ import type { RequestContext, StructuredDataDocument } from "@warp-drive/core/ty
|
|
|
3
3
|
import type { MinimumAdapterInterface } from "@warp-drive/legacy/compat";
|
|
4
4
|
import type { Store } from "@warp-drive/legacy/store";
|
|
5
5
|
import type { ScaffoldGenerator } from "./mock.js";
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
6
9
|
export declare function setConfig({ host }: {
|
|
7
10
|
host: string;
|
|
8
11
|
}): void;
|
|
12
|
+
/**
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
9
15
|
export declare function setTestId(context: object, str: string | null): void;
|
|
16
|
+
/**
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
10
19
|
export declare function setIsRecording(value: boolean): void;
|
|
20
|
+
/**
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
11
23
|
export declare function getIsRecording(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* A request handler that intercepts requests and routes them through
|
|
26
|
+
* the Holodeck mock server.
|
|
27
|
+
*
|
|
28
|
+
* This handler modifies the request URL to include test identifiers
|
|
29
|
+
* and manages request counts for accurate mocking.
|
|
30
|
+
*
|
|
31
|
+
* Requires that the test context be configured with a testId using `setTestId`.
|
|
32
|
+
*
|
|
33
|
+
* @param owner - the test context object used to retrieve the test ID.
|
|
34
|
+
*/
|
|
12
35
|
export declare class MockServerHandler implements Handler {
|
|
13
36
|
owner: object;
|
|
14
37
|
constructor(owner: object);
|
|
15
38
|
request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>>;
|
|
16
39
|
}
|
|
17
|
-
interface
|
|
40
|
+
interface HasAdapterForFn {
|
|
18
41
|
adapterFor(this: Store, modelName: string): MinimumAdapterInterface;
|
|
19
42
|
adapterFor(this: Store, modelName: string, _allowMissing?: true): MinimumAdapterInterface | undefined;
|
|
20
43
|
}
|
|
21
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Creates an adapterFor function that wraps the provided adapterFor function
|
|
46
|
+
* to override the adapter's _fetchRequest method to route requests through
|
|
47
|
+
* the Holodeck mock server.
|
|
48
|
+
*
|
|
49
|
+
* @param owner - The test context object used to retrieve the test ID.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createAdapterFor(owner: object, store: HasAdapterForFn): HasAdapterForFn["adapterFor"];
|
|
52
|
+
/**
|
|
53
|
+
* Mock a request by sending the scaffold to the mock server.
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
22
57
|
export declare function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean): Promise<void>;
|
|
23
58
|
export {};
|
package/declarations/mock.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @public
|
|
3
|
+
*/
|
|
1
4
|
export interface Scaffold {
|
|
2
5
|
status: number;
|
|
3
6
|
statusText?: string;
|
|
@@ -7,7 +10,13 @@ export interface Scaffold {
|
|
|
7
10
|
url: string;
|
|
8
11
|
response: Record<string, unknown>;
|
|
9
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
10
16
|
export type ScaffoldGenerator = () => Scaffold;
|
|
17
|
+
/**
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
11
20
|
export type ResponseGenerator = () => Record<string, unknown>;
|
|
12
21
|
/**
|
|
13
22
|
* Sets up Mocking for a GET request on the mock server
|
|
@@ -29,15 +38,28 @@ export type ResponseGenerator = () => Record<string, unknown>;
|
|
|
29
38
|
export declare function GET(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
30
39
|
RECORD?: boolean;
|
|
31
40
|
}): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Mock a POST request
|
|
43
|
+
*/
|
|
32
44
|
export declare function POST(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
33
45
|
RECORD?: boolean;
|
|
34
46
|
}): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* mock a PUT request
|
|
49
|
+
*/
|
|
35
50
|
export declare function PUT(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
36
51
|
RECORD?: boolean;
|
|
37
52
|
}): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* mock a PATCH request
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
38
57
|
export declare function PATCH(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
39
58
|
RECORD?: boolean;
|
|
40
59
|
}): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* mock a DELETE request
|
|
62
|
+
*/
|
|
41
63
|
export declare function DELETE(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
42
64
|
RECORD?: boolean;
|
|
43
65
|
}): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { SHOULD_RECORD } from '@warp-drive/core/build-config/env';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @module
|
|
5
|
+
* @mergeModuleWith <project>
|
|
6
|
+
*/
|
|
3
7
|
const TEST_IDS = new WeakMap();
|
|
4
8
|
let HOST = 'https://localhost:1135/';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
|
|
5
14
|
function setConfig({
|
|
6
15
|
host
|
|
7
16
|
}) {
|
|
8
17
|
HOST = host.endsWith('/') ? host : `${host}/`;
|
|
9
18
|
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
|
|
10
24
|
function setTestId(context, str) {
|
|
11
25
|
if (str && TEST_IDS.has(context)) {
|
|
12
26
|
throw new Error(`MockServerHandler is already configured with a testId.`);
|
|
@@ -45,12 +59,32 @@ function setTestId(context, str) {
|
|
|
45
59
|
}
|
|
46
60
|
const shouldRecord = SHOULD_RECORD ? true : false;
|
|
47
61
|
let IS_RECORDING = null;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
48
66
|
function setIsRecording(value) {
|
|
49
67
|
IS_RECORDING = value === null ? value : Boolean(value);
|
|
50
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @public
|
|
72
|
+
*/
|
|
51
73
|
function getIsRecording() {
|
|
52
74
|
return IS_RECORDING === null ? shouldRecord : IS_RECORDING;
|
|
53
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A request handler that intercepts requests and routes them through
|
|
79
|
+
* the Holodeck mock server.
|
|
80
|
+
*
|
|
81
|
+
* This handler modifies the request URL to include test identifiers
|
|
82
|
+
* and manages request counts for accurate mocking.
|
|
83
|
+
*
|
|
84
|
+
* Requires that the test context be configured with a testId using `setTestId`.
|
|
85
|
+
*
|
|
86
|
+
* @param owner - the test context object used to retrieve the test ID.
|
|
87
|
+
*/
|
|
54
88
|
class MockServerHandler {
|
|
55
89
|
constructor(owner) {
|
|
56
90
|
this.owner = owner;
|
|
@@ -100,9 +134,19 @@ function setupHolodeckFetch(owner, request) {
|
|
|
100
134
|
queryForTest
|
|
101
135
|
};
|
|
102
136
|
}
|
|
103
|
-
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Creates an adapterFor function that wraps the provided adapterFor function
|
|
140
|
+
* to override the adapter's _fetchRequest method to route requests through
|
|
141
|
+
* the Holodeck mock server.
|
|
142
|
+
*
|
|
143
|
+
* @param owner - The test context object used to retrieve the test ID.
|
|
144
|
+
*/
|
|
145
|
+
function createAdapterFor(owner, store) {
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
147
|
+
const adapterFor = store.adapterFor;
|
|
104
148
|
return function holodeckAdapterFor(modelName, _allowMissing) {
|
|
105
|
-
const adapter =
|
|
149
|
+
const adapter = adapterFor.call(this, modelName, _allowMissing);
|
|
106
150
|
if (adapter) {
|
|
107
151
|
if (!adapter.hasOverriddenFetch) {
|
|
108
152
|
adapter.hasOverriddenFetch = true;
|
|
@@ -122,6 +166,12 @@ function createAdapterFor(owner, fn) {
|
|
|
122
166
|
return adapter;
|
|
123
167
|
};
|
|
124
168
|
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Mock a request by sending the scaffold to the mock server.
|
|
172
|
+
*
|
|
173
|
+
* @public
|
|
174
|
+
*/
|
|
125
175
|
async function mock(owner, generate, isRecording) {
|
|
126
176
|
if (getIsRecording() || isRecording) {
|
|
127
177
|
const test = TEST_IDS.get(owner);
|
package/dist/mock.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { mock, getIsRecording } from './index.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
|
|
3
15
|
/**
|
|
4
16
|
* Sets up Mocking for a GET request on the mock server
|
|
5
17
|
* for the supplied url.
|
|
@@ -29,6 +41,10 @@ function GET(owner, url, response, options) {
|
|
|
29
41
|
}), getIsRecording() || (options?.RECORD ?? false));
|
|
30
42
|
}
|
|
31
43
|
const STATUS_TEXT_FOR = new Map([[200, 'OK'], [201, 'Created'], [202, 'Accepted'], [203, 'Non-Authoritative Information'], [204, 'No Content'], [205, 'Reset Content'], [206, 'Partial Content'], [207, 'Multi-Status'], [208, 'Already Reported'], [226, 'IM Used'], [300, 'Multiple Choices'], [301, 'Moved Permanently'], [302, 'Found'], [303, 'See Other'], [304, 'Not Modified'], [307, 'Temporary Redirect'], [308, 'Permanent Redirect'], [400, 'Bad Request'], [401, 'Unauthorized'], [402, 'Payment Required'], [403, 'Forbidden'], [404, 'Not Found'], [405, 'Method Not Allowed'], [406, 'Not Acceptable'], [407, 'Proxy Authentication Required'], [408, 'Request Timeout'], [409, 'Conflict'], [410, 'Gone'], [411, 'Length Required'], [412, 'Precondition Failed'], [413, 'Payload Too Large'], [414, 'URI Too Long'], [415, 'Unsupported Media Type'], [416, 'Range Not Satisfiable'], [417, 'Expectation Failed'], [419, 'Page Expired'], [420, 'Enhance Your Calm'], [421, 'Misdirected Request'], [422, 'Unprocessable Entity'], [423, 'Locked'], [424, 'Failed Dependency'], [425, 'Too Early'], [426, 'Upgrade Required'], [428, 'Precondition Required'], [429, 'Too Many Requests'], [430, 'Request Header Fields Too Large'], [431, 'Request Header Fields Too Large'], [450, 'Blocked By Windows Parental Controls'], [451, 'Unavailable For Legal Reasons'], [500, 'Internal Server Error'], [501, 'Not Implemented'], [502, 'Bad Gateway'], [503, 'Service Unavailable'], [504, 'Gateway Timeout'], [505, 'HTTP Version Not Supported'], [506, 'Variant Also Negotiates'], [507, 'Insufficient Storage'], [508, 'Loop Detected'], [509, 'Bandwidth Limit Exceeded'], [510, 'Not Extended'], [511, 'Network Authentication Required']]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Mock a POST request
|
|
47
|
+
*/
|
|
32
48
|
function POST(owner, url, response, options) {
|
|
33
49
|
return mock(owner, () => {
|
|
34
50
|
const body = response();
|
|
@@ -44,6 +60,10 @@ function POST(owner, url, response, options) {
|
|
|
44
60
|
};
|
|
45
61
|
}, getIsRecording() || (options?.RECORD ?? false));
|
|
46
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* mock a PUT request
|
|
66
|
+
*/
|
|
47
67
|
function PUT(owner, url, response, options) {
|
|
48
68
|
return mock(owner, () => {
|
|
49
69
|
const body = response();
|
|
@@ -59,6 +79,10 @@ function PUT(owner, url, response, options) {
|
|
|
59
79
|
};
|
|
60
80
|
}, getIsRecording() || (options?.RECORD ?? false));
|
|
61
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* mock a PATCH request
|
|
84
|
+
*
|
|
85
|
+
*/
|
|
62
86
|
function PATCH(owner, url, response, options) {
|
|
63
87
|
return mock(owner, () => {
|
|
64
88
|
const body = response();
|
|
@@ -74,6 +98,9 @@ function PATCH(owner, url, response, options) {
|
|
|
74
98
|
};
|
|
75
99
|
}, getIsRecording() || (options?.RECORD ?? false));
|
|
76
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* mock a DELETE request
|
|
103
|
+
*/
|
|
77
104
|
function DELETE(owner, url, response, options) {
|
|
78
105
|
return mock(owner, () => {
|
|
79
106
|
const body = response();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warp-drive/holodeck",
|
|
3
3
|
"description": "⚡️ Simple, Fast HTTP Mocking for Tests",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.15",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chris Thoburn <runspired@users.noreply.github.com>",
|
|
7
7
|
"repository": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"ensure-cert": "./server/ensure-cert.js"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@warp-drive/utilities": "5.8.0-alpha.
|
|
43
|
-
"@warp-drive/legacy": "5.8.0-alpha.
|
|
44
|
-
"@warp-drive/core": "5.8.0-alpha.
|
|
42
|
+
"@warp-drive/utilities": "5.8.0-alpha.15",
|
|
43
|
+
"@warp-drive/legacy": "5.8.0-alpha.15",
|
|
44
|
+
"@warp-drive/core": "5.8.0-alpha.15"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
|
47
47
|
"@warp-drive/utilities": {
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"@babel/preset-env": "^7.28.3",
|
|
58
58
|
"@babel/preset-typescript": "^7.27.1",
|
|
59
59
|
"@babel/runtime": "^7.28.3",
|
|
60
|
-
"@warp-drive/utilities": "5.8.0-alpha.
|
|
61
|
-
"@warp-drive/legacy": "5.8.0-alpha.
|
|
62
|
-
"@warp-drive/core": "5.8.0-alpha.
|
|
63
|
-
"@warp-drive/internal-config": "5.8.0-alpha.
|
|
60
|
+
"@warp-drive/utilities": "5.8.0-alpha.15",
|
|
61
|
+
"@warp-drive/legacy": "5.8.0-alpha.15",
|
|
62
|
+
"@warp-drive/core": "5.8.0-alpha.15",
|
|
63
|
+
"@warp-drive/internal-config": "5.8.0-alpha.15",
|
|
64
64
|
"vite": "^7.1.3"
|
|
65
65
|
},
|
|
66
66
|
"exports": {
|
package/server/ensure-cert.js
CHANGED
|
@@ -11,6 +11,10 @@ function getShellConfigFilePath() {
|
|
|
11
11
|
return path.join(homedir(), '.zshrc');
|
|
12
12
|
case '/bin/bash':
|
|
13
13
|
return path.join(homedir(), '.bashrc');
|
|
14
|
+
case '/opt/homebrew/bin/fish':
|
|
15
|
+
case '/usr/local/bin/fish':
|
|
16
|
+
case '/bin/fish':
|
|
17
|
+
return path.join(homedir(), '.config', 'fish', 'config.fish');
|
|
14
18
|
default:
|
|
15
19
|
throw Error(
|
|
16
20
|
`Unable to determine configuration file for shell: ${shell}. Manual SSL Cert Setup Required for Holodeck.`
|
package/server/index.js
CHANGED
|
@@ -301,6 +301,10 @@ function createTestHandler(projectRoot) {
|
|
|
301
301
|
body: body ? body : null,
|
|
302
302
|
testRequestNumber,
|
|
303
303
|
});
|
|
304
|
+
|
|
305
|
+
console.log(
|
|
306
|
+
`Replaying mock for ${req.method} ${niceUrl} (test: ${testId} request #${testRequestNumber}) from '${cacheKey}' if available`
|
|
307
|
+
);
|
|
304
308
|
return replayRequest(context, cacheKey);
|
|
305
309
|
}
|
|
306
310
|
} catch (e) {
|
|
@@ -369,6 +373,12 @@ async function waitForLog(server, logMessage) {
|
|
|
369
373
|
}
|
|
370
374
|
}
|
|
371
375
|
|
|
376
|
+
async function reprintLogs(server) {
|
|
377
|
+
for await (const chunk of server.stdout) {
|
|
378
|
+
process.stdout.write(chunk);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
372
382
|
/*
|
|
373
383
|
{ port?: number, projectRoot: string }
|
|
374
384
|
*/
|
|
@@ -385,6 +395,7 @@ export async function createServer(options, useBun = false) {
|
|
|
385
395
|
});
|
|
386
396
|
|
|
387
397
|
await waitForLog(server, 'Serving Holodeck HTTP Mocks');
|
|
398
|
+
void reprintLogs(server);
|
|
388
399
|
|
|
389
400
|
return {
|
|
390
401
|
terminate() {
|