@warp-drive/holodeck 0.1.0-alpha.63 → 0.1.0-alpha.65

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.
@@ -0,0 +1,56 @@
1
+ import { ScaffoldGenerator } from "./mock.js";
2
+ import { Handler, NextFn } from "@warp-drive/core/request";
3
+ import { RequestContext, StructuredDataDocument } from "@warp-drive/core/types/request";
4
+ import { Store } from "@warp-drive/legacy/store";
5
+ //#region src/index.d.ts
6
+ /**
7
+ * @public
8
+ */
9
+ declare function setConfig({ host }: {
10
+ host: string;
11
+ }): void;
12
+ /**
13
+ * @public
14
+ */
15
+ declare function setTestId(context: object, str: string | null): void;
16
+ /**
17
+ * @public
18
+ */
19
+ declare function setIsRecording(value: boolean): void;
20
+ /**
21
+ * @public
22
+ */
23
+ declare function getIsRecording(): boolean;
24
+ /**
25
+ * A request handler that intercepts requests and routes them through
26
+ * the Holodeck mock server.
27
+ *
28
+ * This handler modifies the request URL to include test identifiers
29
+ * and manages request counts for accurate mocking.
30
+ *
31
+ * Requires that the test context be configured with a testId using `setTestId`.
32
+ *
33
+ * @param owner - the test context object used to retrieve the test ID.
34
+ */
35
+ declare class MockServerHandler implements Handler {
36
+ owner: object;
37
+ constructor(owner: object);
38
+ request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>>;
39
+ }
40
+ /**
41
+ * Creates an adapterFor function that wraps the provided adapterFor function
42
+ * to override the adapter's _fetchRequest method to route requests through
43
+ * the Holodeck mock server.
44
+ *
45
+ * @param owner - The test context object used to retrieve the test ID.
46
+ */
47
+ declare function installAdapterFor(owner: object, store: Store): void;
48
+ /**
49
+ * Mock a request by sending the scaffold to the mock server.
50
+ *
51
+ * @public
52
+ */
53
+ declare function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean): Promise<void>;
54
+ //#endregion
55
+ export { MockServerHandler, getIsRecording, installAdapterFor, mock, setConfig, setIsRecording, setTestId };
56
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;iBAuIgB,YAAY;EAAU;;;;;iBAQtB,UAAU,iBAAiB;;;;iBA2C3B,eAAe;;;;iBAOf;;;;;;;;;;;;cAeH,6BAA6B;EACxC;EACA,YAAY;EAGZ,QAAc,GAAG,SAAS,gBAAgB,MAAM,OAAO,KAAK,QAAQ,uBAAuB;;;;;;;;;iBAkG7E,kBAAkB,eAAe,OAAO;;;;;;iBAuClC,KAAK,eAAe,UAAU,mBAAmB,wBAAwB"}
package/dist/index.js CHANGED
@@ -1,237 +1,181 @@
1
- import { SHOULD_RECORD } from '@warp-drive/core/build-config/env';
1
+ import { getGlobalConfig, macroCondition } from "@embroider/macros";
2
2
 
