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

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ScaffoldGenerator } from "./mock.js";
1
+ import { LazyScaffold, ScaffoldGenerator } from "./mock.js";
2
2
  import { Handler, NextFn } from "@warp-drive/core/request";
3
3
  import { RequestContext, StructuredDataDocument } from "@warp-drive/core/types/request";
4
4
  import { Store } from "@warp-drive/legacy/store";
@@ -50,6 +50,6 @@ export declare function installAdapterFor(owner: object, store: Store): void;
50
50
  *
51
51
  * @public
52
52
  */
53
- export declare function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean): Promise<void>;
53
+ export declare function mock(owner: object, generate: ScaffoldGenerator | LazyScaffold, isRecording?: boolean): Promise<void>;
54
54
  //#endregion
55
55
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;wBAuIgB,YAAY;EAAU;;;;;wBAQtB,UAAU,iBAAiB;;;;wBA2C3B,eAAe;;;;wBAOf;;;;;;;;;;;;qBAeH,6BAA6B;EACxC;EACA,YAAY;EAGZ,QAAc,GAAG,SAAS,gBAAgB,MAAM,OAAO,KAAK,QAAQ,uBAAuB;;;;;;;;;wBAkG7E,kBAAkB,eAAe,OAAO;;;;;;wBAuClC,KAAK,eAAe,UAAU,mBAAmB,wBAAwB"}
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"}
package/dist/index.js CHANGED
@@ -6,6 +6,10 @@ import { getGlobalConfig, macroCondition } from "@embroider/macros";
6
6
  * @mergeModuleWith <project>
7
7
  */
8
8
  const TEST_IDS = /* @__PURE__ */ new WeakMap();
9
+ /**
10
+ * The shape `setTestId` stores per test context, named so the report below can
11
+ * take one as an argument.
12
+ */
9
13
  let HOST = "/";
10
14
  /**
11
15
  * @public
@@ -45,7 +49,49 @@ function setTestId(context, str) {
45
49
  TRACE: {}
46
50
  }
47
51
  });
48
- else TEST_IDS.delete(context);
52
+ else {
53
+ const test = TEST_IDS.get(context);
54
+ TEST_IDS.delete(context);
55
+ if (test) reportUnrequestedMocks(test);
56
+ }
57
+ }
58
+ /**
59
+ * A mock is declared relative to the mock server (`users/1`) while the request
60
+ * carries the absolute url the code under test built
61
+ * (`https://localhost:7358/users/1`). The mock server reconciles the two by
62
+ * keying a fixture on the request's path, so compare them the same way.
63
+ */
64
+ function normalizeUrlKey(url) {
65
+ const withoutOrigin = url.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i, "");
66
+ return withoutOrigin.startsWith("/") ? withoutOrigin.slice(1) : withoutOrigin;
67
+ }
68
+ /**
69
+ * A mock the test never requested proves nothing: the test passes whether or
70
+ * not the code under test would have made that request. Nothing compared the
71
+ * two counters holodeck already keeps, so report the difference here, from the
72
+ * `afterEach` every suite runs, which fails the test that leaked.
73
+ *
74
+ * The original assertions are not masked by this: a framework reports an
75
+ * `afterEach` throw alongside the results the test body already recorded
76
+ * rather than in place of them.
77
+ */
78
+ function reportUnrequestedMocks(test) {
79
+ const unrequested = [];
80
+ for (const method of Object.keys(test.mock)) {
81
+ const mocked = test.mock[method];
82
+ const requested = test.request[method] ?? {};
83
+ const requestCounts = /* @__PURE__ */ new Map();
84
+ for (const url of Object.keys(requested)) {
85
+ const key = normalizeUrlKey(url);
86
+ requestCounts.set(key, (requestCounts.get(key) ?? 0) + requested[url]);
87
+ }
88
+ for (const url of Object.keys(mocked)) {
89
+ const mockCount = mocked[url];
90
+ const requestCount = requestCounts.get(normalizeUrlKey(url)) ?? 0;
91
+ if (mockCount > requestCount) unrequested.push(`\t${method} ${url} (mocked ${mockCount}, requested ${requestCount})`);
92
+ }
93
+ }
94
+ if (unrequested.length) throw new Error(`Holodeck: this test declared mocks it never requested.\n\n${unrequested.join("\n")}\n\nA mock that is never requested proves nothing. Remove it, or make the request it describes.`);
49
95
  }
