@warp-drive/holodeck 0.1.0-alpha.86 → 0.1.0-alpha.89

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 CHANGED
@@ -8,13 +8,11 @@
8
8
  />
9
9
  </p>
10
10
 
11
- ![NPM Stable Version](https://img.shields.io/npm/v/ember-data/latest?label=version&style=flat&color=fdb155)
12
- ![NPM Downloads](https://img.shields.io/npm/dm/ember-data.svg?style=flat&color=fdb155)
11
+ ![NPM Stable Version](https://img.shields.io/npm/v/%40warp-drive%2Fholodeck/latest?label=version&style=flat&color=fdb155)
12
+ ![NPM Downloads](https://img.shields.io/npm/dm/%40warp-drive%2Fholodeck.svg?style=flat&color=fdb155)
13
13
  ![License](https://img.shields.io/github/license/warp-drive-data/warp-drive.svg?style=flat&color=fdb155)
14
- [![EmberJS Discord Community Server](https://img.shields.io/badge/EmberJS-grey?logo=discord&logoColor=fdb155)](https://discord.gg/zT3asNS
15
- )
16
- [![WarpDrive Discord Server](https://img.shields.io/badge/WarpDrive-grey?logo=discord&logoColor=fdb155)](https://discord.gg/PHBbnWJx5S
17
- )
14
+ [![EmberJS Discord Community Server](https://img.shields.io/badge/EmberJS-grey?logo=discord&logoColor=fdb155)](https://discord.gg/zT3asNS)
15
+ [![WarpDrive Discord Server](https://img.shields.io/badge/WarpDrive-grey?logo=discord&logoColor=fdb155)](https://discord.gg/PHBbnWJx5S)
18
16
 
19
17
  <p align="center">
20
18
  <br>
@@ -51,13 +49,14 @@
51
49
  - 🔥 Blazing Fast Tests
52
50
  - record your tests when you change them
53
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
54
54
  - zero-work: setup work is skipped when in replay mode
55
55
 
56
56
  ## Installation
57
57
 
58
-
59
- ```json
60
- pnpm install @warp-drive/holodeck
58
+ ```sh
59
+ pnpm add -E @warp-drive/holodeck@canary
61
60
  ```
62
61
 
63
62
  **Tagged Releases**
@@ -70,190 +69,18 @@ pnpm install @warp-drive/holodeck
70
69
 
71
70
 
72
71
 
73
- ## Usage
74
- #### Mocking from Within a Test
75
-
76
- ```ts
77
- import { GET } from '@warp-drive/holodeck/mock';
78
-
79
- await GET(context, 'users/1', () => ({
80
- data: {
81
- id: '1',
82
- type: 'user',
83
- attributes: {
84
- name: 'Chris Thoburn',
85
- },
86
- },
87
-
88
- // set RECORD to false or remove
89
- // the options hash entirely once the request
90
- // has been recorded
91
- }), { RECORD: true });
92
- ```
93
-
94
- ## Motivations
95
-
96
- Comprehensive DX around data management should extend to testing.
97
-
98
- ### ✨ Amazing Developer Experience
99
-
100
- 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.
101
-
102
- 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.
103
-
104
- ### 🔥 Blazing Fast Tests
105
-
106
- 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
107
- an approach built over `http/3` (`http/2` for now) utilizing aggressive caching
108
- and `brotli` minification in a way that can be replayed over and over again.
109
-
110
- Basically, pay the cost when you write the test. Forever after skip the cost until you need to edit the test again.
111
-
112
- ## Setup
113
-
114
- ### Use with WarpDrive
115
-
116
- 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).
117
-
118
- For instance:
119
-
120
- ```ts
121
- import RequestManager from '@ember-data/request';
122
- import Fetch from '@ember-data/request/fetch';
123
- import { MockServerHandler } from '@warp-drive/holodeck';
124
-
125
- const manager = new RequestManager()
126
- .use([new MockServerHandler(testContext), Fetch]);
127
- ```
128
-
129
- From within a test this might look like:
130
-
131
- ```ts
132
- import RequestManager from '@ember-data/request';
133
- import Fetch from '@ember-data/request/fetch';
134
- import { MockServerHandler } from '@warp-drive/holodeck';
135
- import { module, test } from 'qunit';
136
-
137
- module('my module', function() {
138
- test('my test', async function() {
139
- const manager = new RequestManager()
140
- .use([new MockServerHandler(this), Fetch]);
141
- });
142
- });
143
- ```
144
-
145
- Next, you will need to configure holodeck to understand your tests contexts. For qunit and diagnostic
146
- in a project using Ember this is typically done in `tests/test-helper.js`
147
-
148
- #### With Diagnostic
149
-
150
- ```ts
151
- import { setupGlobalHooks } from '@warp-drive/diagnostic';
152
- import { setConfig, setTestId } from '@warp-drive/holodeck';
153
-
154
- // if not proxying the port / set port to the correct value here
155
- const MockHost = `https://${window.location.hostname}:${Number(window.location.port) + 1}`;
156
-
157
- setConfig({ host: MockHost });
158
-
159
- setupGlobalHooks((hooks) => {
160
- hooks.beforeEach(function (assert) {
161
- setTestId(this, assert.test.testId);
162
- });
163
- hooks.afterEach(function () {
164
- setTestId(this, null);
165
- });
166
- });
167
- ```
168
-
169
- #### With QUnit
170
-
171
- ```ts
172
- import * as QUnit from 'qunit';
173
- import { setConfig, setTestId } from '@warp-drive/holodeck';
174
-
175
- // if not proxying the port / set port to the correct value here
176
- const MockHost = `https://${window.location.hostname}:${Number(window.location.port) + 1}`;
177
-
178
- setConfig({ host: MockHost });
179
-
180
- QUnit.hooks.beforeEach(function (assert) {
181
- setTestId(this, assert.test.testId);
182
- });
183
- QUnit.hooks.afterEach(function (assert) {
184
- setTestId(this, null);
185
- });
186
- ```
187
-
188
- ### Testem
189
-
190
- 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):
191
-
192
- ```ts
193
- module.exports = async function () {
194
- const holodeck = (await import('@warp-drive/holodeck')).default;
195
- await holodeck.launchProgram({
196
- port: 7373,
197
- });
198
-
199
- process.on('beforeExit', async () => {
200
- await holodeck.endProgram();
201
- });
202
-
203
- return {
204
- // ... testem config
205
- };
206
- };
207
- ```
72
+ ## Documentation
208
73
 
209
- If you need the API mock to run on the same port as the test suite, you can use Testem's [API Proxy](https://github.com/testem/testem/tree/master?tab=readme-ov-file#api-proxy)
74
+ API docs for this package → [@warp-drive/holodeck](https://warp-drive.io/api/@warp-drive/holodeck/)
210
75
 
211
- ```ts
212
- module.exports = async function () {
213
- const holodeck = (await import('@warp-drive/holodeck')).default;
214
- await holodeck.launchProgram({
215
- port: 7373,
216
- });
217
-
218
- process.on('beforeExit', async () => {
219
- await holodeck.endProgram();
220
- });
221
-
222
- return {
223
- "proxies": {
224
- "/api": {
225
- // holodeck always runs on https
226
- // the proxy is transparent so this means /api/v1 will route to https://localhost:7373/api/v1
227
- "target": "https://localhost:7373",
228
- // "onlyContentTypes": ["xml", "json"],
229
- // if test suite is on http, set this to false
230
- // "secure": false,
231
- },
232
- }
233
- };
234
- };
235
- ```
236
-
237
- ### Diagnostic
238
-
239
- holodeck can be launched and cleaned up using the lifecycle hooks in the launch config
240
- for diagnostic in `diagnostic.js`:
241
-
242
- ```ts
243
- import { launch } from '@warp-drive/diagnostic/server';
244
- import holodeck from '@warp-drive/holodeck';
245
-
246
- await launch({
247
- async setup(options) {
248
- await holodeck.launchProgram({
249
- port: options.port + 1,
250
- });
251
- },
252
- async cleanup() {
253
- await holodeck.endProgram();
254
- },
255
- });
256
- ```
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
257
84
 
258
85
  ### ♥️ Credits
259
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>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;wBA6IgB,YAAY;EAAU;;;;;wBAQtB,UAAU,iBAAiB;;;;wBAoG3B,eAAe;;;;wBAOf;;;;;;;;;;;;qBAeH,6BAA6B;EACxC;EACA,YAAY;EAGZ,QAAc,GAAG,SAAS,gBAAgB,MAAM,OAAO,KAAK,QAAQ,uBAAuB;;;;;;;;;wBAwI7E,kBAAkB,eAAe,OAAO;;;;;;wBAuClC,KACpB,eACA,UAAU,oBAAoB,cAC9B,wBACC"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"mock.d.ts","names":[],"sources":["../src/mock.ts"],"mappings":";;;;iBAKiB;EACf;EACA;EACA,SAAS;EACT,MAAM;EACN;EACA;EACA,UAAU;;;;;YAMA,0BAA0B;;;;;;;;;iBAUrB;EACf;EACA;EACA,gBAAgB;;;;;YAMN,0BAA0B;;;;;;;;;;;;;;;;;;;;wBAqBtB,IACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;wBAuFa,KACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;wBA4Ba,IACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;wBA4Ba,MACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;wBA2Ba,OACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;;;;;;;;;;;;;;;;wBA4Ca,KACd,eACA,aACA,UAAU,mBAIV,UAAU,QAAQ,KAAK;EAA8C;IACpE"}
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/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.86",
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.89",
5
5
  "license": "MIT",
6
6
  "author": "Chris Thoburn <runspired@users.noreply.github.com>",
7
7
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "http-mock"
19
19
  ],
20
20
  "dependencies": {
21
- "hono": "^4.13.8",
21
+ "hono": "^4.13.9",
22
22
  "@hono/node-server": "^2.1.1"
23
23
  },
24
24
  "type": "module",
@@ -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.10",
37
- "@warp-drive/legacy": "5.10.0-alpha.10",
38
- "@warp-drive/core": "5.10.0-alpha.10"
36
+ "@warp-drive/utilities": "5.10.0-alpha.13",
37
+ "@warp-drive/legacy": "5.10.0-alpha.13",
38
+ "@warp-drive/core": "5.10.0-alpha.13"
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.10",
55
- "@warp-drive/legacy": "5.10.0-alpha.10",
56
- "@warp-drive/core": "5.10.0-alpha.10",
57
- "@warp-drive/internal-config": "5.10.0-alpha.10",
58
- "@types/node": "^26.6.2",
54
+ "@warp-drive/utilities": "5.10.0-alpha.13",
55
+ "@warp-drive/legacy": "5.10.0-alpha.13",
56
+ "@warp-drive/core": "5.10.0-alpha.13",
57
+ "@warp-drive/internal-config": "5.10.0-alpha.13",
58
+ "@types/node": "^26.6.3",
59
59
  "tsdown": "^0.23.0",
60
- "typescript-7": "npm:typescript@7.1.0-dev.20260920.1"
60
+ "typescript-7": "npm:typescript@7.1.0-dev.20260926.1"
61
61
  },
62
62
  "exports": {
63
63
  ".": {