3
+ //#region src/index.ts
3
4
  /**
4
- * @module
5
- * @mergeModuleWith <project>
6
- */
7
- const TEST_IDS = new WeakMap();
8
- let HOST = '/';
9
-
5
+ * @module
6
+ * @mergeModuleWith <project>
7
+ */
8
+ const TEST_IDS = /* @__PURE__ */ new WeakMap();
9
+ let HOST = "/";
10
10
  /**
11
- * @public
12
- */
13
-
14
- function setConfig({
15
- host
16
- }) {
17
- HOST = host.endsWith('/') ? host : `${host}/`;
11
+ * @public
12
+ */
13
+ function setConfig({ host }) {
14
+ HOST = host.endsWith("/") ? host : `${host}/`;
18
15
  }
19
-
20
16
  /**
21
- * @public
22
- */
23
-
17
+ * @public
18
+ */
24
19
  function setTestId(context, str) {
25
- if (str && TEST_IDS.has(context)) {
26
- throw new Error(`MockServerHandler is already configured with a testId.`);
27
- }
28
- if (str) {
29
- TEST_IDS.set(context, {
30
- id: str,
31
- mock: {
32
- GET: {},
33
- PUT: {},
34
- PATCH: {},
35
- DELETE: {},
36
- POST: {},
37
- QUERY: {},
38
- OPTIONS: {},
39
- HEAD: {},
40
- CONNECT: {},
41
- TRACE: {}
42
- },
43
- request: {
44
- GET: {},
45
- PUT: {},
46
- PATCH: {},
47
- DELETE: {},
48
- POST: {},
49
- QUERY: {},
50
- OPTIONS: {},
51
- HEAD: {},
52
- CONNECT: {},
53
- TRACE: {}
54
- }
55
- });
56
- } else {
57
- TEST_IDS.delete(context);
58
- }
20
+ if (str && TEST_IDS.has(context)) throw new Error(`MockServerHandler is already configured with a testId.`);
21
+ if (str) TEST_IDS.set(context, {
22
+ id: str,
23
+ mock: {
24
+ GET: {},
25
+ PUT: {},
26
+ PATCH: {},
27
+ DELETE: {},
28
+ POST: {},
29
+ QUERY: {},
30
+ OPTIONS: {},
31
+ HEAD: {},
32
+ CONNECT: {},
33
+ TRACE: {}
34
+ },
35
+ request: {
36
+ GET: {},
37
+ PUT: {},
38
+ PATCH: {},
39
+ DELETE: {},
40
+ POST: {},
41
+ QUERY: {},
42
+ OPTIONS: {},
43
+ HEAD: {},
44
+ CONNECT: {},
45
+ TRACE: {}
46
+ }
47
+ });
48
+ else TEST_IDS.delete(context);
59
49
  }
60
- const shouldRecord = SHOULD_RECORD ? true : false;
50
+ const shouldRecord = macroCondition(getGlobalConfig().WarpDrive.env.SHOULD_RECORD) ? true : false;
61
51
  let IS_RECORDING = null;
62
-
63
52
  /**
64
- * @public
65
- */
53
+ * @public
54
+ */
66
55
  function setIsRecording(value) {
67
- IS_RECORDING = value === null ? value : Boolean(value);
56
+ IS_RECORDING = value === null ? value : Boolean(value);
68
57
  }
69
-
70
58
  /**
71
- * @public
72
- */
59
+ * @public
60
+ */
73
61
  function getIsRecording() {
74
- return IS_RECORDING === null ? shouldRecord : IS_RECORDING;
62
+ return IS_RECORDING === null ? shouldRecord : IS_RECORDING;
75
63
  }
76
-
77
64
  /**
78
- * A request handler that intercepts requests and routes them through
79
- * the Holodeck mock server.
80
- *
81
- * This handler modifies the request URL to include test identifiers
82
- * and manages request counts for accurate mocking.
83
- *
84
- * Requires that the test context be configured with a testId using `setTestId`.
85
- *
86
- * @param owner - the test context object used to retrieve the test ID.
87
- */
88
- class MockServerHandler {
89
- constructor(owner) {
90
- this.owner = owner;
91
- }
92
- async request(context, next) {
93
- const {
94
- request,
95
- queryForTest
96
- } = setupHolodeckFetch(this.owner, Object.assign({}, context.request));
97
- try {
98
- const future = next(request);
99
- context.setStream(future.getStream());
100
- return await future;
101
- } catch (e) {
102
- if (e instanceof Error && !(e instanceof DOMException)) {
103
- e.message = e.message.replace(queryForTest, '');
104
- }
105
- throw e;
106
- }
107
- }
108
- }
65
+ * A request handler that intercepts requests and routes them through
66
+ * the Holodeck mock server.
67
+ *
68
+ * This handler modifies the request URL to include test identifiers
69
+ * and manages request counts for accurate mocking.
70
+ *
71
+ * Requires that the test context be configured with a testId using `setTestId`.
72
+ *
73
+ * @param owner - the test context object used to retrieve the test ID.
74
+ */
75
+ var MockServerHandler = class {
76
+ constructor(owner) {
77
+ this.owner = owner;
78
+ }
79
+ async request(context, next) {
80
+ const { request, queryForTest } = setupHolodeckFetch(this.owner, Object.assign({}, context.request));
81
+ try {
82
+ const future = next(request);
83
+ context.setStream(future.getStream());
84
+ return await future;
85
+ } catch (e) {
86
+ if (e instanceof Error && !(e instanceof DOMException)) e.message = e.message.replace(queryForTest, "");
87
+ throw e;
88
+ }
89
+ }
90
+ };
109
91
  function setupHolodeckFetch(owner, request) {
110
- const test = TEST_IDS.get(owner);
111
- if (!test) {
112
- throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);
113
- }
114
- const url = request.url;
115
- const firstChar = url.includes('?') ? '&' : '?';
116
- const method = request.method?.toUpperCase() ?? 'GET';
117
-
118
- // enable custom methods
119
- if (!test.request[method]) {
120
- // eslint-disable-next-line no-console
121
- console.log(`⚠️ Using custom HTTP method ${method} for response to request ${url}`);
122
- test.request[method] = {};
123
- }
124
- if (!(url in test.request[method])) {
125
- test.request[method][url] = 0;
126
- }
127
- const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${test.request[method][url]++}`;
128
- request.url = url + queryForTest;
129
- request.method = method;
130
- request.mode = 'cors';
131
- request.credentials = 'omit';
132
- request.referrerPolicy = '';
133
-
134
- // since holodeck currently runs on a separate port
135
- // and we don't want to trigger cors pre-flight
136
- // we convert PUT to POST to keep the request in the
137
- // "simple" cors category.
138
- // if (request.method === 'PUT') {
139
- // request.method = 'POST';
140
- // }
141
-
142
- const headers = new Headers(request.headers);
143
- if (headers.has('Content-Type')) {
144
- // under the rules of simple-cors, content-type can only be
145
- // one of three things, none of which are what folks typically
146
- // set this to. Since holodeck always expects body to be JSON
147
- // this "just works".
148
- headers.set('Content-Type', 'text/plain');
149
- request.headers = headers;
150
- }
151
- return {
152
- request,
153
- queryForTest
154
- };
92
+ const test = TEST_IDS.get(owner);
93
+ if (!test) throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);
94
+ const url = request.url;
95
+ const firstChar = url.includes("?") ? "&" : "?";
96
+ const method = request.method?.toUpperCase() ?? "GET";
97
+ if (!test.request[method]) {
98
+ console.log(`⚠️ Using custom HTTP method ${method} for response to request ${url}`);
99
+ test.request[method] = {};
100
+ }
101
+ if (!(url in test.request[method])) test.request[method][url] = 0;
102
+ const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${test.request[method][url]++}`;
103
+ request.url = url + queryForTest;
104
+ request.method = method;
105
+ request.mode = "cors";
106
+ request.credentials = "omit";
107
+ request.referrerPolicy = "";
108
+ const headers = new Headers(request.headers);
109
+ if (headers.has("Content-Type")) {
110
+ headers.set("Content-Type", "text/plain");
111
+ request.headers = headers;
112
+ }
113
+ return {
114
+ request,
115
+ queryForTest
116
+ };
155
117
  }