50
96
  const shouldRecord = macroCondition(getGlobalConfig().WarpDrive.env.SHOULD_RECORD) ? true : false;
51
97
  let IS_RECORDING = null;
@@ -83,11 +129,34 @@ var MockServerHandler = class {
83
129
  context.setStream(future.getStream());
84
130
  return await future;
85
131
  } catch (e) {
86
- if (e instanceof Error && !(e instanceof DOMException)) e.message = e.message.replace(queryForTest, "");
132
+ if (e instanceof Error && !(e instanceof DOMException)) {
133
+ const explanation = getHolodeckExplanation(e);
134
+ if (explanation) e.message = `${e.message}\n\n${explanation}`;
135
+ e.message = e.message.split(queryForTest).join("");
136
+ }
87
137
  throw e;
88
138
  }
89
139
  }
90
140
  };
141
+ const HOLODECK_ERROR_CODES = /* @__PURE__ */ new Set([
142
+ "MOCK_NOT_FOUND",
143
+ "MISSING_X_TEST_ID_HEADER",
144
+ "MISSING_X_TEST_REQUEST_NUMBER_HEADER"
145
+ ]);
146
+ /**
147
+ * The mock server explains itself in the response body, which the thrown
148
+ * error only carries as data. Lift that explanation into the message so it
149
+ * reaches a terminal and a CI log.
150
+ */
151
+ function getHolodeckExplanation(e) {
152
+ const { content } = e;
153
+ if (!content || typeof content !== "object") return null;
154
+ const { errors } = content;
155
+ if (!Array.isArray(errors)) return null;
156
+ return errors.filter((error) => {
157
+ return !!error && typeof error === "object" && HOLODECK_ERROR_CODES.has(error.code);
158
+ }).map((error) => error.detail).filter((detail) => typeof detail === "string").join("\n\n") || null;
159
+ }
91
160
  function setupHolodeckFetch(owner, request) {
92
161
  const test = TEST_IDS.get(owner);
93
162
  if (!test) throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);
@@ -152,27 +221,49 @@ function installAdapterFor(owner, store) {
152
221
  * @public
153
222
  */
154
223
  async function mock(owner, generate, isRecording) {
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
- });
224
+ const test = TEST_IDS.get(owner);
225
+ if (!test) throw new Error(`Cannot call "mock" before configuring a testId. Use setTestId to set the testId for each test`);
226
+ let method;
227
+ let mockUrl;
228
+ let buildScaffold;
229
+ if (typeof generate === "function") {
230
+ const scaffold = generate();
231
+ ({method, url: mockUrl} = scaffold);
232
+ buildScaffold = () => scaffold;
233
+ } else {
234
+ ({method, url: mockUrl} = generate);
235
+ buildScaffold = generate.scaffold;
236
+ }
237
+ if (!mockUrl || !method) throw new Error(`MockError: Cannot mock a request without providing a URL and Method`);
238
+ const mockMethod = method.toUpperCase() ?? "GET";
239
+ if (!test.mock[mockMethod]) {
240
+ console.log(`⚠️ Using custom HTTP method ${mockMethod} for response to request ${mockUrl}`);
241
+ test.mock[mockMethod] = {};
242
+ }
243
+ if (!(mockUrl in test.mock[mockMethod])) test.mock[mockMethod][mockUrl] = 0;
244
+ const testMockNum = test.mock[mockMethod][mockUrl]++;
245
+ if (!getIsRecording() && !isRecording) return;
246
+ const requestToMock = buildScaffold();
247
+ const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;
248
+ const response = await fetch(url, {
249
+ method: "POST",
250
+ body: JSON.stringify(requestToMock),
251
+ mode: "cors",
252
+ credentials: "omit",
253
+ referrerPolicy: ""
254
+ });
255
+ if (!response.ok) throw new Error(`MockError: Holodeck failed to record ${mockMethod} ${mockUrl} (${response.status} ${response.statusText}). ${await getRecordFailureDetail(response)}`);
256
+ }
257
+ /**
258
+ * A failed recording is otherwise invisible until the next replay run fails
259
+ * with a missing fixture, so report what the server said at the point of
260
+ * failure.
261
+ */
262
+ async function getRecordFailureDetail(response) {
263
+ try {
264
+ return (await response.json()).errors?.[0]?.detail ?? "The mock server gave no explanation.";
265
+ } catch {
266
+ return "The mock server gave no explanation.";
176
267
  }
177
268
  }
