@warp-drive/holodeck 0.1.0-alpha.88 → 0.1.0-alpha.90
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/README.md +14 -185
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/mock.d.ts +22 -0
- package/dist/mock.d.ts.map +1 -1
- package/dist/mock.js +20 -0
- package/dist/mock.js.map +1 -1
- package/logos/README.md +1 -1
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -49,13 +49,14 @@
|
|
|
49
49
|
- 🔥 Blazing Fast Tests
|
|
50
50
|
- record your tests when you change them
|
|
51
51
|
- replays from cache until you change them again
|
|
52
|
+
- the cache is managed by git, so switching branches works seamlessly, and CI and
|
|
53
|
+
rebases skip work that is already recorded
|
|
52
54
|
- zero-work: setup work is skipped when in replay mode
|
|
53
55
|
|
|
54
56
|
## Installation
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
pnpm install @warp-drive/holodeck
|
|
58
|
+
```sh
|
|
59
|
+
pnpm add -E @warp-drive/holodeck@canary
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
**Tagged Releases**
|
|
@@ -68,190 +69,18 @@ pnpm install @warp-drive/holodeck
|
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
|
|
71
|
-
##
|
|
72
|
-
#### Mocking from Within a Test
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
import { GET } from '@warp-drive/holodeck/mock';
|
|
76
|
-
|
|
77
|
-
await GET(context, 'users/1', () => ({
|
|
78
|
-
data: {
|
|
79
|
-
id: '1',
|
|
80
|
-
type: 'user',
|
|
81
|
-
attributes: {
|
|
82
|
-
name: 'Chris Thoburn',
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
// set RECORD to false or remove
|
|
87
|
-
// the options hash entirely once the request
|
|
88
|
-
// has been recorded
|
|
89
|
-
}), { RECORD: true });
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Motivations
|
|
93
|
-
|
|
94
|
-
Comprehensive DX around data management should extend to testing.
|
|
95
|
-
|
|
96
|
-
### ✨ Amazing Developer Experience
|
|
97
|
-
|
|
98
|
-
EmberData already understands your data schemas. Building a mocking utility with tight integration into your data usage patterns could bring enormous DX and test suite performance benefits.
|
|
99
|
-
|
|
100
|
-
Building a real mock server instead of intercepting requests in the browser or via ServiceWorker gives us out-of-the-box DX, better tunability, and greater ability to optimize test suite performance. Speed is the ultimate DX.
|
|
101
|
-
|
|
102
|
-
### 🔥 Blazing Fast Tests
|
|
103
|
-
|
|
104
|
-
We've noticed test suites spending an enormous amount of time creating and tearing down mock state in between tests. To combat this, we want to provide
|
|
105
|
-
an approach built over `http/3` (`http/2` for now) utilizing aggressive caching
|
|
106
|
-
and `brotli` minification in a way that can be replayed over and over again.
|
|
107
|
-
|
|
108
|
-
Basically, pay the cost when you write the test. Forever after skip the cost until you need to edit the test again.
|
|
109
|
-
|
|
110
|
-
## Setup
|
|
111
|
-
|
|
112
|
-
### Use with WarpDrive
|
|
113
|
-
|
|
114
|
-
First, you will need to add the holodeck handler to the request manager chain prior to `Fetch` (or any equivalent handler that proceeds to network).
|
|
115
|
-
|
|
116
|
-
For instance:
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
import RequestManager from '@ember-data/request';
|
|
120
|
-
import Fetch from '@ember-data/request/fetch';
|
|
121
|
-
import { MockServerHandler } from '@warp-drive/holodeck';
|
|
122
|
-
|
|
123
|
-
const manager = new RequestManager()
|
|
124
|
-
.use([new MockServerHandler(testContext), Fetch]);
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
From within a test this might look like:
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
import RequestManager from '@ember-data/request';
|
|
131
|
-
import Fetch from '@ember-data/request/fetch';
|
|
132
|
-
import { MockServerHandler } from '@warp-drive/holodeck';
|
|
133
|
-
import { module, test } from 'qunit';
|
|
134
|
-
|
|
135
|
-
module('my module', function() {
|
|
136
|
-
test('my test', async function() {
|
|
137
|
-
const manager = new RequestManager()
|
|
138
|
-
.use([new MockServerHandler(this), Fetch]);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Next, you will need to configure holodeck to understand your tests contexts. For qunit and diagnostic
|
|
144
|
-
in a project using Ember this is typically done in `tests/test-helper.js`
|
|
145
|
-
|
|
146
|
-
#### With Diagnostic
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
import { setupGlobalHooks } from '@warp-drive/diagnostic';
|
|
150
|
-
import { setConfig, setTestId } from '@warp-drive/holodeck';
|
|
151
|
-
|
|
152
|
-
// if not proxying the port / set port to the correct value here
|
|
153
|
-
const MockHost = `https://${window.location.hostname}:${Number(window.location.port) + 1}`;
|
|
154
|
-
|
|
155
|
-
setConfig({ host: MockHost });
|
|
156
|
-
|
|
157
|
-
setupGlobalHooks((hooks) => {
|
|
158
|
-
hooks.beforeEach(function (assert) {
|
|
159
|
-
setTestId(this, assert.test.testId);
|
|
160
|
-
});
|
|
161
|
-
hooks.afterEach(function () {
|
|
162
|
-
setTestId(this, null);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
#### With QUnit
|
|
168
|
-
|
|
169
|
-
```ts
|
|
170
|
-
import * as QUnit from 'qunit';
|
|
171
|
-
import { setConfig, setTestId } from '@warp-drive/holodeck';
|
|
172
|
-
|
|
173
|
-
// if not proxying the port / set port to the correct value here
|
|
174
|
-
const MockHost = `https://${window.location.hostname}:${Number(window.location.port) + 1}`;
|
|
175
|
-
|
|
176
|
-
setConfig({ host: MockHost });
|
|
177
|
-
|
|
178
|
-
QUnit.hooks.beforeEach(function (assert) {
|
|
179
|
-
setTestId(this, assert.test.testId);
|
|
180
|
-
});
|
|
181
|
-
QUnit.hooks.afterEach(function (assert) {
|
|
182
|
-
setTestId(this, null);
|
|
183
|
-
});
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Testem
|
|
187
|
-
|
|
188
|
-
You can integrate holodeck with Testem using testem's [async config capability](https://github.com/testem/testem/blob/master/docs/config_file.md#returning-a-promise-from-testemjs):
|
|
189
|
-
|
|
190
|
-
```ts
|
|
191
|
-
module.exports = async function () {
|
|
192
|
-
const holodeck = (await import('@warp-drive/holodeck')).default;
|
|
193
|
-
await holodeck.launchProgram({
|
|
194
|
-
port: 7373,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
process.on('beforeExit', async () => {
|
|
198
|
-
await holodeck.endProgram();
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
// ... testem config
|
|
203
|
-
};
|
|
204
|
-
};
|
|
205
|
-
```
|
|
72
|
+
## Documentation
|
|
206
73
|
|
|
207
|
-
|
|
74
|
+
API docs for this package → [@warp-drive/holodeck](https://warp-drive.io/api/@warp-drive/holodeck/)
|
|
208
75
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
await holodeck.endProgram();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
"proxies": {
|
|
222
|
-
"/api": {
|
|
223
|
-
// holodeck always runs on https
|
|
224
|
-
// the proxy is transparent so this means /api/v1 will route to https://localhost:7373/api/v1
|
|
225
|
-
"target": "https://localhost:7373",
|
|
226
|
-
// "onlyContentTypes": ["xml", "json"],
|
|
227
|
-
// if test suite is on http, set this to false
|
|
228
|
-
// "secure": false,
|
|
229
|
-
},
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
};
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### Diagnostic
|
|
236
|
-
|
|
237
|
-
holodeck can be launched and cleaned up using the lifecycle hooks in the launch config
|
|
238
|
-
for diagnostic in `diagnostic.js`:
|
|
239
|
-
|
|
240
|
-
```ts
|
|
241
|
-
import { launch } from '@warp-drive/diagnostic/server';
|
|
242
|
-
import holodeck from '@warp-drive/holodeck';
|
|
243
|
-
|
|
244
|
-
await launch({
|
|
245
|
-
async setup(options) {
|
|
246
|
-
await holodeck.launchProgram({
|
|
247
|
-
port: options.port + 1,
|
|
248
|
-
});
|
|
249
|
-
},
|
|
250
|
-
async cleanup() {
|
|
251
|
-
await holodeck.endProgram();
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
```
|
|
76
|
+
- [Testing Overview](https://warp-drive.io/guides/the-manual/testing/) why a real server, and how record and replay works
|
|
77
|
+
- [Server Setup](https://warp-drive.io/guides/the-manual/testing/server-setup) certificates, and launching with Diagnostic or Testem
|
|
78
|
+
- [Client Setup](https://warp-drive.io/guides/the-manual/testing/client-setup) adding `MockServerHandler` to the request chain
|
|
79
|
+
- [Test Framework Integration](https://warp-drive.io/guides/the-manual/testing/test-framework-integration) test ids and the mock host
|
|
80
|
+
- [Common Setups](https://warp-drive.io/guides/the-manual/testing/common-setups/) one origin for the page and the mock server, from Vite, testem, or Caddy
|
|
81
|
+
- [Writing Mocks](https://warp-drive.io/guides/the-manual/testing/writing-mocks) the mock helpers and the matching rules
|
|
82
|
+
- [Recording and Replaying](https://warp-drive.io/guides/the-manual/testing/record-and-replay) modes, fixtures, and CI
|
|
83
|
+
- [Troubleshooting](https://warp-drive.io/guides/the-manual/testing/troubleshooting) indexed by the errors holodeck prints
|
|
255
84
|
|
|
256
85
|
### ♥️ Credits
|
|
257
86
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4,20 +4,27 @@ import { RequestContext, StructuredDataDocument } from "@warp-drive/core/types/r
|
|
|
4
4
|
import { Store } from "@warp-drive/legacy/store";
|
|
5
5
|
//#region src/index.d.ts
|
|
6
6
|
/**
|
|
7
|
+
* @summary Sets the host url of the Holodeck mock server that recordings are sent to.
|
|
7
8
|
* @public
|
|
8
9
|
*/
|
|
9
10
|
export declare function setConfig({ host }: {
|
|
10
11
|
host: string;
|
|
11
12
|
}): void;
|
|
12
13
|
/**
|
|
14
|
+
* @summary Assigns a test context its Holodeck test id before a test, or clears it after, reporting an error for any
|
|
15
|
+
* mock the test never requested.
|
|
13
16
|
* @public
|
|
14
17
|
*/
|
|
15
18
|
export declare function setTestId(context: object, str: string | null): void;
|
|
16
19
|
/**
|
|
20
|
+
* @summary Overrides at runtime whether Holodeck mocks are recorded to fixtures, in place of the build-time
|
|
21
|
+
* `SHOULD_RECORD` flag.
|
|
17
22
|
* @public
|
|
18
23
|
*/
|
|
19
24
|
export declare function setIsRecording(value: boolean): void;
|
|
20
25
|
/**
|
|
26
|
+
* @summary Reports whether Holodeck mocks are recorded to fixtures, using the `setIsRecording` override when set or the
|
|
27
|
+
* build-time `SHOULD_RECORD` flag otherwise.
|
|
21
28
|
* @public
|
|
22
29
|
*/
|
|
23
30
|
export declare function getIsRecording(): boolean;
|
|
@@ -30,6 +37,8 @@ export declare function getIsRecording(): boolean;
|
|
|
30
37
|
*
|
|
31
38
|
* Requires that the test context be configured with a testId using `setTestId`.
|
|
32
39
|
*
|
|
40
|
+
* @summary RequestManager handler that routes requests to the Holodeck mock server by tagging each url with the test id
|
|
41
|
+
* and request count.
|
|
33
42
|
* @param owner - the test context object used to retrieve the test ID.
|
|
34
43
|
*/
|
|
35
44
|
export declare class MockServerHandler implements Handler {
|
|
@@ -42,12 +51,16 @@ export declare class MockServerHandler implements Handler {
|
|
|
42
51
|
* to override the adapter's _fetchRequest method to route requests through
|
|
43
52
|
* the Holodeck mock server.
|
|
44
53
|
*
|
|
54
|
+
* @summary Patches a legacy store so its adapters send `_fetchRequest` calls through the Holodeck mock server for the
|
|
55
|
+
* given test context.
|
|
45
56
|
* @param owner - The test context object used to retrieve the test ID.
|
|
46
57
|
*/
|
|
47
58
|
export declare function installAdapterFor(owner: object, store: Store): void;
|
|
48
59
|
/**
|
|
49
60
|
* Mock a request by sending the scaffold to the mock server.
|
|
50
61
|
*
|
|
62
|
+
* @summary Registers a mock response with the Holodeck server for the current test, recording it as a fixture when
|
|
63
|
+
* recording and only counting it when replaying.
|
|
51
64
|
* @public
|
|
52
65
|
*/
|
|
53
66
|
export declare function mock(owner: object, generate: ScaffoldGenerator | LazyScaffold, isRecording?: boolean): Promise<void>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;wBAgJgB,YAAY;EAAU;;;;;;;wBAUtB,UAAU,iBAAiB;;;;;;wBAsG3B,eAAe;;;;;;wBASf;;;;;;;;;;;;;;qBAiBH,6BAA6B;EACxC;EACA,YAAY;EAGZ,QAAc,GAAG,SAAS,gBAAgB,MAAM,OAAO,KAAK,QAAQ,uBAAuB;;;;;;;;;;;wBA0I7E,kBAAkB,eAAe,OAAO;;;;;;;;wBAyClC,KACpB,eACA,UAAU,oBAAoB,cAC9B,wBACC"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import { getGlobalConfig, macroCondition } from "@embroider/macros";
|
|
|
2
2
|
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
/**
|
|
5
|
+
* @summary Test utilities for mocking HTTP requests against the Holodeck mock server, which records responses as
|
|
6
|
+
* fixtures and replays them in later runs.
|
|
5
7
|
* @module
|
|
6
8
|
* @mergeModuleWith <project>
|
|
7
9
|
*/
|
|
@@ -12,12 +14,15 @@ const TEST_IDS = /* @__PURE__ */ new WeakMap();
|
|
|
12
14
|
*/
|
|
13
15
|
let HOST = "/";
|
|
14
16
|
/**
|
|
17
|
+
* @summary Sets the host url of the Holodeck mock server that recordings are sent to.
|
|
15
18
|
* @public
|
|
16
19
|
*/
|
|
17
20
|
function setConfig({ host }) {
|
|
18
21
|
HOST = host.endsWith("/") ? host : `${host}/`;
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
24
|
+
* @summary Assigns a test context its Holodeck test id before a test, or clears it after, reporting an error for any
|
|
25
|
+
* mock the test never requested.
|
|
21
26
|
* @public
|
|
22
27
|
*/
|
|
23
28
|
function setTestId(context, str) {
|
|
@@ -96,12 +101,16 @@ function reportUnrequestedMocks(test) {
|
|
|
96
101
|
const shouldRecord = macroCondition(getGlobalConfig().WarpDrive.env.SHOULD_RECORD) ? true : false;
|
|
97
102
|
let IS_RECORDING = null;
|
|
98
103
|
/**
|
|
104
|
+
* @summary Overrides at runtime whether Holodeck mocks are recorded to fixtures, in place of the build-time
|
|
105
|
+
* `SHOULD_RECORD` flag.
|
|
99
106
|
* @public
|
|
100
107
|
*/
|
|
101
108
|
function setIsRecording(value) {
|
|
102
109
|
IS_RECORDING = value === null ? value : Boolean(value);
|
|
103
110
|
}
|
|
104
111
|
/**
|
|
112
|
+
* @summary Reports whether Holodeck mocks are recorded to fixtures, using the `setIsRecording` override when set or the
|
|
113
|
+
* build-time `SHOULD_RECORD` flag otherwise.
|
|
105
114
|
* @public
|
|
106
115
|
*/
|
|
107
116
|
function getIsRecording() {
|
|
@@ -116,6 +125,8 @@ function getIsRecording() {
|
|
|
116
125
|
*
|
|
117
126
|
* Requires that the test context be configured with a testId using `setTestId`.
|
|
118
127
|
*
|
|
128
|
+
* @summary RequestManager handler that routes requests to the Holodeck mock server by tagging each url with the test id
|
|
129
|
+
* and request count.
|
|
119
130
|
* @param owner - the test context object used to retrieve the test ID.
|
|
120
131
|
*/
|
|
121
132
|
var MockServerHandler = class {
|
|
@@ -192,6 +203,8 @@ function upgradeStore(store) {
|
|
|
192
203
|
* to override the adapter's _fetchRequest method to route requests through
|
|
193
204
|
* the Holodeck mock server.
|
|
194
205
|
*
|
|
206
|
+
* @summary Patches a legacy store so its adapters send `_fetchRequest` calls through the Holodeck mock server for the
|
|
207
|
+
* given test context.
|
|
195
208
|
* @param owner - The test context object used to retrieve the test ID.
|
|
196
209
|
*/
|
|
197
210
|
function installAdapterFor(owner, store) {
|
|
@@ -218,6 +231,8 @@ function installAdapterFor(owner, store) {
|
|
|
218
231
|
/**
|
|
219
232
|
* Mock a request by sending the scaffold to the mock server.
|
|
220
233
|
*
|
|
234
|
+
* @summary Registers a mock response with the Holodeck server for the current test, recording it as a fixture when
|
|
235
|
+
* recording and only counting it when replaying.
|
|
221
236
|
* @public
|
|
222
237
|
*/
|
|
223
238
|
async function mock(owner, generate, isRecording) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["getGlobalConfig","macroCondition","TEST_IDS","WeakMap","HOST","setConfig","host","endsWith","setTestId","context","str","has","Error","set","id","mock","GET","PUT","PATCH","DELETE","POST","QUERY","OPTIONS","HEAD","CONNECT","TRACE","request","test","get","delete","reportUnrequestedMocks","normalizeUrlKey","url","withoutOrigin","replace","startsWith","slice","unrequested","method","Object","keys","mocked","requested","requestCounts","Map","key","mockCount","requestCount","push","length","join","shouldRecord","WarpDrive","env","SHOULD_RECORD","IS_RECORDING","setIsRecording","value","Boolean","getIsRecording","MockServerHandler","constructor","owner","next","queryForTest","setupHolodeckFetch","assign","future","setStream","getStream","e","DOMException","explanation","getHolodeckExplanation","message","split","HOLODECK_ERROR_CODES","Set","content","errors","Array","isArray","filter","error","code","map","detail","firstChar","includes","toUpperCase","console","log","mode","credentials","referrerPolicy","headers","Headers","upgradeAdapter","adapter","upgradeStore","store","adapterFor","installAdapterFor","fn","holodeckAdapterFor","modelName","_allowMissing","call","hasOverriddenFetch","useFetch","originalFetch","_fetchRequest","bind","options","String","generate","isRecording","mockUrl","buildScaffold","scaffold","mockMethod","testMockNum","requestToMock","response","fetch","body","JSON","stringify","ok","status","statusText","getRecordFailureDetail","json"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @module\n * @mergeModuleWith <project>\n */\nimport { SHOULD_RECORD } from '@warp-drive/core/build-config/env';\nimport type { Handler, NextFn } from '@warp-drive/core/request';\nimport type { HTTPMethod, RequestContext, RequestInfo, StructuredDataDocument } from '@warp-drive/core/types/request';\nimport type { MinimumAdapterInterface } from '@warp-drive/legacy/compat';\nimport type { Store } from '@warp-drive/legacy/store';\n\nimport type { LazyScaffold, Scaffold, ScaffoldGenerator } from './mock';\n\nconst TEST_IDS = new WeakMap<\n object,\n {\n id: string;\n /**\n * keeps track of the count of calls to record a mock\n */\n mock: {\n /**\n * For each GET, we keep track of the count\n * for a specific URL\n */\n GET: Record<string, number>;\n /**\n * For each PUT, we keep track of the count\n * for a specific URL\n */\n PUT: Record<string, number>;\n /**\n * For each PATCH, we keep track of the count\n * for a specific URL\n */\n PATCH: Record<string, number>;\n /**\n * For each DELETE, we keep track of the count\n * for a specific URL\n */\n DELETE: Record<string, number>;\n /**\n * For each POST, we keep track of the count\n * for a specific URL\n */\n POST: Record<string, number>;\n /**\n * For each OPTIONS, we keep track of the count\n * for a specific URL\n */\n OPTIONS: Record<string, number>;\n /**\n * For each QUERY, we keep track of the count\n * for a specific URL\n */\n QUERY: Record<string, number>;\n /**\n * for each HEAD, we keep track of the count\n * for a specific URL\n */\n HEAD: Record<string, number>;\n /**\n * for each CONNECT, we keep track of the count\n * for a specific URL\n */\n CONNECT: Record<string, number>;\n /**\n * for each TRACE, we keep track of the count\n * for a specific URL\n */\n TRACE: Record<string, number>;\n };\n /**\n * keeps track of the count of calls to make a request\n */\n request: {\n /**\n * For each GET, we keep track of the count\n * for a specific URL\n */\n GET: Record<string, number>;\n /**\n * For each PUT, we keep track of the count\n * for a specific URL\n */\n PUT: Record<string, number>;\n /**\n * For each PATCH, we keep track of the count\n * for a specific URL\n */\n PATCH: Record<string, number>;\n /**\n * For each DELETE, we keep track of the count\n * for a specific URL\n */\n DELETE: Record<string, number>;\n /**\n * For each POST, we keep track of the count\n * for a specific URL\n */\n POST: Record<string, number>;\n /**\n * For each OPTIONS, we keep track of the count\n * for a specific URL\n */\n OPTIONS: Record<string, number>;\n /**\n * For each QUERY, we keep track of the count\n * for a specific URL\n */\n QUERY: Record<string, number>;\n /**\n * for each HEAD, we keep track of the count\n * for a specific URL\n */\n HEAD: Record<string, number>;\n /**\n * for each CONNECT, we keep track of the count\n * for a specific URL\n */\n CONNECT: Record<string, number>;\n /**\n * for each TRACE, we keep track of the count\n * for a specific URL\n */\n TRACE: Record<string, number>;\n };\n }\n>();\n\n/**\n * The shape `setTestId` stores per test context, named so the report below can\n * take one as an argument.\n */\ntype TestEntry = NonNullable<ReturnType<(typeof TEST_IDS)['get']>>;\n\nlet HOST = '/';\n\n/**\n * @public\n */\n\nexport function setConfig({ host }: { host: string }): void {\n HOST = host.endsWith('/') ? host : `${host}/`;\n}\n\n/**\n * @public\n */\n\nexport function setTestId(context: object, str: string | null): void {\n if (str && TEST_IDS.has(context)) {\n throw new Error(`MockServerHandler is already configured with a testId.`);\n }\n if (str) {\n TEST_IDS.set(context, {\n id: str,\n mock: {\n GET: {},\n PUT: {},\n PATCH: {},\n DELETE: {},\n POST: {},\n QUERY: {},\n OPTIONS: {},\n HEAD: {},\n CONNECT: {},\n TRACE: {},\n },\n request: {\n GET: {},\n PUT: {},\n PATCH: {},\n DELETE: {},\n POST: {},\n QUERY: {},\n OPTIONS: {},\n HEAD: {},\n CONNECT: {},\n TRACE: {},\n },\n });\n } else {\n const test = TEST_IDS.get(context);\n TEST_IDS.delete(context);\n\n if (test) {\n reportUnrequestedMocks(test);\n }\n }\n}\n\n/**\n * A mock is declared relative to the mock server (`users/1`) while the request\n * carries the absolute url the code under test built\n * (`https://localhost:7358/users/1`). The mock server reconciles the two by\n * keying a fixture on the request's path, so compare them the same way.\n */\nfunction normalizeUrlKey(url: string): string {\n const withoutOrigin = url.replace(/^[a-z][a-z0-9+.-]*:\\/\\/[^/]+/i, '');\n return withoutOrigin.startsWith('/') ? withoutOrigin.slice(1) : withoutOrigin;\n}\n\n/**\n * A mock the test never requested proves nothing: the test passes whether or\n * not the code under test would have made that request. Nothing compared the\n * two counters holodeck already keeps, so report the difference here, from the\n * `afterEach` every suite runs, which fails the test that leaked.\n *\n * The original assertions are not masked by this: a framework reports an\n * `afterEach` throw alongside the results the test body already recorded\n * rather than in place of them.\n */\nfunction reportUnrequestedMocks(test: TestEntry): void {\n const unrequested: string[] = [];\n\n for (const method of Object.keys(test.mock) as HTTPMethod[]) {\n const mocked = test.mock[method];\n const requested = test.request[method] ?? {};\n const requestCounts = new Map<string, number>();\n\n for (const url of Object.keys(requested)) {\n const key = normalizeUrlKey(url);\n requestCounts.set(key, (requestCounts.get(key) ?? 0) + requested[url]);\n }\n\n for (const url of Object.keys(mocked)) {\n const mockCount = mocked[url];\n const requestCount = requestCounts.get(normalizeUrlKey(url)) ?? 0;\n\n if (mockCount > requestCount) {\n unrequested.push(`\\t${method} ${url} (mocked ${mockCount}, requested ${requestCount})`);\n }\n }\n }\n\n if (unrequested.length) {\n throw new Error(\n `Holodeck: this test declared mocks it never requested.\\n\\n${unrequested.join('\\n')}\\n\\n` +\n `A mock that is never requested proves nothing. Remove it, or make the request it describes.`\n );\n }\n}\n\nconst shouldRecord = SHOULD_RECORD ? true : false;\nlet IS_RECORDING: boolean | null = null;\n\n/**\n * @public\n */\nexport function setIsRecording(value: boolean): void {\n IS_RECORDING = value === null ? value : Boolean(value);\n}\n\n/**\n * @public\n */\nexport function getIsRecording(): boolean {\n return IS_RECORDING === null ? shouldRecord : IS_RECORDING;\n}\n\n/**\n * A request handler that intercepts requests and routes them through\n * the Holodeck mock server.\n *\n * This handler modifies the request URL to include test identifiers\n * and manages request counts for accurate mocking.\n *\n * Requires that the test context be configured with a testId using `setTestId`.\n *\n * @param owner - the test context object used to retrieve the test ID.\n */\nexport class MockServerHandler implements Handler {\n declare owner: object;\n constructor(owner: object) {\n this.owner = owner;\n }\n async request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>> {\n const { request, queryForTest } = setupHolodeckFetch(this.owner, Object.assign({}, context.request));\n\n try {\n const future = next(request);\n context.setStream(future.getStream());\n return await future;\n } catch (e) {\n if (e instanceof Error && !(e instanceof DOMException)) {\n const explanation = getHolodeckExplanation(e);\n if (explanation) {\n e.message = `${e.message}\\n\\n${explanation}`;\n }\n e.message = e.message.split(queryForTest).join('');\n }\n throw e;\n }\n }\n}\n\nconst HOLODECK_ERROR_CODES = new Set([\n 'MOCK_NOT_FOUND',\n 'MISSING_X_TEST_ID_HEADER',\n 'MISSING_X_TEST_REQUEST_NUMBER_HEADER',\n]);\n\n/**\n * The mock server explains itself in the response body, which the thrown\n * error only carries as data. Lift that explanation into the message so it\n * reaches a terminal and a CI log.\n */\nfunction getHolodeckExplanation(e: Error): string | null {\n const { content } = e as Error & { content?: unknown };\n if (!content || typeof content !== 'object') {\n return null;\n }\n const { errors } = content as { errors?: unknown };\n if (!Array.isArray(errors)) {\n return null;\n }\n\n return (\n errors\n .filter((error): error is { code: string; detail?: string } => {\n return (\n !!error && typeof error === 'object' && HOLODECK_ERROR_CODES.has((error as { code?: unknown }).code as string)\n );\n })\n .map((error) => error.detail)\n .filter((detail): detail is string => typeof detail === 'string')\n .join('\\n\\n') || null\n );\n}\n\nfunction setupHolodeckFetch(owner: object, request: RequestInfo): { request: RequestInfo; queryForTest: string } {\n const test = TEST_IDS.get(owner);\n if (!test) {\n throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);\n }\n\n const url = request.url!;\n const firstChar = url.includes('?') ? '&' : '?';\n const method = (request.method?.toUpperCase() ?? 'GET') as HTTPMethod;\n\n // enable custom methods\n if (!test.request[method]) {\n // oxlint-disable-next-line no-console\n console.log(`⚠️ Using custom HTTP method ${method} for response to request ${url}`);\n\n test.request[method] = {};\n }\n if (!(url in test.request[method])) {\n test.request[method][url] = 0;\n }\n\n const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${test.request[method][url]++}`;\n request.url = url + queryForTest;\n request.method = method;\n\n request.mode = 'cors';\n request.credentials = 'omit';\n request.referrerPolicy = '';\n\n // since holodeck currently runs on a separate port\n // and we don't want to trigger cors pre-flight\n // we convert PUT to POST to keep the request in the\n // \"simple\" cors category.\n // if (request.method === 'PUT') {\n // request.method = 'POST';\n // }\n\n const headers = new Headers(request.headers);\n if (headers.has('Content-Type')) {\n // under the rules of simple-cors, content-type can only be\n // one of three things, none of which are what folks typically\n // set this to. Since holodeck always expects body to be JSON\n // this \"just works\".\n headers.set('Content-Type', 'text/plain');\n request.headers = headers;\n }\n\n return { request, queryForTest };\n}\n\ninterface HasAdapterForFn {\n adapterFor(this: Store, modelName: string): MinimumAdapterInterface;\n adapterFor(this: Store, modelName: string, _allowMissing?: true): MinimumAdapterInterface | undefined;\n}\n\n/*\n _fetchRequest(options: FetchRequestInit): Promise<Response> {\n const fetchFunction = fetch();\n\n return fetchFunction(options.url, options);\n }\n*/\ninterface PrivateAdapter {\n _fetchRequest(options: RequestInfo): Promise<Response>;\n hasOverriddenFetch: boolean;\n useFetch: boolean;\n}\n\nfunction upgradeAdapter(adapter: unknown): asserts adapter is PrivateAdapter {}\nfunction upgradeStore(store: Store): asserts store is Store & { adapterFor: HasAdapterForFn['adapterFor'] } {\n if (typeof store.adapterFor !== 'function') {\n throw new Error('Store is not compatible with Holodeck. Missing adapterFor method.');\n }\n}\n\n/**\n * Creates an adapterFor function that wraps the provided adapterFor function\n * to override the adapter's _fetchRequest method to route requests through\n * the Holodeck mock server.\n *\n * @param owner - The test context object used to retrieve the test ID.\n */\nexport function installAdapterFor(owner: object, store: Store): void {\n upgradeStore(store);\n const fn = store.adapterFor;\n function holodeckAdapterFor(\n this: Store,\n modelName: string,\n _allowMissing?: true\n ): MinimumAdapterInterface | undefined {\n const adapter = fn.call(this, modelName, _allowMissing);\n\n if (adapter) {\n upgradeAdapter(adapter);\n\n if (!adapter.hasOverriddenFetch) {\n adapter.hasOverriddenFetch = true;\n adapter.useFetch = true;\n const originalFetch = adapter._fetchRequest?.bind(adapter);\n\n adapter._fetchRequest = function (options: RequestInfo) {\n if (!originalFetch) {\n throw new Error(`Adapter ${String(modelName)} does not implement _fetchRequest`);\n }\n const { request } = setupHolodeckFetch(owner, options);\n\n return originalFetch(request);\n };\n }\n }\n\n return adapter;\n }\n store.adapterFor = holodeckAdapterFor as HasAdapterForFn['adapterFor'];\n}\n\n/**\n * Mock a request by sending the scaffold to the mock server.\n *\n * @public\n */\nexport async function mock(\n owner: object,\n generate: ScaffoldGenerator | LazyScaffold,\n isRecording?: boolean\n): Promise<void> {\n const test = TEST_IDS.get(owner);\n if (!test) {\n throw new Error(`Cannot call \"mock\" before configuring a testId. Use setTestId to set the testId for each test`);\n }\n\n // A LazyScaffold carries its identity, so its body is built only when\n // recording. A bare generator is the only source of its own identity, so it\n // has to run either way; the helpers in ./mock never pass one.\n let method: string;\n let mockUrl: string;\n let buildScaffold: () => Scaffold;\n if (typeof generate === 'function') {\n const scaffold = generate();\n ({ method, url: mockUrl } = scaffold);\n buildScaffold = () => scaffold;\n } else {\n ({ method, url: mockUrl } = generate);\n buildScaffold = generate.scaffold;\n }\n if (!mockUrl || !method) {\n throw new Error(`MockError: Cannot mock a request without providing a URL and Method`);\n }\n const mockMethod = (method.toUpperCase() ?? 'GET') as HTTPMethod;\n\n // enable custom methods\n if (!test.mock[mockMethod]) {\n // oxlint-disable-next-line no-console\n console.log(`⚠️ Using custom HTTP method ${mockMethod} for response to request ${mockUrl}`);\n test.mock[mockMethod] = {};\n }\n if (!(mockUrl in test.mock[mockMethod])) {\n test.mock[mockMethod][mockUrl] = 0;\n }\n // counted whether or not we are recording, so that a mock the test never\n // requests is reported in replay runs too, not only while recording.\n const testMockNum = test.mock[mockMethod][mockUrl]++;\n\n // `isRecording` is the per-request RECORD override, which records even when\n // the suite as a whole is replaying.\n if (!getIsRecording() && !isRecording) {\n return;\n }\n\n const requestToMock = buildScaffold();\n const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;\n const response = await fetch(url, {\n method: 'POST',\n body: JSON.stringify(requestToMock),\n mode: 'cors',\n credentials: 'omit',\n referrerPolicy: '',\n });\n\n if (!response.ok) {\n throw new Error(\n `MockError: Holodeck failed to record ${mockMethod} ${mockUrl} (${response.status} ${response.statusText}). ${await getRecordFailureDetail(response)}`\n );\n }\n}\n\n/**\n * A failed recording is otherwise invisible until the next replay run fails\n * with a missing fixture, so report what the server said at the point of\n * failure.\n */\nasync function getRecordFailureDetail(response: Response): Promise<string> {\n try {\n const body = (await response.json()) as { errors?: { detail?: string }[] };\n const detail = body.errors?.[0]?.detail;\n return detail ?? 'The mock server gave no explanation.';\n } catch {\n return 'The mock server gave no explanation.';\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["getGlobalConfig","macroCondition","TEST_IDS","WeakMap","HOST","setConfig","host","endsWith","setTestId","context","str","has","Error","set","id","mock","GET","PUT","PATCH","DELETE","POST","QUERY","OPTIONS","HEAD","CONNECT","TRACE","request","test","get","delete","reportUnrequestedMocks","normalizeUrlKey","url","withoutOrigin","replace","startsWith","slice","unrequested","method","Object","keys","mocked","requested","requestCounts","Map","key","mockCount","requestCount","push","length","join","shouldRecord","WarpDrive","env","SHOULD_RECORD","IS_RECORDING","setIsRecording","value","Boolean","getIsRecording","MockServerHandler","constructor","owner","next","queryForTest","setupHolodeckFetch","assign","future","setStream","getStream","e","DOMException","explanation","getHolodeckExplanation","message","split","HOLODECK_ERROR_CODES","Set","content","errors","Array","isArray","filter","error","code","map","detail","firstChar","includes","toUpperCase","console","log","mode","credentials","referrerPolicy","headers","Headers","upgradeAdapter","adapter","upgradeStore","store","adapterFor","installAdapterFor","fn","holodeckAdapterFor","modelName","_allowMissing","call","hasOverriddenFetch","useFetch","originalFetch","_fetchRequest","bind","options","String","generate","isRecording","mockUrl","buildScaffold","scaffold","mockMethod","testMockNum","requestToMock","response","fetch","body","JSON","stringify","ok","status","statusText","getRecordFailureDetail","json"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @summary Test utilities for mocking HTTP requests against the Holodeck mock server, which records responses as\n * fixtures and replays them in later runs.\n * @module\n * @mergeModuleWith <project>\n */\nimport { SHOULD_RECORD } from '@warp-drive/core/build-config/env';\nimport type { Handler, NextFn } from '@warp-drive/core/request';\nimport type { HTTPMethod, RequestContext, RequestInfo, StructuredDataDocument } from '@warp-drive/core/types/request';\nimport type { MinimumAdapterInterface } from '@warp-drive/legacy/compat';\nimport type { Store } from '@warp-drive/legacy/store';\n\nimport type { LazyScaffold, Scaffold, ScaffoldGenerator } from './mock';\n\nconst TEST_IDS = new WeakMap<\n object,\n {\n id: string;\n /**\n * keeps track of the count of calls to record a mock\n */\n mock: {\n /**\n * For each GET, we keep track of the count\n * for a specific URL\n */\n GET: Record<string, number>;\n /**\n * For each PUT, we keep track of the count\n * for a specific URL\n */\n PUT: Record<string, number>;\n /**\n * For each PATCH, we keep track of the count\n * for a specific URL\n */\n PATCH: Record<string, number>;\n /**\n * For each DELETE, we keep track of the count\n * for a specific URL\n */\n DELETE: Record<string, number>;\n /**\n * For each POST, we keep track of the count\n * for a specific URL\n */\n POST: Record<string, number>;\n /**\n * For each OPTIONS, we keep track of the count\n * for a specific URL\n */\n OPTIONS: Record<string, number>;\n /**\n * For each QUERY, we keep track of the count\n * for a specific URL\n */\n QUERY: Record<string, number>;\n /**\n * for each HEAD, we keep track of the count\n * for a specific URL\n */\n HEAD: Record<string, number>;\n /**\n * for each CONNECT, we keep track of the count\n * for a specific URL\n */\n CONNECT: Record<string, number>;\n /**\n * for each TRACE, we keep track of the count\n * for a specific URL\n */\n TRACE: Record<string, number>;\n };\n /**\n * keeps track of the count of calls to make a request\n */\n request: {\n /**\n * For each GET, we keep track of the count\n * for a specific URL\n */\n GET: Record<string, number>;\n /**\n * For each PUT, we keep track of the count\n * for a specific URL\n */\n PUT: Record<string, number>;\n /**\n * For each PATCH, we keep track of the count\n * for a specific URL\n */\n PATCH: Record<string, number>;\n /**\n * For each DELETE, we keep track of the count\n * for a specific URL\n */\n DELETE: Record<string, number>;\n /**\n * For each POST, we keep track of the count\n * for a specific URL\n */\n POST: Record<string, number>;\n /**\n * For each OPTIONS, we keep track of the count\n * for a specific URL\n */\n OPTIONS: Record<string, number>;\n /**\n * For each QUERY, we keep track of the count\n * for a specific URL\n */\n QUERY: Record<string, number>;\n /**\n * for each HEAD, we keep track of the count\n * for a specific URL\n */\n HEAD: Record<string, number>;\n /**\n * for each CONNECT, we keep track of the count\n * for a specific URL\n */\n CONNECT: Record<string, number>;\n /**\n * for each TRACE, we keep track of the count\n * for a specific URL\n */\n TRACE: Record<string, number>;\n };\n }\n>();\n\n/**\n * The shape `setTestId` stores per test context, named so the report below can\n * take one as an argument.\n */\ntype TestEntry = NonNullable<ReturnType<(typeof TEST_IDS)['get']>>;\n\nlet HOST = '/';\n\n/**\n * @summary Sets the host url of the Holodeck mock server that recordings are sent to.\n * @public\n */\n\nexport function setConfig({ host }: { host: string }): void {\n HOST = host.endsWith('/') ? host : `${host}/`;\n}\n\n/**\n * @summary Assigns a test context its Holodeck test id before a test, or clears it after, reporting an error for any\n * mock the test never requested.\n * @public\n */\n\nexport function setTestId(context: object, str: string | null): void {\n if (str && TEST_IDS.has(context)) {\n throw new Error(`MockServerHandler is already configured with a testId.`);\n }\n if (str) {\n TEST_IDS.set(context, {\n id: str,\n mock: {\n GET: {},\n PUT: {},\n PATCH: {},\n DELETE: {},\n POST: {},\n QUERY: {},\n OPTIONS: {},\n HEAD: {},\n CONNECT: {},\n TRACE: {},\n },\n request: {\n GET: {},\n PUT: {},\n PATCH: {},\n DELETE: {},\n POST: {},\n QUERY: {},\n OPTIONS: {},\n HEAD: {},\n CONNECT: {},\n TRACE: {},\n },\n });\n } else {\n const test = TEST_IDS.get(context);\n TEST_IDS.delete(context);\n\n if (test) {\n reportUnrequestedMocks(test);\n }\n }\n}\n\n/**\n * A mock is declared relative to the mock server (`users/1`) while the request\n * carries the absolute url the code under test built\n * (`https://localhost:7358/users/1`). The mock server reconciles the two by\n * keying a fixture on the request's path, so compare them the same way.\n */\nfunction normalizeUrlKey(url: string): string {\n const withoutOrigin = url.replace(/^[a-z][a-z0-9+.-]*:\\/\\/[^/]+/i, '');\n return withoutOrigin.startsWith('/') ? withoutOrigin.slice(1) : withoutOrigin;\n}\n\n/**\n * A mock the test never requested proves nothing: the test passes whether or\n * not the code under test would have made that request. Nothing compared the\n * two counters holodeck already keeps, so report the difference here, from the\n * `afterEach` every suite runs, which fails the test that leaked.\n *\n * The original assertions are not masked by this: a framework reports an\n * `afterEach` throw alongside the results the test body already recorded\n * rather than in place of them.\n */\nfunction reportUnrequestedMocks(test: TestEntry): void {\n const unrequested: string[] = [];\n\n for (const method of Object.keys(test.mock) as HTTPMethod[]) {\n const mocked = test.mock[method];\n const requested = test.request[method] ?? {};\n const requestCounts = new Map<string, number>();\n\n for (const url of Object.keys(requested)) {\n const key = normalizeUrlKey(url);\n requestCounts.set(key, (requestCounts.get(key) ?? 0) + requested[url]);\n }\n\n for (const url of Object.keys(mocked)) {\n const mockCount = mocked[url];\n const requestCount = requestCounts.get(normalizeUrlKey(url)) ?? 0;\n\n if (mockCount > requestCount) {\n unrequested.push(`\\t${method} ${url} (mocked ${mockCount}, requested ${requestCount})`);\n }\n }\n }\n\n if (unrequested.length) {\n throw new Error(\n `Holodeck: this test declared mocks it never requested.\\n\\n${unrequested.join('\\n')}\\n\\n` +\n `A mock that is never requested proves nothing. Remove it, or make the request it describes.`\n );\n }\n}\n\nconst shouldRecord = SHOULD_RECORD ? true : false;\nlet IS_RECORDING: boolean | null = null;\n\n/**\n * @summary Overrides at runtime whether Holodeck mocks are recorded to fixtures, in place of the build-time\n * `SHOULD_RECORD` flag.\n * @public\n */\nexport function setIsRecording(value: boolean): void {\n IS_RECORDING = value === null ? value : Boolean(value);\n}\n\n/**\n * @summary Reports whether Holodeck mocks are recorded to fixtures, using the `setIsRecording` override when set or the\n * build-time `SHOULD_RECORD` flag otherwise.\n * @public\n */\nexport function getIsRecording(): boolean {\n return IS_RECORDING === null ? shouldRecord : IS_RECORDING;\n}\n\n/**\n * A request handler that intercepts requests and routes them through\n * the Holodeck mock server.\n *\n * This handler modifies the request URL to include test identifiers\n * and manages request counts for accurate mocking.\n *\n * Requires that the test context be configured with a testId using `setTestId`.\n *\n * @summary RequestManager handler that routes requests to the Holodeck mock server by tagging each url with the test id\n * and request count.\n * @param owner - the test context object used to retrieve the test ID.\n */\nexport class MockServerHandler implements Handler {\n declare owner: object;\n constructor(owner: object) {\n this.owner = owner;\n }\n async request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>> {\n const { request, queryForTest } = setupHolodeckFetch(this.owner, Object.assign({}, context.request));\n\n try {\n const future = next(request);\n context.setStream(future.getStream());\n return await future;\n } catch (e) {\n if (e instanceof Error && !(e instanceof DOMException)) {\n const explanation = getHolodeckExplanation(e);\n if (explanation) {\n e.message = `${e.message}\\n\\n${explanation}`;\n }\n e.message = e.message.split(queryForTest).join('');\n }\n throw e;\n }\n }\n}\n\nconst HOLODECK_ERROR_CODES = new Set([\n 'MOCK_NOT_FOUND',\n 'MISSING_X_TEST_ID_HEADER',\n 'MISSING_X_TEST_REQUEST_NUMBER_HEADER',\n]);\n\n/**\n * The mock server explains itself in the response body, which the thrown\n * error only carries as data. Lift that explanation into the message so it\n * reaches a terminal and a CI log.\n */\nfunction getHolodeckExplanation(e: Error): string | null {\n const { content } = e as Error & { content?: unknown };\n if (!content || typeof content !== 'object') {\n return null;\n }\n const { errors } = content as { errors?: unknown };\n if (!Array.isArray(errors)) {\n return null;\n }\n\n return (\n errors\n .filter((error): error is { code: string; detail?: string } => {\n return (\n !!error && typeof error === 'object' && HOLODECK_ERROR_CODES.has((error as { code?: unknown }).code as string)\n );\n })\n .map((error) => error.detail)\n .filter((detail): detail is string => typeof detail === 'string')\n .join('\\n\\n') || null\n );\n}\n\nfunction setupHolodeckFetch(owner: object, request: RequestInfo): { request: RequestInfo; queryForTest: string } {\n const test = TEST_IDS.get(owner);\n if (!test) {\n throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);\n }\n\n const url = request.url!;\n const firstChar = url.includes('?') ? '&' : '?';\n const method = (request.method?.toUpperCase() ?? 'GET') as HTTPMethod;\n\n // enable custom methods\n if (!test.request[method]) {\n // oxlint-disable-next-line no-console\n console.log(`⚠️ Using custom HTTP method ${method} for response to request ${url}`);\n\n test.request[method] = {};\n }\n if (!(url in test.request[method])) {\n test.request[method][url] = 0;\n }\n\n const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${test.request[method][url]++}`;\n request.url = url + queryForTest;\n request.method = method;\n\n request.mode = 'cors';\n request.credentials = 'omit';\n request.referrerPolicy = '';\n\n // since holodeck currently runs on a separate port\n // and we don't want to trigger cors pre-flight\n // we convert PUT to POST to keep the request in the\n // \"simple\" cors category.\n // if (request.method === 'PUT') {\n // request.method = 'POST';\n // }\n\n const headers = new Headers(request.headers);\n if (headers.has('Content-Type')) {\n // under the rules of simple-cors, content-type can only be\n // one of three things, none of which are what folks typically\n // set this to. Since holodeck always expects body to be JSON\n // this \"just works\".\n headers.set('Content-Type', 'text/plain');\n request.headers = headers;\n }\n\n return { request, queryForTest };\n}\n\ninterface HasAdapterForFn {\n adapterFor(this: Store, modelName: string): MinimumAdapterInterface;\n adapterFor(this: Store, modelName: string, _allowMissing?: true): MinimumAdapterInterface | undefined;\n}\n\n/*\n _fetchRequest(options: FetchRequestInit): Promise<Response> {\n const fetchFunction = fetch();\n\n return fetchFunction(options.url, options);\n }\n*/\ninterface PrivateAdapter {\n _fetchRequest(options: RequestInfo): Promise<Response>;\n hasOverriddenFetch: boolean;\n useFetch: boolean;\n}\n\nfunction upgradeAdapter(adapter: unknown): asserts adapter is PrivateAdapter {}\nfunction upgradeStore(store: Store): asserts store is Store & { adapterFor: HasAdapterForFn['adapterFor'] } {\n if (typeof store.adapterFor !== 'function') {\n throw new Error('Store is not compatible with Holodeck. Missing adapterFor method.');\n }\n}\n\n/**\n * Creates an adapterFor function that wraps the provided adapterFor function\n * to override the adapter's _fetchRequest method to route requests through\n * the Holodeck mock server.\n *\n * @summary Patches a legacy store so its adapters send `_fetchRequest` calls through the Holodeck mock server for the\n * given test context.\n * @param owner - The test context object used to retrieve the test ID.\n */\nexport function installAdapterFor(owner: object, store: Store): void {\n upgradeStore(store);\n const fn = store.adapterFor;\n function holodeckAdapterFor(\n this: Store,\n modelName: string,\n _allowMissing?: true\n ): MinimumAdapterInterface | undefined {\n const adapter = fn.call(this, modelName, _allowMissing);\n\n if (adapter) {\n upgradeAdapter(adapter);\n\n if (!adapter.hasOverriddenFetch) {\n adapter.hasOverriddenFetch = true;\n adapter.useFetch = true;\n const originalFetch = adapter._fetchRequest?.bind(adapter);\n\n adapter._fetchRequest = function (options: RequestInfo) {\n if (!originalFetch) {\n throw new Error(`Adapter ${String(modelName)} does not implement _fetchRequest`);\n }\n const { request } = setupHolodeckFetch(owner, options);\n\n return originalFetch(request);\n };\n }\n }\n\n return adapter;\n }\n store.adapterFor = holodeckAdapterFor as HasAdapterForFn['adapterFor'];\n}\n\n/**\n * Mock a request by sending the scaffold to the mock server.\n *\n * @summary Registers a mock response with the Holodeck server for the current test, recording it as a fixture when\n * recording and only counting it when replaying.\n * @public\n */\nexport async function mock(\n owner: object,\n generate: ScaffoldGenerator | LazyScaffold,\n isRecording?: boolean\n): Promise<void> {\n const test = TEST_IDS.get(owner);\n if (!test) {\n throw new Error(`Cannot call \"mock\" before configuring a testId. Use setTestId to set the testId for each test`);\n }\n\n // A LazyScaffold carries its identity, so its body is built only when\n // recording. A bare generator is the only source of its own identity, so it\n // has to run either way; the helpers in ./mock never pass one.\n let method: string;\n let mockUrl: string;\n let buildScaffold: () => Scaffold;\n if (typeof generate === 'function') {\n const scaffold = generate();\n ({ method, url: mockUrl } = scaffold);\n buildScaffold = () => scaffold;\n } else {\n ({ method, url: mockUrl } = generate);\n buildScaffold = generate.scaffold;\n }\n if (!mockUrl || !method) {\n throw new Error(`MockError: Cannot mock a request without providing a URL and Method`);\n }\n const mockMethod = (method.toUpperCase() ?? 'GET') as HTTPMethod;\n\n // enable custom methods\n if (!test.mock[mockMethod]) {\n // oxlint-disable-next-line no-console\n console.log(`⚠️ Using custom HTTP method ${mockMethod} for response to request ${mockUrl}`);\n test.mock[mockMethod] = {};\n }\n if (!(mockUrl in test.mock[mockMethod])) {\n test.mock[mockMethod][mockUrl] = 0;\n }\n // counted whether or not we are recording, so that a mock the test never\n // requests is reported in replay runs too, not only while recording.\n const testMockNum = test.mock[mockMethod][mockUrl]++;\n\n // `isRecording` is the per-request RECORD override, which records even when\n // the suite as a whole is replaying.\n if (!getIsRecording() && !isRecording) {\n return;\n }\n\n const requestToMock = buildScaffold();\n const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;\n const response = await fetch(url, {\n method: 'POST',\n body: JSON.stringify(requestToMock),\n mode: 'cors',\n credentials: 'omit',\n referrerPolicy: '',\n });\n\n if (!response.ok) {\n throw new Error(\n `MockError: Holodeck failed to record ${mockMethod} ${mockUrl} (${response.status} ${response.statusText}). ${await getRecordFailureDetail(response)}`\n );\n }\n}\n\n/**\n * A failed recording is otherwise invisible until the next replay run fails\n * with a missing fixture, so report what the server said at the point of\n * failure.\n */\nasync function getRecordFailureDetail(response: Response): Promise<string> {\n try {\n const body = (await response.json()) as { errors?: { detail?: string }[] };\n const detail = body.errors?.[0]?.detail;\n return detail ?? 'The mock server gave no explanation.';\n } catch {\n return 'The mock server gave no explanation.';\n }\n}\n"],"mappings":"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"}
|
package/dist/mock.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
//#region src/mock.d.ts
|
|
2
2
|
/**
|
|
3
|
+
* @summary Full description of a mocked request, its request body to match, and its response status, headers, and body,
|
|
4
|
+
* as sent to the Holodeck server to record a fixture.
|
|
3
5
|
* @public
|
|
4
6
|
*/
|
|
5
7
|
export interface Scaffold {
|
|
@@ -12,6 +14,7 @@ export interface Scaffold {
|
|
|
12
14
|
response: Record<string, unknown>;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
17
|
+
* @summary Function that builds a complete mock scaffold, accepted by `mock` in place of a `LazyScaffold`.
|
|
15
18
|
* @public
|
|
16
19
|
*/
|
|
17
20
|
export type ScaffoldGenerator = () => Scaffold;
|
|
@@ -21,6 +24,8 @@ export type ScaffoldGenerator = () => Scaffold;
|
|
|
21
24
|
* helpers pass, so that in replay mode a test's response generators never
|
|
22
25
|
* run.
|
|
23
26
|
*
|
|
27
|
+
* @summary Mock whose method and url are known up front and whose full scaffold is built only when recording, as passed
|
|
28
|
+
* by the Holodeck mock helpers to `mock`.
|
|
24
29
|
* @public
|
|
25
30
|
*/
|
|
26
31
|
export interface LazyScaffold {
|
|
@@ -29,6 +34,8 @@ export interface LazyScaffold {
|
|
|
29
34
|
scaffold: () => Scaffold;
|
|
30
35
|
}
|
|
31
36
|
/**
|
|
37
|
+
* @summary Function passed to the Holodeck mock helpers that builds the response body, called only when recording a
|
|
38
|
+
* fixture.
|
|
32
39
|
* @public
|
|
33
40
|
*/
|
|
34
41
|
export type ResponseGenerator = () => Record<string, unknown>;
|
|
@@ -46,6 +53,8 @@ export type ResponseGenerator = () => Record<string, unknown>;
|
|
|
46
53
|
* A local override for re-recording one request. Do not commit it; a committed RECORD means
|
|
47
54
|
* that request is never replayed against its fixture.
|
|
48
55
|
*
|
|
56
|
+
* @summary Mocks a GET request to a url on the Holodeck server, returning a generated response with a default 200
|
|
57
|
+
* status.
|
|
49
58
|
* @param url the url to mock, relative to the mock server host (e.g. `users/1`)
|
|
50
59
|
* @param response a function which generates the response to return
|
|
51
60
|
* @param options status, headers for the response, body to match against for the request, and whether to force recording
|
|
@@ -56,12 +65,18 @@ export declare function GET(owner: object, url: string, response: ResponseGenera
|
|
|
56
65
|
}): Promise<void>;
|
|
57
66
|
/**
|
|
58
67
|
* Mock a POST request
|
|
68
|
+
*
|
|
69
|
+
* @summary Mocks a POST request to a url on the Holodeck server, returning a generated response with a 201 status, or
|
|
70
|
+
* 204 when it has no body.
|
|
59
71
|
*/
|
|
60
72
|
export declare function POST(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
61
73
|
RECORD?: boolean;
|
|
62
74
|
}): Promise<void>;
|
|
63
75
|
/**
|
|
64
76
|
* mock a PUT request
|
|
77
|
+
*
|
|
78
|
+
* @summary Mocks a PUT request to a url on the Holodeck server, returning a generated response with a 200 status, or
|
|
79
|
+
* 204 when it has no body.
|
|
65
80
|
*/
|
|
66
81
|
export declare function PUT(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
67
82
|
RECORD?: boolean;
|
|
@@ -69,12 +84,17 @@ export declare function PUT(owner: object, url: string, response: ResponseGenera
|
|
|
69
84
|
/**
|
|
70
85
|
* mock a PATCH request
|
|
71
86
|
*
|
|
87
|
+
* @summary Mocks a PATCH request to a url on the Holodeck server, returning a generated response with a 200 status, or
|
|
88
|
+
* 204 when it has no body.
|
|
72
89
|
*/
|
|
73
90
|
export declare function PATCH(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
74
91
|
RECORD?: boolean;
|
|
75
92
|
}): Promise<void>;
|
|
76
93
|
/**
|
|
77
94
|
* mock a DELETE request
|
|
95
|
+
*
|
|
96
|
+
* @summary Mocks a DELETE request to a url on the Holodeck server, returning a generated response with a 200 status, or
|
|
97
|
+
* 204 when it has no body.
|
|
78
98
|
*/
|
|
79
99
|
export declare function DELETE(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
|
|
80
100
|
RECORD?: boolean;
|
|
@@ -93,6 +113,8 @@ export declare function DELETE(owner: object, url: string, response: ResponseGen
|
|
|
93
113
|
* A local override for re-recording one request. Do not commit it; a committed RECORD means
|
|
94
114
|
* that request is never replayed against its fixture.
|
|
95
115
|
*
|
|
116
|
+
* @summary Mocks a HEAD request to a url on the Holodeck server, returning a generated response with a default 200
|
|
117
|
+
* status.
|
|
96
118
|
* @param url the url to mock, relative to the mock server host (e.g. `users/1`)
|
|
97
119
|
* @param response a function which generates the response to return
|
|
98
120
|
* @param options status, headers for the response, body to match against for the request, and whether to force recording
|
package/dist/mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock.d.ts","names":[],"sources":["../src/mock.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"mock.d.ts","names":[],"sources":["../src/mock.ts"],"mappings":";;;;;;iBAaiB;EACf;EACA;EACA,SAAS;EACT,MAAM;EACN;EACA;EACA,UAAU;;;;;;YAOA,0BAA0B;;;;;;;;;;;iBAYrB;EACf;EACA;EACA,gBAAgB;;;;;;;YAQN,0BAA0B;;;;;;;;;;;;;;;;;;;;;;wBAuBtB,IACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;;;wBA0Fa,KACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;;;wBA+Ba,IACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;;;wBA8Ba,MACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;;;wBA8Ba,OACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;;;;;;;;;;;;;;;;;;wBA8Ca,KACd,eACA,aACA,UAAU,mBAIV,UAAU,QAAQ,KAAK;EAA8C;IACpE"}
|
package/dist/mock.js
CHANGED
|
@@ -2,6 +2,11 @@ import { mock } from "./index.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/mock.ts
|
|
4
4
|
/**
|
|
5
|
+
* @module
|
|
6
|
+
* @summary Helpers `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` that register a mocked request and its response
|
|
7
|
+
* with the Holodeck test server, plus the scaffold types they accept.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
5
10
|
* Sets up Mocking for a GET request on the mock server
|
|
6
11
|
* for the supplied url.
|
|
7
12
|
*
|
|
@@ -15,6 +20,8 @@ import { mock } from "./index.js";
|
|
|
15
20
|
* A local override for re-recording one request. Do not commit it; a committed RECORD means
|
|
16
21
|
* that request is never replayed against its fixture.
|
|
17
22
|
*
|
|
23
|
+
* @summary Mocks a GET request to a url on the Holodeck server, returning a generated response with a default 200
|
|
24
|
+
* status.
|
|
18
25
|
* @param url the url to mock, relative to the mock server host (e.g. `users/1`)
|
|
19
26
|
* @param response a function which generates the response to return
|
|
20
27
|
* @param options status, headers for the response, body to match against for the request, and whether to force recording
|
|
@@ -100,6 +107,9 @@ const STATUS_TEXT_FOR = /* @__PURE__ */ new Map([
|
|
|
100
107
|
]);
|
|
101
108
|
/**
|
|
102
109
|
* Mock a POST request
|
|
110
|
+
*
|
|
111
|
+
* @summary Mocks a POST request to a url on the Holodeck server, returning a generated response with a 201 status, or
|
|
112
|
+
* 204 when it has no body.
|
|
103
113
|
*/
|
|
104
114
|
function POST(owner, url, response, options) {
|
|
105
115
|
return mock(owner, {
|
|
@@ -122,6 +132,9 @@ function POST(owner, url, response, options) {
|
|
|
122
132
|
}
|
|
123
133
|
/**
|
|
124
134
|
* mock a PUT request
|
|
135
|
+
*
|
|
136
|
+
* @summary Mocks a PUT request to a url on the Holodeck server, returning a generated response with a 200 status, or
|
|
137
|
+
* 204 when it has no body.
|
|
125
138
|
*/
|
|
126
139
|
function PUT(owner, url, response, options) {
|
|
127
140
|
return mock(owner, {
|
|
@@ -145,6 +158,8 @@ function PUT(owner, url, response, options) {
|
|
|
145
158
|
/**
|
|
146
159
|
* mock a PATCH request
|
|
147
160
|
*
|
|
161
|
+
* @summary Mocks a PATCH request to a url on the Holodeck server, returning a generated response with a 200 status, or
|
|
162
|
+
* 204 when it has no body.
|
|
148
163
|
*/
|
|
149
164
|
function PATCH(owner, url, response, options) {
|
|
150
165
|
return mock(owner, {
|
|
@@ -167,6 +182,9 @@ function PATCH(owner, url, response, options) {
|
|
|
167
182
|
}
|
|
168
183
|
/**
|
|
169
184
|
* mock a DELETE request
|
|
185
|
+
*
|
|
186
|
+
* @summary Mocks a DELETE request to a url on the Holodeck server, returning a generated response with a 200 status, or
|
|
187
|
+
* 204 when it has no body.
|
|
170
188
|
*/
|
|
171
189
|
function DELETE(owner, url, response, options) {
|
|
172
190
|
return mock(owner, {
|
|
@@ -201,6 +219,8 @@ function DELETE(owner, url, response, options) {
|
|
|
201
219
|
* A local override for re-recording one request. Do not commit it; a committed RECORD means
|
|
202
220
|
* that request is never replayed against its fixture.
|
|
203
221
|
*
|
|
222
|
+
* @summary Mocks a HEAD request to a url on the Holodeck server, returning a generated response with a default 200
|
|
223
|
+
* status.
|
|
204
224
|
* @param url the url to mock, relative to the mock server host (e.g. `users/1`)
|
|
205
225
|
* @param response a function which generates the response to return
|
|
206
226
|
* @param options status, headers for the response, body to match against for the request, and whether to force recording
|
package/dist/mock.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock.js","names":[],"sources":["../src/mock.ts"],"sourcesContent":["import { mock } from '.';\n\n/**\n * @public\n */\nexport interface Scaffold {\n status: number;\n statusText?: string;\n headers: Record<string, string>;\n body: Record<string, string> | string | null;\n method: string;\n url: string;\n response: Record<string, unknown>;\n}\n\n/**\n * @public\n */\nexport type ScaffoldGenerator = () => Scaffold;\n\n/**\n * A mock whose method and url are known up front, with the rest of the\n * scaffold built only when holodeck is recording. This is what the mock\n * helpers pass, so that in replay mode a test's response generators never\n * run.\n *\n * @public\n */\nexport interface LazyScaffold {\n method: string;\n url: string;\n scaffold: () => Scaffold;\n}\n\n/**\n * @public\n */\nexport type ResponseGenerator = () => Record<string, unknown>;\n\n/**\n * Sets up Mocking for a GET request on the mock server\n * for the supplied url.\n *\n * The response body is generated by the supplied response function.\n *\n * Available options:\n * - status: the status code to return (default: 200)\n * - headers: the headers to return (default: {})\n * - body: the body to match against for the request (default: null)\n * - RECORD: record this request even when the suite is replaying, such as under CI (default: false).\n * A local override for re-recording one request. Do not commit it; a committed RECORD means\n * that request is never replayed against its fixture.\n *\n * @param url the url to mock, relative to the mock server host (e.g. `users/1`)\n * @param response a function which generates the response to return\n * @param options status, headers for the response, body to match against for the request, and whether to force recording\n * @return\n */\nexport function GET(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'GET',\n url,\n scaffold: () => ({\n status: options?.status ?? 200,\n statusText: options?.statusText ?? 'OK',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'GET',\n url,\n response: response(),\n }),\n },\n options?.RECORD\n );\n}\n\nconst STATUS_TEXT_FOR = new Map([\n [200, 'OK'],\n [201, 'Created'],\n [202, 'Accepted'],\n [203, 'Non-Authoritative Information'],\n [204, 'No Content'],\n [205, 'Reset Content'],\n [206, 'Partial Content'],\n [207, 'Multi-Status'],\n [208, 'Already Reported'],\n [226, 'IM Used'],\n [300, 'Multiple Choices'],\n [301, 'Moved Permanently'],\n [302, 'Found'],\n [303, 'See Other'],\n [304, 'Not Modified'],\n [307, 'Temporary Redirect'],\n [308, 'Permanent Redirect'],\n [400, 'Bad Request'],\n [401, 'Unauthorized'],\n [402, 'Payment Required'],\n [403, 'Forbidden'],\n [404, 'Not Found'],\n [405, 'Method Not Allowed'],\n [406, 'Not Acceptable'],\n [407, 'Proxy Authentication Required'],\n [408, 'Request Timeout'],\n [409, 'Conflict'],\n [410, 'Gone'],\n [411, 'Length Required'],\n [412, 'Precondition Failed'],\n [413, 'Payload Too Large'],\n [414, 'URI Too Long'],\n [415, 'Unsupported Media Type'],\n [416, 'Range Not Satisfiable'],\n [417, 'Expectation Failed'],\n [419, 'Page Expired'],\n [420, 'Enhance Your Calm'],\n [421, 'Misdirected Request'],\n [422, 'Unprocessable Entity'],\n [423, 'Locked'],\n [424, 'Failed Dependency'],\n [425, 'Too Early'],\n [426, 'Upgrade Required'],\n [428, 'Precondition Required'],\n [429, 'Too Many Requests'],\n [430, 'Request Header Fields Too Large'],\n [431, 'Request Header Fields Too Large'],\n [450, 'Blocked By Windows Parental Controls'],\n [451, 'Unavailable For Legal Reasons'],\n [500, 'Internal Server Error'],\n [501, 'Not Implemented'],\n [502, 'Bad Gateway'],\n [503, 'Service Unavailable'],\n [504, 'Gateway Timeout'],\n [505, 'HTTP Version Not Supported'],\n [506, 'Variant Also Negotiates'],\n [507, 'Insufficient Storage'],\n [508, 'Loop Detected'],\n [509, 'Bandwidth Limit Exceeded'],\n [510, 'Not Extended'],\n [511, 'Network Authentication Required'],\n]);\n\n/**\n * Mock a POST request\n */\nexport function POST(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'POST',\n url,\n scaffold: () => {\n const body = response();\n const status = options?.status ?? (body ? 201 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'POST',\n url,\n response: body,\n };\n },\n },\n options?.RECORD\n );\n}\n\n/**\n * mock a PUT request\n */\nexport function PUT(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'PUT',\n url,\n scaffold: () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'PUT',\n url,\n response: body,\n };\n },\n },\n options?.RECORD\n );\n}\n/**\n * mock a PATCH request\n *\n */\nexport function PATCH(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'PATCH',\n url,\n scaffold: () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'PATCH',\n url,\n response: body,\n };\n },\n },\n options?.RECORD\n );\n}\n/**\n * mock a DELETE request\n */\nexport function DELETE(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'DELETE',\n url,\n scaffold: () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'DELETE',\n url,\n response: body,\n };\n },\n },\n options?.RECORD\n );\n}\n\n/**\n * Sets up Mocking for a HEAD request on the mock server\n * for the supplied url.\n *\n * The response body is generated by the supplied response function.\n *\n * Available options:\n * - status: the status code to return (default: 200)\n * - headers: the headers to return (default: {})\n * - body: the body to match against for the request (default: null)\n * - RECORD: record this request even when the suite is replaying, such as under CI (default: false).\n * A local override for re-recording one request. Do not commit it; a committed RECORD means\n * that request is never replayed against its fixture.\n *\n * @param url the url to mock, relative to the mock server host (e.g. `users/1`)\n * @param response a function which generates the response to return\n * @param options status, headers for the response, body to match against for the request, and whether to force recording\n * @return\n */\nexport function HEAD(\n owner: object,\n url: string,\n response: ResponseGenerator,\n // TODO: should we omit the `body` as well?\n // It does _seem_ possible for HEAD requests to have a body, but it should be ignored.\n // From MDN: Warning: If a response to a HEAD request has a body, the response body must be ignored.\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'HEAD',\n url,\n scaffold: () => ({\n status: options?.status ?? 200,\n statusText: options?.statusText ?? 'OK',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'HEAD',\n url,\n response: response(),\n }),\n },\n options?.RECORD\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mock.js","names":[],"sources":["../src/mock.ts"],"sourcesContent":["/**\n * @module\n * @summary Helpers `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` that register a mocked request and its response\n * with the Holodeck test server, plus the scaffold types they accept.\n */\n\nimport { mock } from '.';\n\n/**\n * @summary Full description of a mocked request, its request body to match, and its response status, headers, and body,\n * as sent to the Holodeck server to record a fixture.\n * @public\n */\nexport interface Scaffold {\n status: number;\n statusText?: string;\n headers: Record<string, string>;\n body: Record<string, string> | string | null;\n method: string;\n url: string;\n response: Record<string, unknown>;\n}\n\n/**\n * @summary Function that builds a complete mock scaffold, accepted by `mock` in place of a `LazyScaffold`.\n * @public\n */\nexport type ScaffoldGenerator = () => Scaffold;\n\n/**\n * A mock whose method and url are known up front, with the rest of the\n * scaffold built only when holodeck is recording. This is what the mock\n * helpers pass, so that in replay mode a test's response generators never\n * run.\n *\n * @summary Mock whose method and url are known up front and whose full scaffold is built only when recording, as passed\n * by the Holodeck mock helpers to `mock`.\n * @public\n */\nexport interface LazyScaffold {\n method: string;\n url: string;\n scaffold: () => Scaffold;\n}\n\n/**\n * @summary Function passed to the Holodeck mock helpers that builds the response body, called only when recording a\n * fixture.\n * @public\n */\nexport type ResponseGenerator = () => Record<string, unknown>;\n\n/**\n * Sets up Mocking for a GET request on the mock server\n * for the supplied url.\n *\n * The response body is generated by the supplied response function.\n *\n * Available options:\n * - status: the status code to return (default: 200)\n * - headers: the headers to return (default: {})\n * - body: the body to match against for the request (default: null)\n * - RECORD: record this request even when the suite is replaying, such as under CI (default: false).\n * A local override for re-recording one request. Do not commit it; a committed RECORD means\n * that request is never replayed against its fixture.\n *\n * @summary Mocks a GET request to a url on the Holodeck server, returning a generated response with a default 200\n * status.\n * @param url the url to mock, relative to the mock server host (e.g. `users/1`)\n * @param response a function which generates the response to return\n * @param options status, headers for the response, body to match against for the request, and whether to force recording\n * @return\n */\nexport function GET(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'GET',\n url,\n scaffold: () => ({\n status: options?.status ?? 200,\n statusText: options?.statusText ?? 'OK',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'GET',\n url,\n response: response(),\n }),\n },\n options?.RECORD\n );\n}\n\nconst STATUS_TEXT_FOR = new Map([\n [200, 'OK'],\n [201, 'Created'],\n [202, 'Accepted'],\n [203, 'Non-Authoritative Information'],\n [204, 'No Content'],\n [205, 'Reset Content'],\n [206, 'Partial Content'],\n [207, 'Multi-Status'],\n [208, 'Already Reported'],\n [226, 'IM Used'],\n [300, 'Multiple Choices'],\n [301, 'Moved Permanently'],\n [302, 'Found'],\n [303, 'See Other'],\n [304, 'Not Modified'],\n [307, 'Temporary Redirect'],\n [308, 'Permanent Redirect'],\n [400, 'Bad Request'],\n [401, 'Unauthorized'],\n [402, 'Payment Required'],\n [403, 'Forbidden'],\n [404, 'Not Found'],\n [405, 'Method Not Allowed'],\n [406, 'Not Acceptable'],\n [407, 'Proxy Authentication Required'],\n [408, 'Request Timeout'],\n [409, 'Conflict'],\n [410, 'Gone'],\n [411, 'Length Required'],\n [412, 'Precondition Failed'],\n [413, 'Payload Too Large'],\n [414, 'URI Too Long'],\n [415, 'Unsupported Media Type'],\n [416, 'Range Not Satisfiable'],\n [417, 'Expectation Failed'],\n [419, 'Page Expired'],\n [420, 'Enhance Your Calm'],\n [421, 'Misdirected Request'],\n [422, 'Unprocessable Entity'],\n [423, 'Locked'],\n [424, 'Failed Dependency'],\n [425, 'Too Early'],\n [426, 'Upgrade Required'],\n [428, 'Precondition Required'],\n [429, 'Too Many Requests'],\n [430, 'Request Header Fields Too Large'],\n [431, 'Request Header Fields Too Large'],\n [450, 'Blocked By Windows Parental Controls'],\n [451, 'Unavailable For Legal Reasons'],\n [500, 'Internal Server Error'],\n [501, 'Not Implemented'],\n [502, 'Bad Gateway'],\n [503, 'Service Unavailable'],\n [504, 'Gateway Timeout'],\n [505, 'HTTP Version Not Supported'],\n [506, 'Variant Also Negotiates'],\n [507, 'Insufficient Storage'],\n [508, 'Loop Detected'],\n [509, 'Bandwidth Limit Exceeded'],\n [510, 'Not Extended'],\n [511, 'Network Authentication Required'],\n]);\n\n/**\n * Mock a POST request\n *\n * @summary Mocks a POST request to a url on the Holodeck server, returning a generated response with a 201 status, or\n * 204 when it has no body.\n */\nexport function POST(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'POST',\n url,\n scaffold: () => {\n const body = response();\n const status = options?.status ?? (body ? 201 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'POST',\n url,\n response: body,\n };\n },\n },\n options?.RECORD\n );\n}\n\n/**\n * mock a PUT request\n *\n * @summary Mocks a PUT request to a url on the Holodeck server, returning a generated response with a 200 status, or\n * 204 when it has no body.\n */\nexport function PUT(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'PUT',\n url,\n scaffold: () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'PUT',\n url,\n response: body,\n };\n },\n },\n options?.RECORD\n );\n}\n/**\n * mock a PATCH request\n *\n * @summary Mocks a PATCH request to a url on the Holodeck server, returning a generated response with a 200 status, or\n * 204 when it has no body.\n */\nexport function PATCH(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'PATCH',\n url,\n scaffold: () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'PATCH',\n url,\n response: body,\n };\n },\n },\n options?.RECORD\n );\n}\n/**\n * mock a DELETE request\n *\n * @summary Mocks a DELETE request to a url on the Holodeck server, returning a generated response with a 200 status, or\n * 204 when it has no body.\n */\nexport function DELETE(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'DELETE',\n url,\n scaffold: () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'DELETE',\n url,\n response: body,\n };\n },\n },\n options?.RECORD\n );\n}\n\n/**\n * Sets up Mocking for a HEAD request on the mock server\n * for the supplied url.\n *\n * The response body is generated by the supplied response function.\n *\n * Available options:\n * - status: the status code to return (default: 200)\n * - headers: the headers to return (default: {})\n * - body: the body to match against for the request (default: null)\n * - RECORD: record this request even when the suite is replaying, such as under CI (default: false).\n * A local override for re-recording one request. Do not commit it; a committed RECORD means\n * that request is never replayed against its fixture.\n *\n * @summary Mocks a HEAD request to a url on the Holodeck server, returning a generated response with a default 200\n * status.\n * @param url the url to mock, relative to the mock server host (e.g. `users/1`)\n * @param response a function which generates the response to return\n * @param options status, headers for the response, body to match against for the request, and whether to force recording\n * @return\n */\nexport function HEAD(\n owner: object,\n url: string,\n response: ResponseGenerator,\n // TODO: should we omit the `body` as well?\n // It does _seem_ possible for HEAD requests to have a body, but it should be ignored.\n // From MDN: Warning: If a response to a HEAD request has a body, the response body must be ignored.\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n {\n method: 'HEAD',\n url,\n scaffold: () => ({\n status: options?.status ?? 200,\n statusText: options?.statusText ?? 'OK',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'HEAD',\n url,\n response: response(),\n }),\n },\n options?.RECORD\n );\n}\n"],"mappings":"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"}
|
package/logos/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warp-drive/holodeck",
|
|
3
|
-
"description": "⚡️ Simple, Fast HTTP Mocking for Tests",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"description": "⚡️ Simple, Fast HTTP Mocking for Tests: experimental mocking that makes real network requests and replays git-managed recordings, via `MockServerHandler` and the `GET`/`POST` helpers in `@warp-drive/holodeck/mock`.",
|
|
4
|
+
"version": "0.1.0-alpha.90",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chris Thoburn <runspired@users.noreply.github.com>",
|
|
7
7
|
"repository": {
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"http-mock"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"hono": "^4.13.
|
|
22
|
-
"@hono/node-server": "^2.1.
|
|
21
|
+
"hono": "^4.13.11",
|
|
22
|
+
"@hono/node-server": "^2.1.3"
|
|
23
23
|
},
|
|
24
24
|
"type": "module",
|
|
25
25
|
"files": [
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"ensure-cert": "./server/ensure-cert.js"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@warp-drive/utilities": "5.10.0-alpha.
|
|
37
|
-
"@warp-drive/legacy": "5.10.0-alpha.
|
|
38
|
-
"@warp-drive/core": "5.10.0-alpha.
|
|
36
|
+
"@warp-drive/utilities": "5.10.0-alpha.14",
|
|
37
|
+
"@warp-drive/legacy": "5.10.0-alpha.14",
|
|
38
|
+
"@warp-drive/core": "5.10.0-alpha.14"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@warp-drive/utilities": {
|
|
@@ -51,13 +51,13 @@
|
|
|
51
51
|
"@babel/preset-env": "^7.29.7",
|
|
52
52
|
"@babel/preset-typescript": "^7.29.7",
|
|
53
53
|
"@babel/runtime": "^7.29.7",
|
|
54
|
-
"@warp-drive/utilities": "5.10.0-alpha.
|
|
55
|
-
"@warp-drive/legacy": "5.10.0-alpha.
|
|
56
|
-
"@warp-drive/core": "5.10.0-alpha.
|
|
57
|
-
"@warp-drive/internal-config": "5.10.0-alpha.
|
|
58
|
-
"@types/node": "^26.6.
|
|
54
|
+
"@warp-drive/utilities": "5.10.0-alpha.14",
|
|
55
|
+
"@warp-drive/legacy": "5.10.0-alpha.14",
|
|
56
|
+
"@warp-drive/core": "5.10.0-alpha.14",
|
|
57
|
+
"@warp-drive/internal-config": "5.10.0-alpha.14",
|
|
58
|
+
"@types/node": "^26.6.3",
|
|
59
59
|
"tsdown": "^0.23.0",
|
|
60
|
-
"typescript-7": "npm:typescript@7.1.0-dev.
|
|
60
|
+
"typescript-7": "npm:typescript@7.1.0-dev.20260929.1"
|
|
61
61
|
},
|
|
62
62
|
"exports": {
|
|
63
63
|
".": {
|