156
118
  function upgradeStore(store) {
157
- if (typeof store.adapterFor !== 'function') {
158
- throw new Error('Store is not compatible with Holodeck. Missing adapterFor method.');
159
- }
119
+ if (typeof store.adapterFor !== "function") throw new Error("Store is not compatible with Holodeck. Missing adapterFor method.");
160
120
  }
161
-
162
121
  /**
163
- * Creates an adapterFor function that wraps the provided adapterFor function
164
- * to override the adapter's _fetchRequest method to route requests through
165
- * the Holodeck mock server.
166
- *
167
- * @param owner - The test context object used to retrieve the test ID.
168
- */
122
+ * Creates an adapterFor function that wraps the provided adapterFor function
123
+ * to override the adapter's _fetchRequest method to route requests through
124
+ * the Holodeck mock server.
125
+ *
126
+ * @param owner - The test context object used to retrieve the test ID.
127
+ */
169
128
  function installAdapterFor(owner, store) {
170
- upgradeStore(store);
171
- const fn = store.adapterFor;
172
- function holodeckAdapterFor(modelName, _allowMissing) {
173
- const adapter = fn.call(this, modelName, _allowMissing);
174
- if (adapter) {
175
- if (!adapter.hasOverriddenFetch) {
176
- adapter.hasOverriddenFetch = true;
177
- adapter.useFetch = true;
178
- const originalFetch = adapter._fetchRequest?.bind(adapter);
179
- adapter._fetchRequest = function (options) {
180
- if (!originalFetch) {
181
- throw new Error(`Adapter ${String(modelName)} does not implement _fetchRequest`);
182
- }
183
- const {
184
- request
185
- } = setupHolodeckFetch(owner, options);
186
- return originalFetch(request);
187
- };
188
- }
189
- }
190
- return adapter;
191
- }
192
- store.adapterFor = holodeckAdapterFor;
129
+ upgradeStore(store);
130
+ const fn = store.adapterFor;
131
+ function holodeckAdapterFor(modelName, _allowMissing) {
132
+ const adapter = fn.call(this, modelName, _allowMissing);
133
+ if (adapter) {
134
+ if (!adapter.hasOverriddenFetch) {
135
+ adapter.hasOverriddenFetch = true;
136
+ adapter.useFetch = true;
137
+ const originalFetch = adapter._fetchRequest?.bind(adapter);
138
+ adapter._fetchRequest = function(options) {
139
+ if (!originalFetch) throw new Error(`Adapter ${String(modelName)} does not implement _fetchRequest`);
140
+ const { request } = setupHolodeckFetch(owner, options);
141
+ return originalFetch(request);
142
+ };
143
+ }
144
+ }
145
+ return adapter;
146
+ }
147
+ store.adapterFor = holodeckAdapterFor;
193
148
  }
