@testsmith/api-spector 0.3.4 → 0.3.6

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/out/main/index.js CHANGED
@@ -22,28 +22,28 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  const electron = require("electron");
25
- const handle = require("./chunks/handle-C0IQL-Vl.js");
25
+ const handle = require("./chunks/handle-BtRMtQJg.js");
26
26
  const path = require("path");
27
27
  const fs = require("fs");
28
28
  const promises = require("fs/promises");
29
- const requestCollection = require("./chunks/request-collection-8TOVNXE0.js");
30
- const authBuilder = require("./chunks/auth-builder-CUs9yzOF.js");
29
+ const requestExec = require("./chunks/request-exec-DbNHbA8x.js");
31
30
  const ipcValidate = require("./chunks/ipc-validate-k6KI8adf.js");
32
31
  const uuid = require("uuid");
33
32
  const jsYaml = require("js-yaml");
34
33
  const undici = require("undici");
35
34
  const JSZip = require("jszip");
35
+ const requestCollection = require("./chunks/request-collection-CBuPjBwa.js");
36
36
  const mockServer = require("./chunks/mock-server-DSLR2ulH.js");
37
37
  const http = require("http");
38
38
  const WebSocket = require("ws");
39
- const soapHandler = require("./chunks/soap-handler-B9x_YCtj.js");
39
+ const soapHandler = require("./chunks/soap-handler-CqXVreNa.js");
40
40
  const os = require("os");
41
41
  const crypto = require("crypto");
42
- const snapshots = require("./chunks/snapshots-UFd3XgSS.js");
42
+ const snapshots = require("./chunks/snapshots-rV5tPwAd.js");
43
43
  const simpleGit = require("simple-git");
44
- const recorder = require("./chunks/recorder-DFxJgn9c.js");
45
- require("vm");
44
+ const recorder = require("./chunks/recorder-0Ij921El.js");
46
45
  require("dayjs");
46
+ require("vm");
47
47
  require("tv4");
48
48
  require("jsonpath-plus");
49
49
  require("@xmldom/xmldom");
@@ -67,7 +67,7 @@ function atomicWrite(path2, data) {
67
67
  return promises.writeFile(path2, data, "utf8");
68
68
  }