178
269
 
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","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 // 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(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 // 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 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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"}
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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"}
package/dist/mock.d.ts CHANGED
@@ -15,6 +15,19 @@ export interface Scaffold {
15
15
  * @public
16
16
  */
17
17
  export type ScaffoldGenerator = () => Scaffold;
18
+ /**
19
+ * A mock whose method and url are known up front, with the rest of the
20
+ * scaffold built only when holodeck is recording. This is what the mock
21
+ * helpers pass, so that in replay mode a test's response generators never
22
+ * run.
23
+ *
24
+ * @public
25
+ */
26
+ export interface LazyScaffold {
27
+ method: string;
28
+ url: string;
29
+ scaffold: () => Scaffold;
30
+ }
18
31
  /**
19
32
  * @public
20
33
  */
@@ -29,11 +42,13 @@ export type ResponseGenerator = () => Record<string, unknown>;
29
42
  * - status: the status code to return (default: 200)
30
43
  * - headers: the headers to return (default: {})
31
44
  * - body: the body to match against for the request (default: null)
32
- * - RECORD: whether to record the request (default: false)
45
+ * - RECORD: record this request even when the suite is replaying, such as under CI (default: false).
46
+ * A local override for re-recording one request. Do not commit it; a committed RECORD means
47
+ * that request is never replayed against its fixture.
33
48
  *
34
49
  * @param url the url to mock, relative to the mock server host (e.g. `users/1`)
35
50
  * @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
51
+ * @param options status, headers for the response, body to match against for the request, and whether to force recording
37
52
  * @return
38
53
  */
39
54
  export declare function GET(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
@@ -74,11 +89,13 @@ export declare function DELETE(owner: object, url: string, response: ResponseGen
74
89
  * - status: the status code to return (default: 200)
75
90
  * - headers: the headers to return (default: {})
76
91
  * - body: the body to match against for the request (default: null)
77
- * - RECORD: whether to record the request (default: false)
92
+ * - RECORD: record this request even when the suite is replaying, such as under CI (default: false).
93
+ * A local override for re-recording one request. Do not commit it; a committed RECORD means
94
+ * that request is never replayed against its fixture.
78
95
  *
79
96
  * @param url the url to mock, relative to the mock server host (e.g. `users/1`)
80
97
  * @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
98
+ * @param options status, headers for the response, body to match against for the request, and whether to force recording
82
99
  * @return
83
100
  */
84
101
  export declare function HEAD(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
@@ -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;;;;YAK1B,0BAA0B;;;;;;;;;;;;;;;;;;wBAmBtB,IACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;wBAmFa,KACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;wBAwBa,IACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;wBAwBa,MACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;wBAuBa,OACd,eACA,aACA,UAAU,mBACV,UAAU,QAAQ,KAAK;EAA8C;IACpE;;;;;;;;;;;;;;;;;;wBAsCa,KACd,eACA,aACA,UAAU,mBAIV,UAAU,QAAQ,KAAK;EAA8C;IACpE"}
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"}
package/dist/mock.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getIsRecording, mock } from "./index.js";
1
+ import { mock } from "./index.js";
2
2
 
3
3
  //#region src/mock.ts
4
4
  /**
@@ -11,23 +11,29 @@ import { getIsRecording, mock } from "./index.js";
11
11
  * - status: the status code to return (default: 200)
12
12
  * - headers: the headers to return (default: {})
13
13
  * - body: the body to match against for the request (default: null)
14
- * - RECORD: whether to record the request (default: false)
14
+ * - RECORD: record this request even when the suite is replaying, such as under CI (default: false).
15
+ * A local override for re-recording one request. Do not commit it; a committed RECORD means
16
+ * that request is never replayed against its fixture.
15
17
  *
16
18
  * @param url the url to mock, relative to the mock server host (e.g. `users/1`)
17
19
  * @param response a function which generates the response to return
18
- * @param options status, headers for the response, body to match against for the request, and whether to record the request
20
+ * @param options status, headers for the response, body to match against for the request, and whether to force recording
19
21
  * @return
20
22
  */
21
23
  function GET(owner, url, response, options) {
22
- return mock(owner, () => ({
23
- status: options?.status ?? 200,
24
- statusText: options?.statusText ?? "OK",
25
- headers: options?.headers ?? {},
26
- body: options?.body ?? null,
24
+ return mock(owner, {
27
25
  method: "GET",
28
26
  url,
29
- response: response()
30
- }), getIsRecording() || (options?.RECORD ?? false));
27
+ scaffold: () => ({
28
+ status: options?.status ?? 200,
29
+ statusText: options?.statusText ?? "OK",
30
+ headers: options?.headers ?? {},
31
+ body: options?.body ?? null,
32
+ method: "GET",
33
+ url,
34
+ response: response()
35
+ })
36
+ }, options?.RECORD);
31
37
  }
32
38
  const STATUS_TEXT_FOR = /* @__PURE__ */ new Map([
33
39
  [200, "OK"],
@@ -96,74 +102,90 @@ const STATUS_TEXT_FOR = /* @__PURE__ */ new Map([
96
102
  * Mock a POST request
97
103
  */
98
104
  function POST(owner, url, response, options) {
99
- return mock(owner, () => {
100
- const body = response();
101
- const status = options?.status ?? (body ? 201 : 204);
102
- return {
103
- status,
104
- statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
105
- headers: options?.headers ?? {},
106
- body: options?.body ?? null,
107
- method: "POST",
108
- url,
109
- response: body
110
- };
111
- }, getIsRecording() || (options?.RECORD ?? false));
105
+ return mock(owner, {
106
+ method: "POST",
107
+ url,
108
+ scaffold: () => {
109
+ const body = response();
110
+ const status = options?.status ?? (body ? 201 : 204);
111
+ return {
112
+ status,
113
+ statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
114
+ headers: options?.headers ?? {},
115
+ body: options?.body ?? null,
116
+ method: "POST",
117
+ url,
118
+ response: body
119
+ };
120
+ }
121
+ }, options?.RECORD);
112
122
  }
113
123
  /**
114
124
  * mock a PUT request
115
125
  */
116
126
  function PUT(owner, url, response, options) {
117
- return mock(owner, () => {
118
- const body = response();
119
- const status = options?.status ?? (body ? 200 : 204);
120
- return {
121
- status,
122
- statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
123
- headers: options?.headers ?? {},
124
- body: options?.body ?? null,
125
- method: "PUT",
126
- url,
127
- response: body
128
- };
129
- }, getIsRecording() || (options?.RECORD ?? false));
127
+ return mock(owner, {
128
+ method: "PUT",
129
+ url,
130
+ scaffold: () => {
131
+ const body = response();
132
+ const status = options?.status ?? (body ? 200 : 204);
133
+ return {
134
+ status,
135
+ statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
136
+ headers: options?.headers ?? {},
137
+ body: options?.body ?? null,
138
+ method: "PUT",
139
+ url,
140
+ response: body
141
+ };
142
+ }
143
+ }, options?.RECORD);
130
144
  }
131
145
  /**
132
146
  * mock a PATCH request
133
147
  *
134
148
  */
135
149
  function PATCH(owner, url, response, options) {
136
- return mock(owner, () => {
137
- const body = response();
138
- const status = options?.status ?? (body ? 200 : 204);
139
- return {
140
- status,
141
- statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
142
- headers: options?.headers ?? {},
143
- body: options?.body ?? null,
144
- method: "PATCH",
145
- url,
146
- response: body
147
- };
148
- }, getIsRecording() || (options?.RECORD ?? false));
150
+ return mock(owner, {
151
+ method: "PATCH",
152
+ url,
153
+ scaffold: () => {
154
+ const body = response();
155
+ const status = options?.status ?? (body ? 200 : 204);
156
+ return {
157
+ status,
158
+ statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
159
+ headers: options?.headers ?? {},
160
+ body: options?.body ?? null,
161
+ method: "PATCH",
162
+ url,
163
+ response: body
164
+ };
165
+ }
166
+ }, options?.RECORD);
149
167
  }
150
168
  /**
151
169
  * mock a DELETE request
152
170
  */
153
171
  function DELETE(owner, url, response, options) {
154
- return mock(owner, () => {
155
- const body = response();
156
- const status = options?.status ?? (body ? 200 : 204);
157
- return {
158
- status,
159
- statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
160
- headers: options?.headers ?? {},
161
- body: options?.body ?? null,
162
- method: "DELETE",
163
- url,
164
- response: body
165
- };
166
- }, getIsRecording() || (options?.RECORD ?? false));
172
+ return mock(owner, {
173
+ method: "DELETE",
174
+ url,
175
+ scaffold: () => {
176
+ const body = response();
177
+ const status = options?.status ?? (body ? 200 : 204);
178
+ return {
179
+ status,
180
+ statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
181
+ headers: options?.headers ?? {},
182
+ body: options?.body ?? null,
183
+ method: "DELETE",
184
+ url,
185
+ response: body
186
+ };
187
+ }
188
+ }, options?.RECORD);
167
189
  }
168
190
  /**
169
191
  * Sets up Mocking for a HEAD request on the mock server
@@ -175,23 +197,29 @@ function DELETE(owner, url, response, options) {
175
197
  * - status: the status code to return (default: 200)
176
198
  * - headers: the headers to return (default: {})
177
199
  * - body: the body to match against for the request (default: null)
178
- * - RECORD: whether to record the request (default: false)
200
+ * - RECORD: record this request even when the suite is replaying, such as under CI (default: false).
201
+ * A local override for re-recording one request. Do not commit it; a committed RECORD means
202
+ * that request is never replayed against its fixture.
179
203
  *
180
204
  * @param url the url to mock, relative to the mock server host (e.g. `users/1`)
181
205
  * @param response a function which generates the response to return
182
- * @param options status, headers for the response, body to match against for the request, and whether to record the request
206
+ * @param options status, headers for the response, body to match against for the request, and whether to force recording
183
207
  * @return
184
208
  */
185
209
  function HEAD(owner, url, response, options) {
186
- return mock(owner, () => ({
187
- status: options?.status ?? 200,
188
- statusText: options?.statusText ?? "OK",
189
- headers: options?.headers ?? {},
190
- body: options?.body ?? null,
210
+ return mock(owner, {
191
211
  method: "HEAD",
192
212
  url,
193
- response: response()
194
- }), getIsRecording() || (options?.RECORD ?? false));
213
+ scaffold: () => ({
214
+ status: options?.status ?? 200,
215
+ statusText: options?.statusText ?? "OK",
216
+ headers: options?.headers ?? {},
217
+ body: options?.body ?? null,
218
+ method: "HEAD",
219
+ url,
220
+ response: response()
221
+ })
222
+ }, options?.RECORD);
195
223
  }
196
224
 
197
225
  //#endregion
package/dist/mock.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mock.js","names":[],"sources":["../src/mock.ts"],"sourcesContent":["import { getIsRecording, 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 * @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: whether to record the request (default: false)\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 record the request\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 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 getIsRecording() || (options?.RECORD ?? false)\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 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 getIsRecording() || (options?.RECORD ?? false)\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 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 getIsRecording() || (options?.RECORD ?? false)\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 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 getIsRecording() || (options?.RECORD ?? false)\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 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 getIsRecording() || (options?.RECORD ?? false)\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: whether to record the request (default: false)\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 record the request\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 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 getIsRecording() || (options?.RECORD ?? false)\n );\n}\n"],"mappings":"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"}
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":";;;;;;;;;;;;;;;;;;;;;;AA0DA,SAAgB,IACd,OACA,KACA,UACA,SACe;CACf,OAAO,KACL,OACA;EACE,QAAQ;EACR;EACA,iBAAiB;GACf,QAAQ,SAAS,UAAU;GAC3B,YAAY,SAAS,cAAc;GACnC,SAAS,SAAS,WAAW,CAAC;GAC9B,MAAM,SAAS,QAAQ;GACvB,QAAQ;GACR;GACA,UAAU,SAAS;EACrB;CACF,GACA,SAAS,MACX;AACF;AAEA,MAAM,kCAAkB,IAAI,IAAI;CAC9B,CAAC,KAAK,IAAI;CACV,CAAC,KAAK,SAAS;CACf,CAAC,KAAK,UAAU;CAChB,CAAC,KAAK,+BAA+B;CACrC,CAAC,KAAK,YAAY;CAClB,CAAC,KAAK,eAAe;CACrB,CAAC,KAAK,iBAAiB;CACvB,CAAC,KAAK,cAAc;CACpB,CAAC,KAAK,kBAAkB;CACxB,CAAC,KAAK,SAAS;CACf,CAAC,KAAK,kBAAkB;CACxB,CAAC,KAAK,mBAAmB;CACzB,CAAC,KAAK,OAAO;CACb,CAAC,KAAK,WAAW;CACjB,CAAC,KAAK,cAAc;CACpB,CAAC,KAAK,oBAAoB;CAC1B,CAAC,KAAK,oBAAoB;CAC1B,CAAC,KAAK,aAAa;CACnB,CAAC,KAAK,cAAc;CACpB,CAAC,KAAK,kBAAkB;CACxB,CAAC,KAAK,WAAW;CACjB,CAAC,KAAK,WAAW;CACjB,CAAC,KAAK,oBAAoB;CAC1B,CAAC,KAAK,gBAAgB;CACtB,CAAC,KAAK,+BAA+B;CACrC,CAAC,KAAK,iBAAiB;CACvB,CAAC,KAAK,UAAU;CAChB,CAAC,KAAK,MAAM;CACZ,CAAC,KAAK,iBAAiB;CACvB,CAAC,KAAK,qBAAqB;CAC3B,CAAC,KAAK,mBAAmB;CACzB,CAAC,KAAK,cAAc;CACpB,CAAC,KAAK,wBAAwB;CAC9B,CAAC,KAAK,uBAAuB;CAC7B,CAAC,KAAK,oBAAoB;CAC1B,CAAC,KAAK,cAAc;CACpB,CAAC,KAAK,mBAAmB;CACzB,CAAC,KAAK,qBAAqB;CAC3B,CAAC,KAAK,sBAAsB;CAC5B,CAAC,KAAK,QAAQ;CACd,CAAC,KAAK,mBAAmB;CACzB,CAAC,KAAK,WAAW;CACjB,CAAC,KAAK,kBAAkB;CACxB,CAAC,KAAK,uBAAuB;CAC7B,CAAC,KAAK,mBAAmB;CACzB,CAAC,KAAK,iCAAiC;CACvC,CAAC,KAAK,iCAAiC;CACvC,CAAC,KAAK,sCAAsC;CAC5C,CAAC,KAAK,+BAA+B;CACrC,CAAC,KAAK,uBAAuB;CAC7B,CAAC,KAAK,iBAAiB;CACvB,CAAC,KAAK,aAAa;CACnB,CAAC,KAAK,qBAAqB;CAC3B,CAAC,KAAK,iBAAiB;CACvB,CAAC,KAAK,4BAA4B;CAClC,CAAC,KAAK,yBAAyB;CAC/B,CAAC,KAAK,sBAAsB;CAC5B,CAAC,KAAK,eAAe;CACrB,CAAC,KAAK,0BAA0B;CAChC,CAAC,KAAK,cAAc;CACpB,CAAC,KAAK,iCAAiC;AACzC,CAAC;;;;AAKD,SAAgB,KACd,OACA,KACA,UACA,SACe;CACf,OAAO,KACL,OACA;EACE,QAAQ;EACR;EACA,gBAAgB;GACd,MAAM,OAAO,SAAS;GACtB,MAAM,SAAS,SAAS,WAAW,OAAO,MAAM;GAEhD,OAAO;IACG;IACR,YAAY,SAAS,cAAc,gBAAgB,IAAI,MAAM,KAAK;IAClE,SAAS,SAAS,WAAW,CAAC;IAC9B,MAAM,SAAS,QAAQ;IACvB,QAAQ;IACR;IACA,UAAU;GACZ;EACF;CACF,GACA,SAAS,MACX;AACF;;;;AAKA,SAAgB,IACd,OACA,KACA,UACA,SACe;CACf,OAAO,KACL,OACA;EACE,QAAQ;EACR;EACA,gBAAgB;GACd,MAAM,OAAO,SAAS;GACtB,MAAM,SAAS,SAAS,WAAW,OAAO,MAAM;GAEhD,OAAO;IACG;IACR,YAAY,SAAS,cAAc,gBAAgB,IAAI,MAAM,KAAK;IAClE,SAAS,SAAS,WAAW,CAAC;IAC9B,MAAM,SAAS,QAAQ;IACvB,QAAQ;IACR;IACA,UAAU;GACZ;EACF;CACF,GACA,SAAS,MACX;AACF;;;;;AAKA,SAAgB,MACd,OACA,KACA,UACA,SACe;CACf,OAAO,KACL,OACA;EACE,QAAQ;EACR;EACA,gBAAgB;GACd,MAAM,OAAO,SAAS;GACtB,MAAM,SAAS,SAAS,WAAW,OAAO,MAAM;GAEhD,OAAO;IACG;IACR,YAAY,SAAS,cAAc,gBAAgB,IAAI,MAAM,KAAK;IAClE,SAAS,SAAS,WAAW,CAAC;IAC9B,MAAM,SAAS,QAAQ;IACvB,QAAQ;IACR;IACA,UAAU;GACZ;EACF;CACF,GACA,SAAS,MACX;AACF;;;;AAIA,SAAgB,OACd,OACA,KACA,UACA,SACe;CACf,OAAO,KACL,OACA;EACE,QAAQ;EACR;EACA,gBAAgB;GACd,MAAM,OAAO,SAAS;GACtB,MAAM,SAAS,SAAS,WAAW,OAAO,MAAM;GAEhD,OAAO;IACG;IACR,YAAY,SAAS,cAAc,gBAAgB,IAAI,MAAM,KAAK;IAClE,SAAS,SAAS,WAAW,CAAC;IAC9B,MAAM,SAAS,QAAQ;IACvB,QAAQ;IACR;IACA,UAAU;GACZ;EACF;CACF,GACA,SAAS,MACX;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,KACd,OACA,KACA,UAIA,SACe;CACf,OAAO,KACL,OACA;EACE,QAAQ;EACR;EACA,iBAAiB;GACf,QAAQ,SAAS,UAAU;GAC3B,YAAY,SAAS,cAAc;GACnC,SAAS,SAAS,WAAW,CAAC;GAC9B,MAAM,SAAS,QAAQ;GACvB,QAAQ;GACR;GACA,UAAU,SAAS;EACrB;CACF,GACA,SAAS,MACX;AACF"}