194
-
195
149
  /**
196
- * Mock a request by sending the scaffold to the mock server.
197
- *
198
- * @public
199
- */
150
+ * Mock a request by sending the scaffold to the mock server.
151
+ *
152
+ * @public
153
+ */
200
154
  async function mock(owner, generate, isRecording) {
201
- if (getIsRecording() || isRecording) {
202
- const test = TEST_IDS.get(owner);
203
- if (!test) {
204
- throw new Error(`Cannot call "mock" before configuring a testId. Use setTestId to set the testId for each test`);
205
- }
206
- const requestToMock = generate();
207
- const {
208
- url: mockUrl,
209
- method
210
- } = requestToMock;
211
- if (!mockUrl || !method) {
212
- throw new Error(`MockError: Cannot mock a request without providing a URL and Method`);
213
- }
214
- const mockMethod = method?.toUpperCase() ?? 'GET';
215
-
216
- // enable custom methods
217
- if (!test.mock[mockMethod]) {
218
- // eslint-disable-next-line no-console
219
- console.log(`⚠️ Using custom HTTP method ${mockMethod} for response to request ${mockUrl}`);
220
- test.mock[mockMethod] = {};
221
- }
222
- if (!(mockUrl in test.mock[mockMethod])) {
223
- test.mock[mockMethod][mockUrl] = 0;
224
- }
225
- const testMockNum = test.mock[mockMethod][mockUrl]++;
226
- const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;
227
- await fetch(url, {
228
- method: 'POST',
229
- body: JSON.stringify(requestToMock),
230
- mode: 'cors',
231
- credentials: 'omit',
232
- referrerPolicy: ''
233
- });
234
- }
155
+ if (getIsRecording() || isRecording) {
156
+ const test = TEST_IDS.get(owner);
157
+ if (!test) throw new Error(`Cannot call "mock" before configuring a testId. Use setTestId to set the testId for each test`);
158
+ const requestToMock = generate();
159
+ const { url: mockUrl, method } = requestToMock;
160
+ if (!mockUrl || !method) throw new Error(`MockError: Cannot mock a request without providing a URL and Method`);
161
+ const mockMethod = method?.toUpperCase() ?? "GET";
162
+ if (!test.mock[mockMethod]) {
163
+ console.log(`⚠️ Using custom HTTP method ${mockMethod} for response to request ${mockUrl}`);
164
+ test.mock[mockMethod] = {};
165
+ }
166
+ if (!(mockUrl in test.mock[mockMethod])) test.mock[mockMethod][mockUrl] = 0;
167
+ const testMockNum = test.mock[mockMethod][mockUrl]++;
168
+ const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;
169
+ await fetch(url, {
170
+ method: "POST",
171
+ body: JSON.stringify(requestToMock),
172
+ mode: "cors",
173
+ credentials: "omit",
174
+ referrerPolicy: ""
175
+ });
176
+ }
235
177
  }
236
178
 
179
+ //#endregion
237
180
  export { MockServerHandler, getIsRecording, installAdapterFor, mock, setConfig, setIsRecording, setTestId };
