@jterrazz/test 6.4.1 → 6.5.0

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.
@@ -23,55 +23,57 @@ async function ensureInterceptServer() {
23
23
  }
24
24
  /**
25
25
  * Register intercept entries as MSW handlers. Returns a cleanup function.
26
- * Handlers are consumed in queue order — each trigger match pops one response.
26
+ *
27
+ * Each incoming request is matched against all unconsumed entries in order.
28
+ * The first entry whose URL and optional body filter match is consumed.
29
+ * This supports multiple entries for the same URL with different body matchers.
27
30
  */
28
31
  async function registerIntercepts(entries) {
29
32
  if (entries.length === 0) return () => {};
30
33
  await ensureInterceptServer();
31
34
  const { msw } = await loadMsw();
32
- const queues = /* @__PURE__ */ new Map();
33
- for (const entry of entries) {
34
- const key = `${entry.trigger.method}:${entry.trigger.url}`;
35
- const existing = queues.get(key) ?? [];
36
- existing.push(entry);
37
- queues.set(key, existing);
38
- }
35
+ const consumed = Array.from({ length: entries.length }, () => false);
36
+ const urls = /* @__PURE__ */ new Set();
37
+ for (const entry of entries) urls.add(entry.trigger.url);
39
38
  const handlers = [];
40
- for (const [, queue] of queues) {
41
- let index = 0;
42
- const trigger = queue[0].trigger;
43
- const handler = trigger.method === "*" ? msw.http.all(trigger.url instanceof RegExp ? trigger.url : trigger.url, createResolver(queue, () => index, () => {
44
- index++;
45
- })) : msw.http[trigger.method.toLowerCase()](trigger.url instanceof RegExp ? trigger.url : trigger.url, createResolver(queue, () => index, () => {
46
- index++;
47
- }));
48
- handlers.push(handler);
39
+ for (const url of urls) {
40
+ const methods = /* @__PURE__ */ new Set();
41
+ for (const entry of entries) if (entry.trigger.url === url || String(entry.trigger.url) === String(url)) methods.add(entry.trigger.method);
42
+ for (const method of methods) {
43
+ const handlerFn = method === "*" ? msw.http.all : msw.http[method.toLowerCase()];
44
+ if (!handlerFn) continue;
45
+ const handler = handlerFn(url, async ({ request }) => {
46
+ let body = null;
47
+ let bodyParsed = false;
48
+ for (let i = 0; i < entries.length; i++) {
49
+ if (consumed[i]) continue;
50
+ const entry = entries[i];
51
+ if (entry.trigger.url !== url && String(entry.trigger.url) !== String(url)) continue;
52
+ if (entry.trigger.method !== method && entry.trigger.method !== "*") continue;
53
+ if (entry.trigger.match) {
54
+ if (!bodyParsed) {
55
+ try {
56
+ body = await request.clone().json();
57
+ } catch {}
58
+ bodyParsed = true;
59
+ }
60
+ if (!entry.trigger.match(body)) continue;
61
+ }
62
+ consumed[i] = true;
63
+ if (entry.response.delay) await new Promise((r) => setTimeout(r, entry.response.delay));
64
+ return msw.HttpResponse.json(entry.response.body, {
65
+ status: entry.response.status ?? 200,
66
+ headers: entry.response.headers
67
+ });
68
+ }
69
+ });
70
+ handlers.push(handler);
71
+ }
49
72
  }
50
73
  serverInstance.use(...handlers);
51
74
  return () => {
52
75
  serverInstance.resetHandlers();
53
76
  };
54
- function createResolver(queue, getIndex, advance) {
55
- return async ({ request }) => {
56
- const idx = getIndex();
57
- if (idx >= queue.length) return;
58
- const entry = queue[idx];
59
- if (entry.trigger.match) {
60
- let body = null;
61
- try {
62
- body = await request.clone().json();
63
- } catch {}
64
- if (!entry.trigger.match(body)) return;
65
- }
66
- advance();
67
- if (entry.response.delay) await new Promise((r) => setTimeout(r, entry.response.delay));
68
- const { HttpResponse } = await loadMsw().then((m) => m.msw);
69
- return HttpResponse.json(entry.response.body, {
70
- status: entry.response.status ?? 200,
71
- headers: entry.response.headers
72
- });
73
- };
74
- }
75
77
  }
76
78
  //#endregion
77
79
  export { registerIntercepts };
@@ -1 +1 @@
1
- {"version":3,"file":"intercept2.js","names":[],"sources":["../src/builder/common/intercept/intercept.ts"],"sourcesContent":["/**\n * MSW-based intercept server. Manages HTTP interception for spec.run().\n *\n * Intercepts are queued per triggerthe first matching handler is consumed,\n * then the next one in the queue is used for subsequent matching requests.\n */\nimport type { InterceptEntry } from './types.js';\n\nlet mswModule: any = null;\nlet mswHttp: any = null;\n\nasync function loadMsw() {\n if (!mswModule) {\n mswModule = await import('msw/node');\n mswHttp = await import('msw');\n }\n return { node: mswModule, msw: mswHttp! };\n}\n\nlet serverInstance: any = null;\n\n/**\n * Start the MSW server (once per process).\n */\nexport async function ensureInterceptServer(): Promise<void> {\n if (serverInstance) {\n return;\n }\n const { node } = await loadMsw();\n serverInstance = node.setupServer();\n serverInstance.listen({ onUnhandledRequest: 'bypass' });\n}\n\n/**\n * Register intercept entries as MSW handlers. Returns a cleanup function.\n * Handlers are consumed in queue order each trigger match pops one response.\n */\nexport async function registerIntercepts(entries: InterceptEntry[]): Promise<() => void> {\n if (entries.length === 0) {\n return () => {};\n }\n\n await ensureInterceptServer();\n const { msw } = await loadMsw();\n\n // Build a queue per unique trigger (same method+url+match combo)\n const queues = new Map<string, InterceptEntry[]>();\n for (const entry of entries) {\n const key = `${entry.trigger.method}:${entry.trigger.url}`;\n const existing = queues.get(key) ?? [];\n existing.push(entry);\n queues.set(key, existing);\n }\n\n const handlers: any[] = [];\n\n for (const [, queue] of queues) {\n let index = 0;\n const trigger = queue[0].trigger;\n\n const handler =\n trigger.method === '*'\n ? msw.http.all(\n trigger.url instanceof RegExp ? trigger.url : trigger.url,\n createResolver(\n queue,\n () => index,\n () => {\n index++;\n },\n ),\n )\n : (msw.http as any)[trigger.method.toLowerCase()](\n trigger.url instanceof RegExp ? trigger.url : trigger.url,\n createResolver(\n queue,\n () => index,\n () => {\n index++;\n },\n ),\n );\n\n handlers.push(handler);\n }\n\n serverInstance.use(...handlers);\n\n return () => {\n serverInstance.resetHandlers();\n };\n\n function createResolver(queue: InterceptEntry[], getIndex: () => number, advance: () => void) {\n return async ({ request }: { request: Request }) => {\n const idx = getIndex();\n if (idx >= queue.length) {\n // Queue exhausted — passthrough\n return undefined;\n }\n\n const entry = queue[idx];\n\n // Check body matcher if present\n if (entry.trigger.match) {\n let body: unknown = null;\n try {\n body = await request.clone().json();\n } catch {\n // Not JSON skip match\n }\n if (!entry.trigger.match(body)) {\n return undefined;\n }\n }\n\n advance();\n\n // Apply delay if specified\n if (entry.response.delay) {\n await new Promise((r) => setTimeout(r, entry.response.delay));\n }\n\n const { HttpResponse } = await loadMsw().then((m) => m.msw);\n return HttpResponse.json(entry.response.body, {\n status: entry.response.status ?? 200,\n headers: entry.response.headers,\n });\n };\n }\n}\n\n/**\n * Stop the MSW server (call in afterAll).\n */\nexport async function stopInterceptServer(): Promise<void> {\n if (serverInstance) {\n serverInstance.close();\n serverInstance = null;\n }\n}\n"],"mappings":";AAQA,IAAI,YAAiB;AACrB,IAAI,UAAe;AAEnB,eAAe,UAAU;AACrB,KAAI,CAAC,WAAW;AACZ,cAAY,MAAM,OAAO;AACzB,YAAU,MAAM,OAAO;;AAE3B,QAAO;EAAE,MAAM;EAAW,KAAK;EAAU;;AAG7C,IAAI,iBAAsB;;;;AAK1B,eAAsB,wBAAuC;AACzD,KAAI,eACA;CAEJ,MAAM,EAAE,SAAS,MAAM,SAAS;AAChC,kBAAiB,KAAK,aAAa;AACnC,gBAAe,OAAO,EAAE,oBAAoB,UAAU,CAAC;;;;;;AAO3D,eAAsB,mBAAmB,SAAgD;AACrF,KAAI,QAAQ,WAAW,EACnB,cAAa;AAGjB,OAAM,uBAAuB;CAC7B,MAAM,EAAE,QAAQ,MAAM,SAAS;CAG/B,MAAM,yBAAS,IAAI,KAA+B;AAClD,MAAK,MAAM,SAAS,SAAS;EACzB,MAAM,MAAM,GAAG,MAAM,QAAQ,OAAO,GAAG,MAAM,QAAQ;EACrD,MAAM,WAAW,OAAO,IAAI,IAAI,IAAI,EAAE;AACtC,WAAS,KAAK,MAAM;AACpB,SAAO,IAAI,KAAK,SAAS;;CAG7B,MAAM,WAAkB,EAAE;AAE1B,MAAK,MAAM,GAAG,UAAU,QAAQ;EAC5B,IAAI,QAAQ;EACZ,MAAM,UAAU,MAAM,GAAG;EAEzB,MAAM,UACF,QAAQ,WAAW,MACb,IAAI,KAAK,IACL,QAAQ,eAAe,SAAS,QAAQ,MAAM,QAAQ,KACtD,eACI,aACM,aACA;AACF;IAEP,CACJ,GACA,IAAI,KAAa,QAAQ,OAAO,aAAa,EAC1C,QAAQ,eAAe,SAAS,QAAQ,MAAM,QAAQ,KACtD,eACI,aACM,aACA;AACF;IAEP,CACJ;AAEX,WAAS,KAAK,QAAQ;;AAG1B,gBAAe,IAAI,GAAG,SAAS;AAE/B,cAAa;AACT,iBAAe,eAAe;;CAGlC,SAAS,eAAe,OAAyB,UAAwB,SAAqB;AAC1F,SAAO,OAAO,EAAE,cAAoC;GAChD,MAAM,MAAM,UAAU;AACtB,OAAI,OAAO,MAAM,OAEb;GAGJ,MAAM,QAAQ,MAAM;AAGpB,OAAI,MAAM,QAAQ,OAAO;IACrB,IAAI,OAAgB;AACpB,QAAI;AACA,YAAO,MAAM,QAAQ,OAAO,CAAC,MAAM;YAC/B;AAGR,QAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,CAC1B;;AAIR,YAAS;AAGT,OAAI,MAAM,SAAS,MACf,OAAM,IAAI,SAAS,MAAM,WAAW,GAAG,MAAM,SAAS,MAAM,CAAC;GAGjE,MAAM,EAAE,iBAAiB,MAAM,SAAS,CAAC,MAAM,MAAM,EAAE,IAAI;AAC3D,UAAO,aAAa,KAAK,MAAM,SAAS,MAAM;IAC1C,QAAQ,MAAM,SAAS,UAAU;IACjC,SAAS,MAAM,SAAS;IAC3B,CAAC"}
1
+ {"version":3,"file":"intercept2.js","names":[],"sources":["../src/builder/common/intercept/intercept.ts"],"sourcesContent":["/**\n * MSW-based intercept server. Manages HTTP interception for spec.run().\n *\n * Intercepts are matched in orderfor each incoming request, the first\n * unconsumed entry whose trigger matches (URL + optional body filter) is\n * used and consumed. This supports body-based routing to the same URL.\n */\nimport type { InterceptEntry } from './types.js';\n\nlet mswModule: any = null;\nlet mswHttp: any = null;\n\nasync function loadMsw() {\n if (!mswModule) {\n mswModule = await import('msw/node');\n mswHttp = await import('msw');\n }\n return { node: mswModule, msw: mswHttp! };\n}\n\nlet serverInstance: any = null;\n\n/**\n * Start the MSW server (once per process).\n */\nexport async function ensureInterceptServer(): Promise<void> {\n if (serverInstance) {\n return;\n }\n const { node } = await loadMsw();\n serverInstance = node.setupServer();\n serverInstance.listen({ onUnhandledRequest: 'bypass' });\n}\n\n/**\n * Register intercept entries as MSW handlers. Returns a cleanup function.\n *\n * Each incoming request is matched against all unconsumed entries in order.\n * The first entry whose URL and optional body filter match is consumed.\n * This supports multiple entries for the same URL with different body matchers.\n */\nexport async function registerIntercepts(entries: InterceptEntry[]): Promise<() => void> {\n if (entries.length === 0) {\n return () => {};\n }\n\n await ensureInterceptServer();\n const { msw } = await loadMsw();\n\n // Track which entries have been consumed\n const consumed = Array.from({ length: entries.length }, () => false);\n\n // Collect unique URL patterns to register handlers for\n const urls = new Set<RegExp | string>();\n for (const entry of entries) {\n urls.add(entry.trigger.url);\n }\n\n const handlers: any[] = [];\n\n for (const url of urls) {\n // Collect all methods for this URL\n const methods = new Set<string>();\n for (const entry of entries) {\n if (entry.trigger.url === url || String(entry.trigger.url) === String(url)) {\n methods.add(entry.trigger.method);\n }\n }\n\n for (const method of methods) {\n const handlerFn =\n method === '*' ? msw.http.all : (msw.http as any)[method.toLowerCase()];\n if (!handlerFn) {\n continue;\n }\n\n const handler = handlerFn(url, async ({ request }: { request: Request }) => {\n // Clone body once for all match checks\n let body: unknown = null;\n let bodyParsed = false;\n\n for (let i = 0; i < entries.length; i++) {\n if (consumed[i]) {\n continue;\n }\n\n const entry = entries[i];\n\n // Check URL matches\n if (entry.trigger.url !== url && String(entry.trigger.url) !== String(url)) {\n continue;\n }\n\n // Check method matches\n if (entry.trigger.method !== method && entry.trigger.method !== '*') {\n continue;\n }\n\n // Check body matcher if present\n if (entry.trigger.match) {\n if (!bodyParsed) {\n try {\n body = await request.clone().json();\n } catch {\n // Not JSON\n }\n bodyParsed = true;\n }\n if (!entry.trigger.match(body)) {\n continue;\n }\n }\n\n // Match found consume and respond\n consumed[i] = true;\n\n if (entry.response.delay) {\n await new Promise((r) => setTimeout(r, entry.response.delay));\n }\n\n return msw.HttpResponse.json(entry.response.body, {\n status: entry.response.status ?? 200,\n headers: entry.response.headers,\n });\n }\n\n // No match — passthrough\n return undefined;\n });\n\n handlers.push(handler);\n }\n }\n\n serverInstance.use(...handlers);\n\n return () => {\n serverInstance.resetHandlers();\n };\n}\n\n/**\n * Stop the MSW server (call in afterAll).\n */\nexport async function stopInterceptServer(): Promise<void> {\n if (serverInstance) {\n serverInstance.close();\n serverInstance = null;\n }\n}\n"],"mappings":";AASA,IAAI,YAAiB;AACrB,IAAI,UAAe;AAEnB,eAAe,UAAU;AACrB,KAAI,CAAC,WAAW;AACZ,cAAY,MAAM,OAAO;AACzB,YAAU,MAAM,OAAO;;AAE3B,QAAO;EAAE,MAAM;EAAW,KAAK;EAAU;;AAG7C,IAAI,iBAAsB;;;;AAK1B,eAAsB,wBAAuC;AACzD,KAAI,eACA;CAEJ,MAAM,EAAE,SAAS,MAAM,SAAS;AAChC,kBAAiB,KAAK,aAAa;AACnC,gBAAe,OAAO,EAAE,oBAAoB,UAAU,CAAC;;;;;;;;;AAU3D,eAAsB,mBAAmB,SAAgD;AACrF,KAAI,QAAQ,WAAW,EACnB,cAAa;AAGjB,OAAM,uBAAuB;CAC7B,MAAM,EAAE,QAAQ,MAAM,SAAS;CAG/B,MAAM,WAAW,MAAM,KAAK,EAAE,QAAQ,QAAQ,QAAQ,QAAQ,MAAM;CAGpE,MAAM,uBAAO,IAAI,KAAsB;AACvC,MAAK,MAAM,SAAS,QAChB,MAAK,IAAI,MAAM,QAAQ,IAAI;CAG/B,MAAM,WAAkB,EAAE;AAE1B,MAAK,MAAM,OAAO,MAAM;EAEpB,MAAM,0BAAU,IAAI,KAAa;AACjC,OAAK,MAAM,SAAS,QAChB,KAAI,MAAM,QAAQ,QAAQ,OAAO,OAAO,MAAM,QAAQ,IAAI,KAAK,OAAO,IAAI,CACtE,SAAQ,IAAI,MAAM,QAAQ,OAAO;AAIzC,OAAK,MAAM,UAAU,SAAS;GAC1B,MAAM,YACF,WAAW,MAAM,IAAI,KAAK,MAAO,IAAI,KAAa,OAAO,aAAa;AAC1E,OAAI,CAAC,UACD;GAGJ,MAAM,UAAU,UAAU,KAAK,OAAO,EAAE,cAAoC;IAExE,IAAI,OAAgB;IACpB,IAAI,aAAa;AAEjB,SAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACrC,SAAI,SAAS,GACT;KAGJ,MAAM,QAAQ,QAAQ;AAGtB,SAAI,MAAM,QAAQ,QAAQ,OAAO,OAAO,MAAM,QAAQ,IAAI,KAAK,OAAO,IAAI,CACtE;AAIJ,SAAI,MAAM,QAAQ,WAAW,UAAU,MAAM,QAAQ,WAAW,IAC5D;AAIJ,SAAI,MAAM,QAAQ,OAAO;AACrB,UAAI,CAAC,YAAY;AACb,WAAI;AACA,eAAO,MAAM,QAAQ,OAAO,CAAC,MAAM;eAC/B;AAGR,oBAAa;;AAEjB,UAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,CAC1B;;AAKR,cAAS,KAAK;AAEd,SAAI,MAAM,SAAS,MACf,OAAM,IAAI,SAAS,MAAM,WAAW,GAAG,MAAM,SAAS,MAAM,CAAC;AAGjE,YAAO,IAAI,aAAa,KAAK,MAAM,SAAS,MAAM;MAC9C,QAAQ,MAAM,SAAS,UAAU;MACjC,SAAS,MAAM,SAAS;MAC3B,CAAC;;KAKR;AAEF,YAAS,KAAK,QAAQ;;;AAI9B,gBAAe,IAAI,GAAG,SAAS;AAE/B,cAAa;AACT,iBAAe,eAAe"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jterrazz/test",
3
- "version": "6.4.1",
3
+ "version": "6.5.0",
4
4
  "author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
5
5
  "repository": {
6
6
  "type": "git",