69
69
  const SPECTOR_GITIGNORE = [
70
- "# API Spector never commit secrets",
70
+ "# API Spector - never commit secrets",
71
71
  "*.secrets",
72
72
  ".env",
73
73
  ".env.local",
@@ -102,7 +102,7 @@ function readmeContents(workspaceFileName) {
102
102
  `# API tests`,
103
103
  ``,
104
104
  `This folder is an [API Spector](https://github.com/testsmith-io/api-spector) workspace.`,
105
- `Everything here is plain JSON diff it, commit it, review it like any other code.`,
105
+ `Everything here is plain JSON - diff it, commit it, review it like any other code.`,
106
106
  ``,
107
107
  `## Layout`,
108
108
  ``,
@@ -144,7 +144,7 @@ function readmeContents(workspaceFileName) {
144
144
  `## A note on secrets`,
145
145
  ``,
146
146
  `Secret values (passwords, OAuth client secrets, API keys) are stored in your`,
147
- `OS keychain **not** in this folder. Environment files only reference the`,
147
+ `OS keychain - **not** in this folder. Environment files only reference the`,
148
148
  `keychain entry by name, so it's safe to commit them.`,
149
149
  ``
150
150
  ].join("\n");
@@ -223,7 +223,7 @@ function registerFileHandlers(ipc) {
223
223
  const wsPath = result.filePaths[0];
224
224
  workspaceDir = path.dirname(wsPath);
225
225
  workspaceFile = wsPath;
226
- await requestCollection.loadGlobals(workspaceDir);
226
+ await requestExec.loadGlobals(workspaceDir);
227
227
  await saveLastWorkspacePath(wsPath);
228
228
  const raw = await promises.readFile(wsPath, "utf8");
229
229
  return { workspace: JSON.parse(raw), workspacePath: wsPath };
@@ -239,7 +239,7 @@ function registerFileHandlers(ipc) {
239
239
  if (result.canceled || !result.filePath) return null;
240
240
  workspaceDir = path.dirname(result.filePath);
241
241
  workspaceFile = result.filePath;
242
- await requestCollection.loadGlobals(workspaceDir);
242
+ await requestExec.loadGlobals(workspaceDir);
243
243
  await promises.mkdir(path.join(workspaceDir, "collections"), { recursive: true });
244
244
  await promises.mkdir(path.join(workspaceDir, "environments"), { recursive: true });
245
245
  await ensureGitignore(workspaceDir);
@@ -317,10 +317,10 @@ function registerFileHandlers(ipc) {
317
317
  await promises.writeFile(result.filePath, content, "utf8");
318
318
  return true;
319
319
  });
320
- handle.handleIpc(ipc, handle.IPC.globals.get, () => requestCollection.getGlobals());
320
+ handle.handleIpc(ipc, handle.IPC.globals.get, () => requestExec.getGlobals());
321
321
  handle.handleIpc(ipc, handle.IPC.globals.set, async (_e, patch) => {
322
- requestCollection.setGlobals(patch);
323
- await requestCollection.persistGlobals();
322
+ requestExec.setGlobals(patch);
323
+ await requestExec.persistGlobals();
324
324
  });
325
325
  handle.handleIpc(ipc, handle.IPC.file.closeWorkspace, async () => {
326
326
  workspaceDir = null;
@@ -342,7 +342,7 @@ function registerFileHandlers(ipc) {
342
342
  const workspace = JSON.parse(raw);
343
343
  workspaceDir = path.dirname(wsPath);
344
344
  workspaceFile = wsPath;
345
- await requestCollection.loadGlobals(workspaceDir);
345
+ await requestExec.loadGlobals(workspaceDir);
346
346
  return { workspace, workspacePath: wsPath };
347
347
  } catch {
348
348
  return null;
@@ -364,7 +364,7 @@ async function tryOpenWorkspaceInDir(dir) {
364
364
  const workspace = JSON.parse(raw);
365
365
  workspaceDir = dir;
366
366
  workspaceFile = wsPath;
367
- await requestCollection.loadGlobals(workspaceDir);
367
+ await requestExec.loadGlobals(workspaceDir);
368
368
  return { workspace, workspacePath: wsPath };
369
369
  } catch {
370
370
  return null;
@@ -383,7 +383,7 @@ function readStringField(obj, key) {
383
383
  function safeProxySummary(proxy) {
384
384
  if (!proxy?.url?.trim()) return "off";
385
385
  try {
386
- const normalized = requestCollection.buildProxyUri({ url: proxy.url });
386
+ const normalized = requestExec.buildProxyUri({ url: proxy.url });
387
387
  const parsed = new URL(normalized);
388
388
  const host = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname;
389
389
  const auth = proxy.auth ? "yes" : "no";
@@ -438,11 +438,11 @@ function registerRequestHandler(ipc) {
438
438
  tls,
439
439
  piiMaskPatterns = []
440
440
  } = payload;
441
- requestCollection.applyRequestDefaults(req);
441
+ requestExec.applyRequestDefaults(req);
442
442
  const start = Date.now();
443
- const liveGlobals = requestCollection.getGlobals();
443
+ const liveGlobals = requestExec.getGlobals();
444
444
  const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
445
- const envVars = await authBuilder.buildEnvVars(environment);
445
+ const envVars = await requestExec.buildEnvVars(environment);
446
446
  let localVars = {};
447
447
  const decryptionWarnings = [];
448
448
  if (environment) {
@@ -456,14 +456,14 @@ function registerRequestHandler(ipc) {
456
456
  }
457
457
  }
458
458
  }
459
- const dynamicVars = await authBuilder.buildDynamicVars();
460
- let vars = authBuilder.mergeVars(envVars, collectionVars, mergedGlobals, localVars, dynamicVars);
459
+ const dynamicVars = await requestExec.buildDynamicVars();
460
+ let vars = requestExec.mergeVars(envVars, collectionVars, mergedGlobals, localVars, dynamicVars);
461
461
  let preScriptMeta = { consoleOutput: [] };
462
462
  let updatedCollectionVars = { ...collectionVars };
463
463
  let updatedEnvVars = { ...envVars };
464
464
  let updatedGlobals = { ...mergedGlobals };
465
465
  if (req.preRequestScript?.trim()) {
466
- const result = await requestCollection.runScript(authBuilder.interpolate(req.preRequestScript, vars), {
466
+ const result = await requestExec.runScript(requestExec.interpolate(req.preRequestScript, vars), {
467
467
  envVars: { ...envVars },
468
468
  collectionVars: { ...collectionVars },
469
469
  globals: { ...mergedGlobals },
@@ -474,14 +474,14 @@ function registerRequestHandler(ipc) {
474
474
  updatedEnvVars = result.updatedEnvVars;
475
475
  updatedCollectionVars = result.updatedCollectionVars;
476
476
  updatedGlobals = result.updatedGlobals;
477
- requestCollection.patchGlobals(result.updatedGlobals);
478
- await requestCollection.persistGlobals();
479
- vars = authBuilder.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
477
+ requestExec.patchGlobals(result.updatedGlobals);
478
+ await requestExec.persistGlobals();
479
+ vars = requestExec.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
480
480
  }
481
481
  let response;
482
482
  let scriptResponse;
483
483
  let sentRequest = { method: req.method, url: "", headers: {} };
484
- const resolvedUrl = authBuilder.buildUrl(req.url, req.params, vars);
484
+ const resolvedUrl = requestExec.buildUrl(req.url, req.params, vars);
485
485
  const secretValues = /* @__PURE__ */ new Set();
486
486
  if (environment) {
487
487
  for (const v of environment.variables) {
@@ -512,8 +512,8 @@ function registerRequestHandler(ipc) {
512
512
  };
513
513
  }
514
514
  try {
515
- const dispatcher = await requestCollection.buildDispatcher(proxy, tls);
516
- const exchange = await requestCollection.performHttpExchange({
515
+ const dispatcher = await requestExec.buildDispatcher(proxy, tls);
516
+ const exchange = await requestExec.performHttpExchange({
517
517
  req,
518
518
  vars,
519
519
  resolvedUrl,
@@ -524,8 +524,8 @@ function registerRequestHandler(ipc) {
524
524
  sentRequest = sent;
525
525
  }
526
526
  });
527
- const maskedBody = requestCollection.maskPii(exchange.responseBody, piiMaskPatterns);
528
- const maskedHeaders = requestCollection.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
527
+ const maskedBody = requestExec.maskPii(exchange.responseBody, piiMaskPatterns);
528
+ const maskedHeaders = requestExec.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
529
529
  const bodySize = Buffer.byteLength(exchange.responseBody, "utf8");
530
530
  response = {
531
531
  status: exchange.status,
@@ -563,12 +563,12 @@ function registerRequestHandler(ipc) {
563
563
  };
564
564
  scriptResponse = response;
565
565
  }
566
- const schemaTestResults = !response.error ? requestCollection.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
566
+ const schemaTestResults = !response.error ? requestExec.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
567
567
  let postTestResults = [];
568
568
  let postConsole = [];
569
569
  let postError;
570
570
  if (req.postRequestScript?.trim() && !response.error) {
571
- const result = await requestCollection.runScript(authBuilder.interpolate(req.postRequestScript, vars), {
571
+ const result = await requestExec.runScript(requestExec.interpolate(req.postRequestScript, vars), {
572
572
  envVars: { ...updatedEnvVars },
573
573
  collectionVars: { ...updatedCollectionVars },
574
574
  globals: { ...updatedGlobals },
@@ -584,15 +584,15 @@ function registerRequestHandler(ipc) {
584
584
  updatedCollectionVars = result.updatedCollectionVars;
585
585
  updatedGlobals = result.updatedGlobals;
586
586
  localVars = result.updatedLocalVars;
587
- requestCollection.patchGlobals(result.updatedGlobals);
588
- await requestCollection.persistGlobals();
587
+ requestExec.patchGlobals(result.updatedGlobals);
588
+ await requestExec.persistGlobals();
589
589
  }
590
590
  const combinedTestResults = [...schemaTestResults, ...postTestResults];
591
591
  if (!response.error && response.status >= 400 && combinedTestResults.length === 0) {
592
592
  combinedTestResults.push({
593
593
  name: `HTTP status ${response.status} ${response.statusText}`.trim(),
594
594
  passed: false,
595
- error: `Request returned ${response.status} no assertion was defined to verify the status code.`
595
+ error: `Request returned ${response.status} - no assertion was defined to verify the status code.`
596
596
  });
597
597
  }
598
598
  const scriptResult = {
@@ -610,9 +610,9 @@ function registerRequestHandler(ipc) {
610
610
  });
611
611
  handle.handleIpc(ipc, handle.IPC.script.runHook, async (_e, payload) => {
612
612
  const { script, envVars, collectionVars, globals } = payload;
613
- const result = await requestCollection.runScript(script, { envVars, collectionVars, globals, localVars: {} });
614
- requestCollection.patchGlobals(result.updatedGlobals);
615
- await requestCollection.persistGlobals();
613
+ const result = await requestExec.runScript(script, { envVars, collectionVars, globals, localVars: {} });
614
+ requestExec.patchGlobals(result.updatedGlobals);
615
+ await requestExec.persistGlobals();
616
616
  return {
617
617
  updatedEnvVars: result.updatedEnvVars,
618
618
  updatedCollectionVars: result.updatedCollectionVars,
@@ -1015,7 +1015,7 @@ function buildAuth(security, securitySchemes) {
1015
1015
  }
1016
1016
  return { type: "none" };
1017
1017
  }
1018
- const HTTP_METHODS$1 = ["get", "post", "put", "patch", "delete", "head", "options"];
1018
+ const HTTP_METHODS$1 = ["get", "post", "put", "patch", "delete", "head", "options", "query"];
1019
1019
  function buildCollection(spec) {
1020
1020
  const info = spec.info ?? {};
1021
1021
  const servers = spec.servers ?? [{}];
@@ -1262,7 +1262,7 @@ function parseKv(blockContent) {
1262
1262
  }
1263
1263
  return result;
1264
1264
  }
1265
- const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
1265
+ const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "query"];
1266
1266
  function parseBruFile(content, fileName) {
1267
1267
  const meta = parseKv(extractBlock(content, "meta"));
1268
1268
  let method = "GET";
@@ -1375,7 +1375,7 @@ async function importBruno(filePath) {
1375
1375
  requests
1376
1376
  };
1377
1377
  }
1378
- const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
1378
+ const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
1379
1379
  const HTTP_TO_SPECTOR = {
1380
1380
  $guid: "$uuid",
1381
1381
  $randomInt: "$randomInt",
@@ -1734,6 +1734,13 @@ function renderTree(paths) {
1734
1734
  }
1735
1735
  return [".", ...render(root)].join("\n");
1736
1736
  }
1737
+ const PLAYWRIGHT_VERBS = ["get", "post", "put", "patch", "delete", "head"];
1738
+ const SUPERTEST_VERBS = ["get", "post", "put", "patch", "delete", "head", "options"];
1739
+ function restAssuredCall(method, pathArg) {
1740
+ const verbs = ["get", "post", "put", "patch", "delete", "head", "options"];
1741
+ return verbs.includes(method) ? `.${method}(${pathArg})` : `.request("${method.toUpperCase()}", ${pathArg})`;
1742
+ }
1743
+ const ROBOT_REQUESTS_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
1737
1744
  function safeName(name) {
1738
1745
  return name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1739
1746
  }
@@ -1774,14 +1781,14 @@ function buildNameMap(root, requests) {
1774
1781
  function buildVariablesFile(environment) {
1775
1782
  const lines = ["*** Variables ***"];
1776
1783
  if (!environment) {
1777
- lines.push("# No environment add your variables here");
1784
+ lines.push("# No environment - add your variables here");
1778
1785
  lines.push("${BASE_URL} http://localhost:8080");
1779
1786
  return lines.join("\n") + "\n";
1780
1787
  }
1781
1788
  for (const v of environment.variables) {
1782
1789
  if (!v.enabled) continue;
1783
1790
  if (v.secret) {
1784
- lines.push(`# ${envVar(v.key)} stored in OS keychain, never hardcoded`);
1791
+ lines.push(`# ${envVar(v.key)} - stored in OS keychain, never hardcoded`);
1785
1792
  } else {
1786
1793
  lines.push(`${robotVar(v.key)} ${v.value}`);
1787
1794
  }
@@ -1817,7 +1824,13 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
1817
1824
  const url = interpolate(req.url, varMap);
1818
1825
  const method = req.method.charAt(0) + req.method.slice(1).toLowerCase();
1819
1826
  lines.push(kwName);
1820
- lines.push(` [Documentation] Hook: ${req.hookType} ${req.name}`);
1827
+ lines.push(` [Documentation] Hook: ${req.hookType} - ${req.name}`);
1828
+ if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
1829
+ lines.push(` Log ${req.method} is not supported by robotframework-requests - hook skipped WARN`);
1830
+ lines.push(` RETURN \${None}`);
1831
+ lines.push("");
1832
+ continue;
1833
+ }
1821
1834
  const { body } = req;
1822
1835
  if (hasBody(req) && body.mode === "json" && body.json) {
1823
1836
  const bodyPairs = jsonToRfDictPairs(body.json, varMap);
@@ -1853,6 +1866,12 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
1853
1866
  const url = interpolate(req.url, varMap);
1854
1867
  lines.push(kwName);
1855
1868
  lines.push(` [Documentation] ${req.description || req.name}`);
1869
+ if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
1870
+ lines.push(` Log ${req.method} is not supported by robotframework-requests - request skipped WARN`);
1871
+ lines.push(` RETURN \${None}`);
1872
+ lines.push("");
1873
+ continue;
1874
+ }
1856
1875
  const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
1857
1876
  const effectiveAuth = resolveEffectiveAuth(req, inherited);
1858
1877
  const allHeaders = mergeHeaders(req, inherited);
@@ -1996,7 +2015,7 @@ function buildTestSuite(collection, environment, nameMap) {
1996
2015
  }
1997
2016
  function buildReadme$6(collectionName, filePaths) {
1998
2017
  const tree = renderTree(filePaths);
1999
- return `# ${collectionName} API Tests (Robot Framework)
2018
+ return `# ${collectionName} - API Tests (Robot Framework)
2000
2019
 
2001
2020
  ## Project structure
2002
2021
 
@@ -2005,7 +2024,7 @@ ${tree}
2005
2024
  \`\`\`
2006
2025
 
2007
2026
  > Secrets are read from OS environment variables (e.g. \`%{API_TOKEN}\`).
2008
- > Never hardcode secrets export them in your shell or CI environment.
2027
+ > Never hardcode secrets - export them in your shell or CI environment.
2009
2028
 
2010
2029
  ## Setup
2011
2030
 
@@ -2084,12 +2103,15 @@ function buildHookLines$1(req, sharedVars) {
2084
2103
  } catch {
2085
2104
  }
2086
2105
  }
2106
+ const nativeVerb = PLAYWRIGHT_VERBS.includes(method);
2107
+ if (!nativeVerb) optionParts.unshift(`method: '${req.method}'`);
2087
2108
  const opts = optionParts.length ? `, { ${optionParts.join(", ")} }` : "";
2109
+ const hookCall = nativeVerb ? `request.${method}(${pathExpr}${opts})` : `request.fetch(${pathExpr}${opts})`;
2088
2110
  const lines = [];
2089
2111
  lines.push(` // ${req.name}`);
2090
2112
  const parsed = parsePostScript(req.postRequestScript);
2091
2113
  if (parsed.extractions.length > 0) {
2092
- lines.push(` const hookResponse = await request.${method}(${pathExpr}${opts});`);
2114
+ lines.push(` const hookResponse = await ${hookCall};`);
2093
2115
  lines.push(` const hookJson = await hookResponse.json();`);
2094
2116
  for (const e of parsed.extractions) {
2095
2117
  const jsonPath = e.accessor.replace(/^json\.?/, "");
@@ -2099,7 +2121,7 @@ function buildHookLines$1(req, sharedVars) {
2099
2121
  lines.push(` ${varName} = String(${expr});`);
2100
2122
  }
2101
2123
  } else {
2102
- lines.push(` await request.${method}(${pathExpr}${opts});`);
2124
+ lines.push(` await ${hookCall};`);
2103
2125
  }
2104
2126
  return lines;
2105
2127
  }
@@ -2199,13 +2221,16 @@ ${lines.join("\n")}
2199
2221
  optionParts.push(` data: \`${interpolateEnvVars(req.body.json, sharedVars)}\``);
2200
2222
  }
2201
2223
  }
2224
+ const nativeVerb = PLAYWRIGHT_VERBS.includes(method);
2225
+ if (!nativeVerb) optionParts.unshift(` method: '${req.method}'`);
2202
2226
  const optionsStr = optionParts.length ? `, {
2203
2227
  ${optionParts.join(",\n")},
2204
2228
  }` : "";
2229
+ const callExpr = nativeVerb ? `request.${method}(${pathExpr}${optionsStr})` : `request.fetch(${pathExpr}${optionsStr})`;
2205
2230
  const parsed = parsePostScript(req.postRequestScript);
2206
2231
  const lines = [
2207
2232
  ` test('${testName}', async ({ request }) => {`,
2208
- ` const response = await request.${method}(${pathExpr}${optionsStr});`
2233
+ ` const response = await ${callExpr};`
2209
2234
  ];
2210
2235
  const needsJson = parsed.assertions.some((a) => a.accessor.startsWith("json")) || parsed.extractions.length > 0;
2211
2236
  if (needsJson) {
@@ -2284,7 +2309,7 @@ function buildPackageJson$3(collectionName) {
2284
2309
  }
2285
2310
  function buildReadme$5(collectionName, filePaths) {
2286
2311
  const tree = renderTree([...filePaths, ".env.local"]);
2287
- return `# ${collectionName} API Tests (Playwright TypeScript)
2312
+ return `# ${collectionName} - API Tests (Playwright TypeScript)
2288
2313
 
2289
2314
  ## Project structure
2290
2315
 
@@ -2292,7 +2317,7 @@ function buildReadme$5(collectionName, filePaths) {
2292
2317
  ${tree}
2293
2318
  \`\`\`
2294
2319
 
2295
- > \`.env.local\` is git-ignored fill in your secrets before running.
2320
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2296
2321
 
2297
2322
  ## Setup
2298
2323
 
@@ -2364,11 +2389,14 @@ function buildHookLines(req, sharedVars) {
2364
2389
  } catch {
2365
2390
  }
2366
2391
  }
2392
+ const nativeVerb = PLAYWRIGHT_VERBS.includes(method);
2393
+ if (!nativeVerb) optParts.unshift(`method: '${req.method}'`);
2367
2394
  const opts = optParts.length ? `, { ${optParts.join(", ")} }` : "";
2395
+ const hookCall = nativeVerb ? `request.${method}(${pathExpr}${opts})` : `request.fetch(${pathExpr}${opts})`;
2368
2396
  const lines = [` // ${req.name}`];
2369
2397
  const parsed = parsePostScript(req.postRequestScript);
2370
2398
  if (parsed.extractions.length > 0) {
2371
- lines.push(` const hookResponse = await request.${method}(${pathExpr}${opts});`);
2399
+ lines.push(` const hookResponse = await ${hookCall};`);
2372
2400
  lines.push(` const hookJson = await hookResponse.json();`);
2373
2401
  for (const e of parsed.extractions) {
2374
2402
  const jp = e.accessor.replace(/^json\.?/, "");
@@ -2378,7 +2406,7 @@ function buildHookLines(req, sharedVars) {
2378
2406
  lines.push(` ${varName} = String(${expr});`);
2379
2407
  }
2380
2408
  } else {
2381
- lines.push(` await request.${method}(${pathExpr}${opts});`);
2409
+ lines.push(` await ${hookCall};`);
2382
2410
  }
2383
2411
  return lines;
2384
2412
  }
@@ -2476,13 +2504,16 @@ ${lines.join("\n")}
2476
2504
  optionParts.push(` data: \`${interpolateEnvVars(req.body.json, sharedVars)}\``);
2477
2505
  }
2478
2506
  }
2507
+ const nativeVerb = PLAYWRIGHT_VERBS.includes(method);
2508
+ if (!nativeVerb) optionParts.unshift(` method: '${req.method}'`);
2479
2509
  const optionsStr = optionParts.length ? `, {
2480
2510
  ${optionParts.join(",\n")},
2481
2511
  }` : "";
2512
+ const callExpr = nativeVerb ? `request.${method}(${pathExpr}${optionsStr})` : `request.fetch(${pathExpr}${optionsStr})`;
2482
2513
  const parsed = parsePostScript(req.postRequestScript);
2483
2514
  const lines = [
2484
2515
  ` test('${testName}', async ({ request }) => {`,
2485
- ` const response = await request.${method}(${pathExpr}${optionsStr});`
2516
+ ` const response = await ${callExpr};`
2486
2517
  ];
2487
2518
  const needsJson = parsed.assertions.some((a) => a.accessor.startsWith("json")) || parsed.extractions.length > 0;
2488
2519
  if (needsJson) {
@@ -2551,7 +2582,7 @@ function buildPackageJson$2(collectionName) {
2551
2582
  }
2552
2583
  function buildReadme$4(collectionName, filePaths) {
2553
2584
  const tree = renderTree([...filePaths, ".env.local"]);
2554
- return `# ${collectionName} API Tests (Playwright JavaScript)
2585
+ return `# ${collectionName} - API Tests (Playwright JavaScript)
2555
2586
 
2556
2587
  ## Project structure
2557
2588
 
@@ -2559,7 +2590,7 @@ function buildReadme$4(collectionName, filePaths) {
2559
2590
  ${tree}
2560
2591
  \`\`\`
2561
2592
 
2562
- > \`.env.local\` is git-ignored fill in your secrets before running.
2593
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2563
2594
 
2564
2595
  ## Setup
2565
2596
 
@@ -2643,6 +2674,10 @@ function buildTestFile$1(folderName, folder, collection) {
2643
2674
  function buildSupertestHookLines(h) {
2644
2675
  const lines = [` // ${h.name}`];
2645
2676
  const method = h.method.toLowerCase();
2677
+ if (!SUPERTEST_VERBS.includes(method)) {
2678
+ lines.push(` // ${h.method} (RFC 10008) is not supported by supertest -- hook skipped`);
2679
+ return lines;
2680
+ }
2646
2681
  const path2 = h.url.replace(/^https?:\/\/[^/]+/, "") || "/";
2647
2682
  const parsed = parsePostScript(h.postRequestScript);
2648
2683
  if (parsed.extractions.length > 0) {
@@ -2687,6 +2722,13 @@ ${lines.join("\n")}
2687
2722
  const allHeaders = mergeHeaders(req, inherited);
2688
2723
  const enabledParams = req.params.filter((p) => p.enabled && p.key);
2689
2724
  const lines = [];
2725
+ if (!SUPERTEST_VERBS.includes(method)) {
2726
+ lines.push(` // ${req.method} (RFC 10008) is not supported by supertest`);
2727
+ lines.push(` it.skip('${nameMap.get(reqId)} [${req.method} unsupported]', () => {});`);
2728
+ lines.push("");
2729
+ tests.push(...lines);
2730
+ continue;
2731
+ }
2690
2732
  lines.push(` it('${nameMap.get(reqId)}', async () => {`);
2691
2733
  lines.push(` const res = await api`);
2692
2734
  lines.push(` .${method}(\`${path2}\`)`);
@@ -2800,7 +2842,7 @@ function buildTsConfig() {
2800
2842
  }
2801
2843
  function buildReadme$3(collectionName, filePaths) {
2802
2844
  const tree = renderTree([...filePaths, ".env.local"]);
2803
- return `# ${collectionName} API Tests (Supertest + Jest TypeScript)
2845
+ return `# ${collectionName} - API Tests (Supertest + Jest TypeScript)
2804
2846
 
2805
2847
  ## Project structure
2806
2848
 
@@ -2808,7 +2850,7 @@ function buildReadme$3(collectionName, filePaths) {
2808
2850
  ${tree}
2809
2851
  \`\`\`
2810
2852
 
2811
- > \`.env.local\` is git-ignored fill in your secrets before running.
2853
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2812
2854
 
2813
2855
  ## Setup
2814
2856
 
@@ -2896,6 +2938,10 @@ function buildTestFile(folderName, folder, collection) {
2896
2938
  function buildJsHookLines(h) {
2897
2939
  const lines = [` // ${h.name}`];
2898
2940
  const method = h.method.toLowerCase();
2941
+ if (!SUPERTEST_VERBS.includes(method)) {
2942
+ lines.push(` // ${h.method} (RFC 10008) is not supported by supertest -- hook skipped`);
2943
+ return lines;
2944
+ }
2899
2945
  const path2 = h.url.replace(/^https?:\/\/[^/]+/, "") || "/";
2900
2946
  const parsed = parsePostScript(h.postRequestScript);
2901
2947
  if (parsed.extractions.length > 0) {
@@ -2940,6 +2986,13 @@ ${lines.join("\n")}
2940
2986
  const allHeaders = mergeHeaders(req, inherited);
2941
2987
  const enabledParams = req.params.filter((p) => p.enabled && p.key);
2942
2988
  const lines = [];
2989
+ if (!SUPERTEST_VERBS.includes(method)) {
2990
+ lines.push(` // ${req.method} (RFC 10008) is not supported by supertest`);
2991
+ lines.push(` it.skip('${nameMap.get(reqId)} [${req.method} unsupported]', () => {});`);
2992
+ lines.push("");
2993
+ tests.push(...lines);
2994
+ continue;
2995
+ }
2943
2996
  lines.push(` it('${nameMap.get(reqId)}', async () => {`);
2944
2997
  lines.push(` const res = await api`);
2945
2998
  lines.push(` .${method}(\`${path2}\`)`);
@@ -3035,7 +3088,7 @@ function buildPackageJson(collectionName) {
3035
3088
  }
3036
3089
  function buildReadme$2(collectionName, filePaths) {
3037
3090
  const tree = renderTree([...filePaths, ".env.local"]);
3038
- return `# ${collectionName} API Tests (Supertest + Jest JavaScript)
3091
+ return `# ${collectionName} - API Tests (Supertest + Jest JavaScript)
3039
3092
 
3040
3093
  ## Project structure
3041
3094
 
@@ -3043,7 +3096,7 @@ function buildReadme$2(collectionName, filePaths) {
3043
3096
  ${tree}
3044
3097
  \`\`\`
3045
3098
 
3046
- > \`.env.local\` is git-ignored fill in your secrets before running.
3099
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
3047
3100
 
3048
3101
  ## Setup
3049
3102
 
@@ -3234,7 +3287,7 @@ function buildTestClass(folderName, folder, collection) {
3234
3287
  const escaped = h.body.json.replace(/"/g, '\\"').replace(/\n/g, "\\n");
3235
3288
  lines.push(` .body("${escaped}")`);
3236
3289
  }
3237
- lines.push(` .when().${method}("${path2}");`);
3290
+ lines.push(` .when()${restAssuredCall(method, `"${path2}"`)};`);
3238
3291
  for (const e of parsed.extractions) {
3239
3292
  const jp = accessorToJsonPath(e.accessor);
3240
3293
  const varName = toEnvVar(e.varName);
@@ -3242,7 +3295,7 @@ function buildTestClass(folderName, folder, collection) {
3242
3295
  lines.push(` ${varName} = hookResponse.jsonPath().getString("${jp}");`);
3243
3296
  }
3244
3297
  } else {
3245
- lines.push(` given().spec(requestSpec).when().${method}("${path2}");`);
3298
+ lines.push(` given().spec(requestSpec).when()${restAssuredCall(method, `"${path2}"`)};`);
3246
3299
  }
3247
3300
  return lines;
3248
3301
  }
@@ -3318,7 +3371,7 @@ ${lines.join("\n")}
3318
3371
  }
3319
3372
  }
3320
3373
  lines.push(` .when()`);
3321
- lines.push(` .${method}(${javaPath})`);
3374
+ lines.push(` ${restAssuredCall(method, javaPath)}`);
3322
3375
  lines.push(` .then()`);
3323
3376
  const parsed = parsePostScript(req.postRequestScript);
3324
3377
  if (parsed.assertions.length > 0) {
@@ -3374,7 +3427,7 @@ ${methods.join("\n\n")}
3374
3427
  }
3375
3428
  function buildReadme$1(collectionName, filePaths) {
3376
3429
  const tree = renderTree(filePaths);
3377
- return `# ${collectionName} API Tests (REST Assured + JUnit 5)
3430
+ return `# ${collectionName} - API Tests (REST Assured + JUnit 5)
3378
3431
 
3379
3432
  ## Project structure
3380
3433
 
@@ -3774,10 +3827,10 @@ ${scenarios.join("\n\n")}
3774
3827
  }
3775
3828
  function buildReadme(collectionName, filePaths) {
3776
3829
  const tree = renderTree(filePaths);
3777
- return `# ${collectionName} API Tests (Karate + JUnit 5)
3830
+ return `# ${collectionName} - API Tests (Karate + JUnit 5)
3778
3831
 
3779
3832
  Karate is a BDD-flavoured API test framework that uses Gherkin feature files
3780
- (no glue code) see https://docs.karatelabs.io for the full reference.
3833
+ (no glue code) - see https://docs.karatelabs.io for the full reference.
3781
3834
 
3782
3835
  ## Project structure
3783
3836
 
@@ -3982,17 +4035,17 @@ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
3982
4035
  function registerRunnerHandler(ipc) {
3983
4036
  handle.handleIpc(ipc, handle.IPC.runner.start, async (event, payload) => {
3984
4037
  const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
3985
- const envVars = await authBuilder.buildEnvVars(environment);
3986
- const liveGlobals = requestCollection.getGlobals();
4038
+ const envVars = await requestExec.buildEnvVars(environment);
4039
+ const liveGlobals = requestExec.getGlobals();
3987
4040
  const globals = { ...payloadGlobals, ...liveGlobals };
3988
- const dispatcher = await requestCollection.buildDispatcher(proxy, tls);
4041
+ const dispatcher = await requestExec.buildDispatcher(proxy, tls);
3989
4042
  const summary = { total: items.length, passed: 0, failed: 0, errors: 0, skipped: 0, durationMs: 0 };
3990
4043
  const totalStart = Date.now();
3991
4044
  let runEnvVars = { ...envVars };
3992
4045
  let runCollectionVars = {};
3993
4046
  let runGlobals = { ...globals };
3994
4047
  let runLocalVars = {};
3995
- const skipTracker = new requestCollection.HookSkipTracker();
4048
+ const skipTracker = new requestExec.HookSkipTracker();
3996
4049
  for (const item of items) {
3997
4050
  const skipReason = skipTracker.shouldSkip(item);
3998
4051
  if (skipReason) {
@@ -4022,7 +4075,7 @@ function registerRunnerHandler(ipc) {
4022
4075
  scopePath: item.scopePath
4023
4076
  };
4024
4077
  event.sender.send(handle.IPC.runner.progress, { requestId: item.request.id, ...runningUpdate });
4025
- const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestCollection.executeRunnerRequest({
4078
+ const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestExec.executeRunnerRequest({
4026
4079
  req: item.request,
4027
4080
  collectionVars: { ...item.collectionVars, ...runCollectionVars },
4028
4081
  envVars: runEnvVars,
@@ -4089,14 +4142,14 @@ function registerOAuth2Handlers(ipc) {
4089
4142
  handle.handleIpc(ipc, handle.IPC.oauth2.startFlow, async (_e, auth, vars) => {
4090
4143
  const port = auth.oauth2RedirectPort ?? 9876;
4091
4144
  const redirectUri = `http://localhost:${port}/callback`;
4092
- const authUrl = authBuilder.interpolate(auth.oauth2AuthUrl ?? "", vars);
4093
- const tokenUrl = authBuilder.interpolate(auth.oauth2TokenUrl ?? "", vars);
4094
- const clientId = authBuilder.interpolate(auth.oauth2ClientId ?? "", vars);
4145
+ const authUrl = requestExec.interpolate(auth.oauth2AuthUrl ?? "", vars);
4146
+ const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
4147
+ const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
4095
4148
  let clientSecret = auth.oauth2ClientSecret ?? "";
4096
4149
  if (!clientSecret && auth.oauth2ClientSecretRef) {
4097
- clientSecret = await authBuilder.getSecret(auth.oauth2ClientSecretRef) ?? "";
4150
+ clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
4098
4151
  }
4099
- clientSecret = authBuilder.interpolate(clientSecret, vars);
4152
+ clientSecret = requestExec.interpolate(clientSecret, vars);
4100
4153
  if (!authUrl) throw new Error("OAuth 2.0: authUrl is required for authorization_code flow.");
4101
4154
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for authorization_code flow.");
4102
4155
  if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
@@ -4171,13 +4224,13 @@ function registerOAuth2Handlers(ipc) {
4171
4224
  };
4172
4225
  });
4173
4226
  handle.handleIpc(ipc, handle.IPC.oauth2.refreshToken, async (_e, auth, vars, refreshToken) => {
4174
- const tokenUrl = authBuilder.interpolate(auth.oauth2TokenUrl ?? "", vars);
4175
- const clientId = authBuilder.interpolate(auth.oauth2ClientId ?? "", vars);
4227
+ const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
4228
+ const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
4176
4229
  let clientSecret = auth.oauth2ClientSecret ?? "";
4177
4230
  if (!clientSecret && auth.oauth2ClientSecretRef) {
4178
- clientSecret = await authBuilder.getSecret(auth.oauth2ClientSecretRef) ?? "";
4231
+ clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
4179
4232
  }
4180
- clientSecret = authBuilder.interpolate(clientSecret, vars);
4233
+ clientSecret = requestExec.interpolate(clientSecret, vars);
4181
4234
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for refresh.");
4182
4235
  const { fetch: nodeFetch } = await import("undici");
4183
4236
  const params = new URLSearchParams();
@@ -4585,7 +4638,7 @@ function inferSchemaFromJson(json) {
4585
4638
  }
4586
4639
  async function resolveSnapshotSpec(relPath) {
4587
4640
  const dir = getWorkspaceDir();
4588
- if (!dir) throw new Error("No workspace open cannot resolve snapshot.");
4641
+ if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
4589
4642
  const snap = await snapshots.loadSnapshot(dir, relPath);
4590
4643
  const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
4591
4644
  await promises.writeFile(tmp, snap.spec, "utf8");
@@ -4605,7 +4658,7 @@ function registerContractHandlers(ipc) {
4605
4658
  case "consumer":
4606
4659
  return snapshots.runConsumerContracts(requests, envVars, collectionVars);
4607
4660
  case "provider":
4608
- return snapshots.runProviderVerification(requests, envVars, specUrl, specPath, requestBaseUrl);
4661
+ return snapshots.runProviderVerification(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
4609
4662
  case "provider-live":
4610
4663
  return snapshots.runLiveProviderVerification(requests, envVars, collectionVars, providerBaseUrl, stateHandlerUrl);
4611
4664
  case "bidirectional":
@@ -4628,7 +4681,7 @@ function registerContractHandlers(ipc) {
4628
4681
  });
4629
4682
  handle.handleIpc(ipc, handle.IPC.contract.captureSnapshot, async (_e, opts) => {
4630
4683
  const dir = getWorkspaceDir();
4631
- if (!dir) throw new Error("No workspace open cannot capture snapshot.");
4684
+ if (!dir) throw new Error("No workspace open - cannot capture snapshot.");
4632
4685
  const snapshot = await snapshots.captureSnapshot(dir, opts);
4633
4686
  const relPath = snapshots.relPathOf(snapshot);
4634
4687
  if (!relPath) throw new Error("Snapshot created but relPath was not attached.");
@@ -4649,6 +4702,24 @@ function registerContractHandlers(ipc) {
4649
4702
  if (!dir) return;
4650
4703
  await snapshots.deleteSnapshot(dir, relPath);
4651
4704
  });
4705
+ handle.handleIpc(ipc, handle.IPC.contract.fuzz, async (_e, payload) => {
4706
+ let { specUrl, specPath } = payload;
4707
+ if (payload.specSnapshotRelPath) {
4708
+ const resolved = await resolveSnapshotSpec(payload.specSnapshotRelPath);
4709
+ specPath = resolved.specPath;
4710
+ specUrl = void 0;
4711
+ }
4712
+ return snapshots.runFuzz({ ...payload, specUrl, specPath });
4713
+ });
4714
+ handle.handleIpc(ipc, handle.IPC.contract.recordResult, async (_e, opts) => {
4715
+ const dir = getWorkspaceDir();
4716
+ if (!dir) throw new Error("No workspace open — cannot record result.");
4717
+ const pacticipant = opts.pacticipant?.trim();
4718
+ const version = opts.version?.trim();
4719
+ if (!pacticipant || !version) throw new Error("Pacticipant and version are required.");
4720
+ const file = await snapshots.recordResult(dir, pacticipant, version, opts.report, (/* @__PURE__ */ new Date()).toISOString());
4721
+ return { file };
4722
+ });
4652
4723
  }
4653
4724
  function git() {
4654
4725
  const dir = getWorkspaceDir();
@@ -4930,10 +5001,10 @@ function createWindow() {
4930
5001
  }
4931
5002
  electron.app.whenReady().then(async () => {
4932
5003
  if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
4933
- await authBuilder.initSecretStore(electron.app.getPath("userData"));
5004
+ await requestExec.initSecretStore(electron.app.getPath("userData"));
4934
5005
  registerFileHandlers(electron.ipcMain);
4935
5006
  registerRequestHandler(electron.ipcMain);
4936
- authBuilder.registerSecretHandlers(electron.ipcMain);
5007
+ requestExec.registerSecretHandlers(electron.ipcMain);
4937
5008
  registerImportHandlers(electron.ipcMain);
4938
5009
  registerGenerateHandlers(electron.ipcMain);
4939
5010
  registerRunnerHandler(electron.ipcMain);