181
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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","delete","shouldRecord","WarpDrive","env","SHOULD_RECORD","IS_RECORDING","setIsRecording","value","Boolean","getIsRecording","MockServerHandler","constructor","owner","next","queryForTest","setupHolodeckFetch","Object","assign","future","setStream","getStream","e","DOMException","message","replace","test","get","url","firstChar","includes","method","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","requestToMock","mockUrl","mockMethod","testMockNum","fetch","body","JSON","stringify"],"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 { 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\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 TEST_IDS.delete(context);\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 e.message = e.message.replace(queryForTest, '');\n }\n throw e;\n }\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 // eslint-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(owner: object, generate: ScaffoldGenerator, isRecording?: boolean): Promise<void> {\n if (getIsRecording() || isRecording) {\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 const requestToMock = generate();\n const { url: mockUrl, method } = requestToMock;\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 // eslint-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 const testMockNum = test.mock[mockMethod][mockUrl]++;\n const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;\n await fetch(url, {\n method: 'POST',\n body: JSON.stringify(requestToMock),\n mode: 'cors',\n credentials: 'omit',\n referrerPolicy: '',\n });\n }\n}\n"],"mappings":"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"}
package/dist/mock.d.ts ADDED
@@ -0,0 +1,89 @@
1
+ //#region src/mock.d.ts
2
+ /**
3
+ * @public
4
+ */
5
+ interface Scaffold {
6
+ status: number;
7
+ statusText?: string;
8
+ headers: Record<string, string>;
9
+ body: Record<string, string> | string | null;
10
+ method: string;
11
+ url: string;
12
+ response: Record<string, unknown>;
13
+ }
14
+ /**
15
+ * @public
16
+ */
17
+ type ScaffoldGenerator = () => Scaffold;
18
+ /**
19
+ * @public
20
+ */
21
+ type ResponseGenerator = () => Record<string, unknown>;
22
+ /**
23
+ * Sets up Mocking for a GET request on the mock server
24
+ * for the supplied url.
25
+ *
26
+ * The response body is generated by the supplied response function.
27
+ *
28
+ * Available options:
29
+ * - status: the status code to return (default: 200)
30
+ * - headers: the headers to return (default: {})
31
+ * - body: the body to match against for the request (default: null)
32
+ * - RECORD: whether to record the request (default: false)
33
+ *
34
+ * @param url the url to mock, relative to the mock server host (e.g. `users/1`)
35
+ * @param response a function which generates the response to return
36
+ * @param options status, headers for the response, body to match against for the request, and whether to record the request
37
+ * @return
38
+ */
39
+ declare function GET(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
40
+ RECORD?: boolean;
41
+ }): Promise<void>;
42
+ /**
43
+ * Mock a POST request
44
+ */
45
+ declare function POST(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
46
+ RECORD?: boolean;
47
+ }): Promise<void>;
48
+ /**
49
+ * mock a PUT request
50
+ */
51
+ declare function PUT(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
52
+ RECORD?: boolean;
53
+ }): Promise<void>;
54
+ /**
55
+ * mock a PATCH request
56
+ *
57
+ */
58
+ declare function PATCH(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
59
+ RECORD?: boolean;
60
+ }): Promise<void>;
61
+ /**
62
+ * mock a DELETE request
63
+ */
64
+ declare function DELETE(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
65
+ RECORD?: boolean;
66
+ }): Promise<void>;
67
+ /**
68
+ * Sets up Mocking for a HEAD request on the mock server
69
+ * for the supplied url.
70
+ *
71
+ * The response body is generated by the supplied response function.
72
+ *
73
+ * Available options:
74
+ * - status: the status code to return (default: 200)
75
+ * - headers: the headers to return (default: {})
76
+ * - body: the body to match against for the request (default: null)
77
+ * - RECORD: whether to record the request (default: false)
78
+ *
79
+ * @param url the url to mock, relative to the mock server host (e.g. `users/1`)
80
+ * @param response a function which generates the response to return
81
+ * @param options status, headers for the response, body to match against for the request, and whether to record the request
82
+ * @return
83
+ */
84
+ declare function HEAD(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
85
+ RECORD?: boolean;
86
+ }): Promise<void>;
87
+ //#endregion
88
+ export { DELETE, GET, HEAD, PATCH, POST, PUT, ResponseGenerator, Scaffold, ScaffoldGenerator };
89
+ //# sourceMappingURL=mock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.d.ts","names":[],"sources":["../src/mock.ts"],"mappings":";;;;UAKiB;EACf;EACA;EACA,SAAS;EACT,MAAM;EACN;EACA;EACA,UAAU;;;;;KAMA,0BAA0B;;;;KAK1B,0BAA0B;;;;;;;;;;;;;;;;;;iBAmBtB,IACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;iBAmFa,KACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;iBAwBa,IACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;iBAwBa,MACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;iBAuBa,OACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;;;;;;;;;;;;;;iBAsCa,KACd,eACA,aACA,UAAU,mBAIV,UAAU,QAAQ,KAAK;EAA8C;IACpE"}