@testsmith/api-spector 0.3.5 → 0.3.7
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/bin/cli.js +11 -7
- package/out/main/agents.js +1 -1
- package/out/main/chunks/environments-iM3SUM-4.js +28 -0
- package/out/main/chunks/{handle-C0IQL-Vl.js → handle-BCnNIZZr.js} +6 -2
- package/out/main/chunks/{import-C5Ngq8hk.js → import-DUKaMYGO.js} +4 -4
- package/out/main/chunks/{recorder-DFxJgn9c.js → recorder-0Ij921El.js} +1 -1
- package/out/main/chunks/request-collection-CBuPjBwa.js +118 -0
- package/out/main/chunks/{request-collection-8TOVNXE0.js → request-exec-AQD6cEgT.js} +602 -146
- package/out/main/chunks/snapshots-NGkGQNZ4.js +1658 -0
- package/out/main/chunks/{soap-handler-B9x_YCtj.js → soap-handler-h0dWIjOK.js} +3 -3
- package/out/main/contract.js +508 -91
- package/out/main/index.js +112 -77
- package/out/main/record.js +3 -3
- package/out/main/runner.js +23 -20
- package/out/main/wsdl.js +4 -4
- package/out/preload/index.js +10 -2
- package/out/renderer/assets/{index-BFxRlU1d.js → index-SXJZlbGN.js} +1342 -214
- package/out/renderer/assets/index-mDBOHLZ5.css +2 -0
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/readme.md +2 -2
- package/out/main/chunks/auth-builder-CUs9yzOF.js +0 -623
- package/out/main/chunks/snapshots-UFd3XgSS.js +0 -928
- package/out/renderer/assets/index-DuMAjIiI.css +0 -2
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-
|
|
25
|
+
const handle = require("./chunks/handle-BCnNIZZr.js");
|
|
26
26
|
const path = require("path");
|
|
27
27
|
const fs = require("fs");
|
|
28
28
|
const promises = require("fs/promises");
|
|
29
|
-
const
|
|
30
|
-
const authBuilder = require("./chunks/auth-builder-CUs9yzOF.js");
|
|
29
|
+
const requestExec = require("./chunks/request-exec-AQD6cEgT.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-
|
|
39
|
+
const soapHandler = require("./chunks/soap-handler-h0dWIjOK.js");
|
|
40
40
|
const os = require("os");
|
|
41
41
|
const crypto = require("crypto");
|
|
42
|
-
const snapshots = require("./chunks/snapshots-
|
|
42
|
+
const snapshots = require("./chunks/snapshots-NGkGQNZ4.js");
|
|
43
43
|
const simpleGit = require("simple-git");
|
|
44
|
-
const recorder = require("./chunks/recorder-
|
|
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
|
|
70
|
+
"# API Spector - never commit secrets",
|
|
71
71
|
"*.secrets",
|
|
72
72
|
".env",
|
|
73
73
|
".env.local",
|
|
@@ -91,6 +91,9 @@ const SPECTOR_GITIGNORE = [
|
|
|
91
91
|
"results.html",
|
|
92
92
|
"coverage/",
|
|
93
93
|
"",
|
|
94
|
+
"# Local session history (opt-in persistence)",
|
|
95
|
+
"history.json",
|
|
96
|
+
"",
|
|
94
97
|
"# OS / editor",
|
|
95
98
|
".DS_Store",
|
|
96
99
|
"Thumbs.db",
|
|
@@ -102,7 +105,7 @@ function readmeContents(workspaceFileName) {
|
|
|
102
105
|
`# API tests`,
|
|
103
106
|
``,
|
|
104
107
|
`This folder is an [API Spector](https://github.com/testsmith-io/api-spector) workspace.`,
|
|
105
|
-
`Everything here is plain JSON
|
|
108
|
+
`Everything here is plain JSON - diff it, commit it, review it like any other code.`,
|
|
106
109
|
``,
|
|
107
110
|
`## Layout`,
|
|
108
111
|
``,
|
|
@@ -144,7 +147,7 @@ function readmeContents(workspaceFileName) {
|
|
|
144
147
|
`## A note on secrets`,
|
|
145
148
|
``,
|
|
146
149
|
`Secret values (passwords, OAuth client secrets, API keys) are stored in your`,
|
|
147
|
-
`OS keychain
|
|
150
|
+
`OS keychain - **not** in this folder. Environment files only reference the`,
|
|
148
151
|
`keychain entry by name, so it's safe to commit them.`,
|
|
149
152
|
``
|
|
150
153
|
].join("\n");
|
|
@@ -223,7 +226,7 @@ function registerFileHandlers(ipc) {
|
|
|
223
226
|
const wsPath = result.filePaths[0];
|
|
224
227
|
workspaceDir = path.dirname(wsPath);
|
|
225
228
|
workspaceFile = wsPath;
|
|
226
|
-
await
|
|
229
|
+
await requestExec.loadGlobals(workspaceDir);
|
|
227
230
|
await saveLastWorkspacePath(wsPath);
|
|
228
231
|
const raw = await promises.readFile(wsPath, "utf8");
|
|
229
232
|
return { workspace: JSON.parse(raw), workspacePath: wsPath };
|
|
@@ -239,7 +242,7 @@ function registerFileHandlers(ipc) {
|
|
|
239
242
|
if (result.canceled || !result.filePath) return null;
|
|
240
243
|
workspaceDir = path.dirname(result.filePath);
|
|
241
244
|
workspaceFile = result.filePath;
|
|
242
|
-
await
|
|
245
|
+
await requestExec.loadGlobals(workspaceDir);
|
|
243
246
|
await promises.mkdir(path.join(workspaceDir, "collections"), { recursive: true });
|
|
244
247
|
await promises.mkdir(path.join(workspaceDir, "environments"), { recursive: true });
|
|
245
248
|
await ensureGitignore(workspaceDir);
|
|
@@ -294,6 +297,20 @@ function registerFileHandlers(ipc) {
|
|
|
294
297
|
if (err.code !== "ENOENT") throw err;
|
|
295
298
|
}
|
|
296
299
|
});
|
|
300
|
+
handle.handleIpc(ipc, handle.IPC.file.loadHistory, async () => {
|
|
301
|
+
if (!workspaceDir) return [];
|
|
302
|
+
try {
|
|
303
|
+
const raw = await promises.readFile(path.join(workspaceDir, "history.json"), "utf8");
|
|
304
|
+
const parsed = JSON.parse(raw);
|
|
305
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
306
|
+
} catch {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
handle.handleIpc(ipc, handle.IPC.file.saveHistory, async (_e, entries) => {
|
|
311
|
+
if (!workspaceDir) return;
|
|
312
|
+
await promises.writeFile(path.join(workspaceDir, "history.json"), JSON.stringify(entries, null, 2), "utf8");
|
|
313
|
+
});
|
|
297
314
|
handle.handleIpc(ipc, handle.IPC.dialog.pickDir, async () => {
|
|
298
315
|
const result = await electron.dialog.showOpenDialog({
|
|
299
316
|
title: "Select Output Directory",
|
|
@@ -317,10 +334,10 @@ function registerFileHandlers(ipc) {
|
|
|
317
334
|
await promises.writeFile(result.filePath, content, "utf8");
|
|
318
335
|
return true;
|
|
319
336
|
});
|
|
320
|
-
handle.handleIpc(ipc, handle.IPC.globals.get, () =>
|
|
337
|
+
handle.handleIpc(ipc, handle.IPC.globals.get, () => requestExec.getGlobals());
|
|
321
338
|
handle.handleIpc(ipc, handle.IPC.globals.set, async (_e, patch) => {
|
|
322
|
-
|
|
323
|
-
await
|
|
339
|
+
requestExec.setGlobals(patch);
|
|
340
|
+
await requestExec.persistGlobals();
|
|
324
341
|
});
|
|
325
342
|
handle.handleIpc(ipc, handle.IPC.file.closeWorkspace, async () => {
|
|
326
343
|
workspaceDir = null;
|
|
@@ -342,7 +359,7 @@ function registerFileHandlers(ipc) {
|
|
|
342
359
|
const workspace = JSON.parse(raw);
|
|
343
360
|
workspaceDir = path.dirname(wsPath);
|
|
344
361
|
workspaceFile = wsPath;
|
|
345
|
-
await
|
|
362
|
+
await requestExec.loadGlobals(workspaceDir);
|
|
346
363
|
return { workspace, workspacePath: wsPath };
|
|
347
364
|
} catch {
|
|
348
365
|
return null;
|
|
@@ -364,7 +381,7 @@ async function tryOpenWorkspaceInDir(dir) {
|
|
|
364
381
|
const workspace = JSON.parse(raw);
|
|
365
382
|
workspaceDir = dir;
|
|
366
383
|
workspaceFile = wsPath;
|
|
367
|
-
await
|
|
384
|
+
await requestExec.loadGlobals(workspaceDir);
|
|
368
385
|
return { workspace, workspacePath: wsPath };
|
|
369
386
|
} catch {
|
|
370
387
|
return null;
|
|
@@ -383,7 +400,7 @@ function readStringField(obj, key) {
|
|
|
383
400
|
function safeProxySummary(proxy) {
|
|
384
401
|
if (!proxy?.url?.trim()) return "off";
|
|
385
402
|
try {
|
|
386
|
-
const normalized =
|
|
403
|
+
const normalized = requestExec.buildProxyUri({ url: proxy.url });
|
|
387
404
|
const parsed = new URL(normalized);
|
|
388
405
|
const host = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname;
|
|
389
406
|
const auth = proxy.auth ? "yes" : "no";
|
|
@@ -438,11 +455,11 @@ function registerRequestHandler(ipc) {
|
|
|
438
455
|
tls,
|
|
439
456
|
piiMaskPatterns = []
|
|
440
457
|
} = payload;
|
|
441
|
-
|
|
458
|
+
requestExec.applyRequestDefaults(req);
|
|
442
459
|
const start = Date.now();
|
|
443
|
-
const liveGlobals =
|
|
460
|
+
const liveGlobals = requestExec.getGlobals();
|
|
444
461
|
const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
|
|
445
|
-
const envVars = await
|
|
462
|
+
const envVars = await requestExec.buildEnvVars(environment);
|
|
446
463
|
let localVars = {};
|
|
447
464
|
const decryptionWarnings = [];
|
|
448
465
|
if (environment) {
|
|
@@ -456,14 +473,14 @@ function registerRequestHandler(ipc) {
|
|
|
456
473
|
}
|
|
457
474
|
}
|
|
458
475
|
}
|
|
459
|
-
const dynamicVars = await
|
|
460
|
-
let vars =
|
|
476
|
+
const dynamicVars = await requestExec.buildDynamicVars();
|
|
477
|
+
let vars = requestExec.mergeVars(envVars, collectionVars, mergedGlobals, localVars, dynamicVars);
|
|
461
478
|
let preScriptMeta = { consoleOutput: [] };
|
|
462
479
|
let updatedCollectionVars = { ...collectionVars };
|
|
463
480
|
let updatedEnvVars = { ...envVars };
|
|
464
481
|
let updatedGlobals = { ...mergedGlobals };
|
|
465
482
|
if (req.preRequestScript?.trim()) {
|
|
466
|
-
const result = await
|
|
483
|
+
const result = await requestExec.runScript(requestExec.interpolate(req.preRequestScript, vars), {
|
|
467
484
|
envVars: { ...envVars },
|
|
468
485
|
collectionVars: { ...collectionVars },
|
|
469
486
|
globals: { ...mergedGlobals },
|
|
@@ -474,14 +491,14 @@ function registerRequestHandler(ipc) {
|
|
|
474
491
|
updatedEnvVars = result.updatedEnvVars;
|
|
475
492
|
updatedCollectionVars = result.updatedCollectionVars;
|
|
476
493
|
updatedGlobals = result.updatedGlobals;
|
|
477
|
-
|
|
478
|
-
await
|
|
479
|
-
vars =
|
|
494
|
+
requestExec.patchGlobals(result.updatedGlobals);
|
|
495
|
+
await requestExec.persistGlobals();
|
|
496
|
+
vars = requestExec.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
|
|
480
497
|
}
|
|
481
498
|
let response;
|
|
482
499
|
let scriptResponse;
|
|
483
500
|
let sentRequest = { method: req.method, url: "", headers: {} };
|
|
484
|
-
const resolvedUrl =
|
|
501
|
+
const resolvedUrl = requestExec.buildUrl(req.url, req.params, vars);
|
|
485
502
|
const secretValues = /* @__PURE__ */ new Set();
|
|
486
503
|
if (environment) {
|
|
487
504
|
for (const v of environment.variables) {
|
|
@@ -512,8 +529,8 @@ function registerRequestHandler(ipc) {
|
|
|
512
529
|
};
|
|
513
530
|
}
|
|
514
531
|
try {
|
|
515
|
-
const dispatcher = await
|
|
516
|
-
const exchange = await
|
|
532
|
+
const dispatcher = await requestExec.buildDispatcher(proxy, tls);
|
|
533
|
+
const exchange = await requestExec.performHttpExchange({
|
|
517
534
|
req,
|
|
518
535
|
vars,
|
|
519
536
|
resolvedUrl,
|
|
@@ -524,8 +541,8 @@ function registerRequestHandler(ipc) {
|
|
|
524
541
|
sentRequest = sent;
|
|
525
542
|
}
|
|
526
543
|
});
|
|
527
|
-
const maskedBody =
|
|
528
|
-
const maskedHeaders =
|
|
544
|
+
const maskedBody = requestExec.maskPii(exchange.responseBody, piiMaskPatterns);
|
|
545
|
+
const maskedHeaders = requestExec.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
|
|
529
546
|
const bodySize = Buffer.byteLength(exchange.responseBody, "utf8");
|
|
530
547
|
response = {
|
|
531
548
|
status: exchange.status,
|
|
@@ -563,12 +580,12 @@ function registerRequestHandler(ipc) {
|
|
|
563
580
|
};
|
|
564
581
|
scriptResponse = response;
|
|
565
582
|
}
|
|
566
|
-
const schemaTestResults = !response.error ?
|
|
583
|
+
const schemaTestResults = !response.error ? requestExec.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
|
|
567
584
|
let postTestResults = [];
|
|
568
585
|
let postConsole = [];
|
|
569
586
|
let postError;
|
|
570
587
|
if (req.postRequestScript?.trim() && !response.error) {
|
|
571
|
-
const result = await
|
|
588
|
+
const result = await requestExec.runScript(requestExec.interpolate(req.postRequestScript, vars), {
|
|
572
589
|
envVars: { ...updatedEnvVars },
|
|
573
590
|
collectionVars: { ...updatedCollectionVars },
|
|
574
591
|
globals: { ...updatedGlobals },
|
|
@@ -584,15 +601,15 @@ function registerRequestHandler(ipc) {
|
|
|
584
601
|
updatedCollectionVars = result.updatedCollectionVars;
|
|
585
602
|
updatedGlobals = result.updatedGlobals;
|
|
586
603
|
localVars = result.updatedLocalVars;
|
|
587
|
-
|
|
588
|
-
await
|
|
604
|
+
requestExec.patchGlobals(result.updatedGlobals);
|
|
605
|
+
await requestExec.persistGlobals();
|
|
589
606
|
}
|
|
590
607
|
const combinedTestResults = [...schemaTestResults, ...postTestResults];
|
|
591
608
|
if (!response.error && response.status >= 400 && combinedTestResults.length === 0) {
|
|
592
609
|
combinedTestResults.push({
|
|
593
610
|
name: `HTTP status ${response.status} ${response.statusText}`.trim(),
|
|
594
611
|
passed: false,
|
|
595
|
-
error: `Request returned ${response.status}
|
|
612
|
+
error: `Request returned ${response.status} - no assertion was defined to verify the status code.`
|
|
596
613
|
});
|
|
597
614
|
}
|
|
598
615
|
const scriptResult = {
|
|
@@ -610,9 +627,9 @@ function registerRequestHandler(ipc) {
|
|
|
610
627
|
});
|
|
611
628
|
handle.handleIpc(ipc, handle.IPC.script.runHook, async (_e, payload) => {
|
|
612
629
|
const { script, envVars, collectionVars, globals } = payload;
|
|
613
|
-
const result = await
|
|
614
|
-
|
|
615
|
-
await
|
|
630
|
+
const result = await requestExec.runScript(script, { envVars, collectionVars, globals, localVars: {} });
|
|
631
|
+
requestExec.patchGlobals(result.updatedGlobals);
|
|
632
|
+
await requestExec.persistGlobals();
|
|
616
633
|
return {
|
|
617
634
|
updatedEnvVars: result.updatedEnvVars,
|
|
618
635
|
updatedCollectionVars: result.updatedCollectionVars,
|
|
@@ -1781,14 +1798,14 @@ function buildNameMap(root, requests) {
|
|
|
1781
1798
|
function buildVariablesFile(environment) {
|
|
1782
1799
|
const lines = ["*** Variables ***"];
|
|
1783
1800
|
if (!environment) {
|
|
1784
|
-
lines.push("# No environment
|
|
1801
|
+
lines.push("# No environment - add your variables here");
|
|
1785
1802
|
lines.push("${BASE_URL} http://localhost:8080");
|
|
1786
1803
|
return lines.join("\n") + "\n";
|
|
1787
1804
|
}
|
|
1788
1805
|
for (const v of environment.variables) {
|
|
1789
1806
|
if (!v.enabled) continue;
|
|
1790
1807
|
if (v.secret) {
|
|
1791
|
-
lines.push(`# ${envVar(v.key)}
|
|
1808
|
+
lines.push(`# ${envVar(v.key)} - stored in OS keychain, never hardcoded`);
|
|
1792
1809
|
} else {
|
|
1793
1810
|
lines.push(`${robotVar(v.key)} ${v.value}`);
|
|
1794
1811
|
}
|
|
@@ -1824,9 +1841,9 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1824
1841
|
const url = interpolate(req.url, varMap);
|
|
1825
1842
|
const method = req.method.charAt(0) + req.method.slice(1).toLowerCase();
|
|
1826
1843
|
lines.push(kwName);
|
|
1827
|
-
lines.push(` [Documentation] Hook: ${req.hookType}
|
|
1844
|
+
lines.push(` [Documentation] Hook: ${req.hookType} - ${req.name}`);
|
|
1828
1845
|
if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
|
|
1829
|
-
lines.push(` Log ${req.method} is not supported by robotframework-requests
|
|
1846
|
+
lines.push(` Log ${req.method} is not supported by robotframework-requests - hook skipped WARN`);
|
|
1830
1847
|
lines.push(` RETURN \${None}`);
|
|
1831
1848
|
lines.push("");
|
|
1832
1849
|
continue;
|
|
@@ -1867,7 +1884,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1867
1884
|
lines.push(kwName);
|
|
1868
1885
|
lines.push(` [Documentation] ${req.description || req.name}`);
|
|
1869
1886
|
if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
|
|
1870
|
-
lines.push(` Log ${req.method} is not supported by robotframework-requests
|
|
1887
|
+
lines.push(` Log ${req.method} is not supported by robotframework-requests - request skipped WARN`);
|
|
1871
1888
|
lines.push(` RETURN \${None}`);
|
|
1872
1889
|
lines.push("");
|
|
1873
1890
|
continue;
|
|
@@ -2015,7 +2032,7 @@ function buildTestSuite(collection, environment, nameMap) {
|
|
|
2015
2032
|
}
|
|
2016
2033
|
function buildReadme$6(collectionName, filePaths) {
|
|
2017
2034
|
const tree = renderTree(filePaths);
|
|
2018
|
-
return `# ${collectionName}
|
|
2035
|
+
return `# ${collectionName} - API Tests (Robot Framework)
|
|
2019
2036
|
|
|
2020
2037
|
## Project structure
|
|
2021
2038
|
|
|
@@ -2024,7 +2041,7 @@ ${tree}
|
|
|
2024
2041
|
\`\`\`
|
|
2025
2042
|
|
|
2026
2043
|
> Secrets are read from OS environment variables (e.g. \`%{API_TOKEN}\`).
|
|
2027
|
-
> Never hardcode secrets
|
|
2044
|
+
> Never hardcode secrets - export them in your shell or CI environment.
|
|
2028
2045
|
|
|
2029
2046
|
## Setup
|
|
2030
2047
|
|
|
@@ -2309,7 +2326,7 @@ function buildPackageJson$3(collectionName) {
|
|
|
2309
2326
|
}
|
|
2310
2327
|
function buildReadme$5(collectionName, filePaths) {
|
|
2311
2328
|
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2312
|
-
return `# ${collectionName}
|
|
2329
|
+
return `# ${collectionName} - API Tests (Playwright TypeScript)
|
|
2313
2330
|
|
|
2314
2331
|
## Project structure
|
|
2315
2332
|
|
|
@@ -2317,7 +2334,7 @@ function buildReadme$5(collectionName, filePaths) {
|
|
|
2317
2334
|
${tree}
|
|
2318
2335
|
\`\`\`
|
|
2319
2336
|
|
|
2320
|
-
> \`.env.local\` is git-ignored
|
|
2337
|
+
> \`.env.local\` is git-ignored - fill in your secrets before running.
|
|
2321
2338
|
|
|
2322
2339
|
## Setup
|
|
2323
2340
|
|
|
@@ -2582,7 +2599,7 @@ function buildPackageJson$2(collectionName) {
|
|
|
2582
2599
|
}
|
|
2583
2600
|
function buildReadme$4(collectionName, filePaths) {
|
|
2584
2601
|
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2585
|
-
return `# ${collectionName}
|
|
2602
|
+
return `# ${collectionName} - API Tests (Playwright JavaScript)
|
|
2586
2603
|
|
|
2587
2604
|
## Project structure
|
|
2588
2605
|
|
|
@@ -2590,7 +2607,7 @@ function buildReadme$4(collectionName, filePaths) {
|
|
|
2590
2607
|
${tree}
|
|
2591
2608
|
\`\`\`
|
|
2592
2609
|
|
|
2593
|
-
> \`.env.local\` is git-ignored
|
|
2610
|
+
> \`.env.local\` is git-ignored - fill in your secrets before running.
|
|
2594
2611
|
|
|
2595
2612
|
## Setup
|
|
2596
2613
|
|
|
@@ -2842,7 +2859,7 @@ function buildTsConfig() {
|
|
|
2842
2859
|
}
|
|
2843
2860
|
function buildReadme$3(collectionName, filePaths) {
|
|
2844
2861
|
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2845
|
-
return `# ${collectionName}
|
|
2862
|
+
return `# ${collectionName} - API Tests (Supertest + Jest TypeScript)
|
|
2846
2863
|
|
|
2847
2864
|
## Project structure
|
|
2848
2865
|
|
|
@@ -2850,7 +2867,7 @@ function buildReadme$3(collectionName, filePaths) {
|
|
|
2850
2867
|
${tree}
|
|
2851
2868
|
\`\`\`
|
|
2852
2869
|
|
|
2853
|
-
> \`.env.local\` is git-ignored
|
|
2870
|
+
> \`.env.local\` is git-ignored - fill in your secrets before running.
|
|
2854
2871
|
|
|
2855
2872
|
## Setup
|
|
2856
2873
|
|
|
@@ -3088,7 +3105,7 @@ function buildPackageJson(collectionName) {
|
|
|
3088
3105
|
}
|
|
3089
3106
|
function buildReadme$2(collectionName, filePaths) {
|
|
3090
3107
|
const tree = renderTree([...filePaths, ".env.local"]);
|
|
3091
|
-
return `# ${collectionName}
|
|
3108
|
+
return `# ${collectionName} - API Tests (Supertest + Jest JavaScript)
|
|
3092
3109
|
|
|
3093
3110
|
## Project structure
|
|
3094
3111
|
|
|
@@ -3096,7 +3113,7 @@ function buildReadme$2(collectionName, filePaths) {
|
|
|
3096
3113
|
${tree}
|
|
3097
3114
|
\`\`\`
|
|
3098
3115
|
|
|
3099
|
-
> \`.env.local\` is git-ignored
|
|
3116
|
+
> \`.env.local\` is git-ignored - fill in your secrets before running.
|
|
3100
3117
|
|
|
3101
3118
|
## Setup
|
|
3102
3119
|
|
|
@@ -3427,7 +3444,7 @@ ${methods.join("\n\n")}
|
|
|
3427
3444
|
}
|
|
3428
3445
|
function buildReadme$1(collectionName, filePaths) {
|
|
3429
3446
|
const tree = renderTree(filePaths);
|
|
3430
|
-
return `# ${collectionName}
|
|
3447
|
+
return `# ${collectionName} - API Tests (REST Assured + JUnit 5)
|
|
3431
3448
|
|
|
3432
3449
|
## Project structure
|
|
3433
3450
|
|
|
@@ -3827,10 +3844,10 @@ ${scenarios.join("\n\n")}
|
|
|
3827
3844
|
}
|
|
3828
3845
|
function buildReadme(collectionName, filePaths) {
|
|
3829
3846
|
const tree = renderTree(filePaths);
|
|
3830
|
-
return `# ${collectionName}
|
|
3847
|
+
return `# ${collectionName} - API Tests (Karate + JUnit 5)
|
|
3831
3848
|
|
|
3832
3849
|
Karate is a BDD-flavoured API test framework that uses Gherkin feature files
|
|
3833
|
-
(no glue code)
|
|
3850
|
+
(no glue code) - see https://docs.karatelabs.io for the full reference.
|
|
3834
3851
|
|
|
3835
3852
|
## Project structure
|
|
3836
3853
|
|
|
@@ -4035,17 +4052,17 @@ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
|
4035
4052
|
function registerRunnerHandler(ipc) {
|
|
4036
4053
|
handle.handleIpc(ipc, handle.IPC.runner.start, async (event, payload) => {
|
|
4037
4054
|
const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
|
|
4038
|
-
const envVars = await
|
|
4039
|
-
const liveGlobals =
|
|
4055
|
+
const envVars = await requestExec.buildEnvVars(environment);
|
|
4056
|
+
const liveGlobals = requestExec.getGlobals();
|
|
4040
4057
|
const globals = { ...payloadGlobals, ...liveGlobals };
|
|
4041
|
-
const dispatcher = await
|
|
4058
|
+
const dispatcher = await requestExec.buildDispatcher(proxy, tls);
|
|
4042
4059
|
const summary = { total: items.length, passed: 0, failed: 0, errors: 0, skipped: 0, durationMs: 0 };
|
|
4043
4060
|
const totalStart = Date.now();
|
|
4044
4061
|
let runEnvVars = { ...envVars };
|
|
4045
4062
|
let runCollectionVars = {};
|
|
4046
4063
|
let runGlobals = { ...globals };
|
|
4047
4064
|
let runLocalVars = {};
|
|
4048
|
-
const skipTracker = new
|
|
4065
|
+
const skipTracker = new requestExec.HookSkipTracker();
|
|
4049
4066
|
for (const item of items) {
|
|
4050
4067
|
const skipReason = skipTracker.shouldSkip(item);
|
|
4051
4068
|
if (skipReason) {
|
|
@@ -4075,7 +4092,7 @@ function registerRunnerHandler(ipc) {
|
|
|
4075
4092
|
scopePath: item.scopePath
|
|
4076
4093
|
};
|
|
4077
4094
|
event.sender.send(handle.IPC.runner.progress, { requestId: item.request.id, ...runningUpdate });
|
|
4078
|
-
const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await
|
|
4095
|
+
const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestExec.executeRunnerRequest({
|
|
4079
4096
|
req: item.request,
|
|
4080
4097
|
collectionVars: { ...item.collectionVars, ...runCollectionVars },
|
|
4081
4098
|
envVars: runEnvVars,
|
|
@@ -4142,14 +4159,14 @@ function registerOAuth2Handlers(ipc) {
|
|
|
4142
4159
|
handle.handleIpc(ipc, handle.IPC.oauth2.startFlow, async (_e, auth, vars) => {
|
|
4143
4160
|
const port = auth.oauth2RedirectPort ?? 9876;
|
|
4144
4161
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
4145
|
-
const authUrl =
|
|
4146
|
-
const tokenUrl =
|
|
4147
|
-
const clientId =
|
|
4162
|
+
const authUrl = requestExec.interpolate(auth.oauth2AuthUrl ?? "", vars);
|
|
4163
|
+
const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
4164
|
+
const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
4148
4165
|
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
4149
4166
|
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
4150
|
-
clientSecret = await
|
|
4167
|
+
clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
4151
4168
|
}
|
|
4152
|
-
clientSecret =
|
|
4169
|
+
clientSecret = requestExec.interpolate(clientSecret, vars);
|
|
4153
4170
|
if (!authUrl) throw new Error("OAuth 2.0: authUrl is required for authorization_code flow.");
|
|
4154
4171
|
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for authorization_code flow.");
|
|
4155
4172
|
if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
|
|
@@ -4224,13 +4241,13 @@ function registerOAuth2Handlers(ipc) {
|
|
|
4224
4241
|
};
|
|
4225
4242
|
});
|
|
4226
4243
|
handle.handleIpc(ipc, handle.IPC.oauth2.refreshToken, async (_e, auth, vars, refreshToken) => {
|
|
4227
|
-
const tokenUrl =
|
|
4228
|
-
const clientId =
|
|
4244
|
+
const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
4245
|
+
const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
4229
4246
|
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
4230
4247
|
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
4231
|
-
clientSecret = await
|
|
4248
|
+
clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
4232
4249
|
}
|
|
4233
|
-
clientSecret =
|
|
4250
|
+
clientSecret = requestExec.interpolate(clientSecret, vars);
|
|
4234
4251
|
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for refresh.");
|
|
4235
4252
|
const { fetch: nodeFetch } = await import("undici");
|
|
4236
4253
|
const params = new URLSearchParams();
|
|
@@ -4638,7 +4655,7 @@ function inferSchemaFromJson(json) {
|
|
|
4638
4655
|
}
|
|
4639
4656
|
async function resolveSnapshotSpec(relPath) {
|
|
4640
4657
|
const dir = getWorkspaceDir();
|
|
4641
|
-
if (!dir) throw new Error("No workspace open
|
|
4658
|
+
if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
|
|
4642
4659
|
const snap = await snapshots.loadSnapshot(dir, relPath);
|
|
4643
4660
|
const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
|
|
4644
4661
|
await promises.writeFile(tmp, snap.spec, "utf8");
|
|
@@ -4658,7 +4675,7 @@ function registerContractHandlers(ipc) {
|
|
|
4658
4675
|
case "consumer":
|
|
4659
4676
|
return snapshots.runConsumerContracts(requests, envVars, collectionVars);
|
|
4660
4677
|
case "provider":
|
|
4661
|
-
return snapshots.runProviderVerification(requests, envVars, specUrl, specPath, requestBaseUrl);
|
|
4678
|
+
return snapshots.runProviderVerification(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
|
|
4662
4679
|
case "provider-live":
|
|
4663
4680
|
return snapshots.runLiveProviderVerification(requests, envVars, collectionVars, providerBaseUrl, stateHandlerUrl);
|
|
4664
4681
|
case "bidirectional":
|
|
@@ -4681,7 +4698,7 @@ function registerContractHandlers(ipc) {
|
|
|
4681
4698
|
});
|
|
4682
4699
|
handle.handleIpc(ipc, handle.IPC.contract.captureSnapshot, async (_e, opts) => {
|
|
4683
4700
|
const dir = getWorkspaceDir();
|
|
4684
|
-
if (!dir) throw new Error("No workspace open
|
|
4701
|
+
if (!dir) throw new Error("No workspace open - cannot capture snapshot.");
|
|
4685
4702
|
const snapshot = await snapshots.captureSnapshot(dir, opts);
|
|
4686
4703
|
const relPath = snapshots.relPathOf(snapshot);
|
|
4687
4704
|
if (!relPath) throw new Error("Snapshot created but relPath was not attached.");
|
|
@@ -4702,6 +4719,24 @@ function registerContractHandlers(ipc) {
|
|
|
4702
4719
|
if (!dir) return;
|
|
4703
4720
|
await snapshots.deleteSnapshot(dir, relPath);
|
|
4704
4721
|
});
|
|
4722
|
+
handle.handleIpc(ipc, handle.IPC.contract.fuzz, async (_e, payload) => {
|
|
4723
|
+
let { specUrl, specPath } = payload;
|
|
4724
|
+
if (payload.specSnapshotRelPath) {
|
|
4725
|
+
const resolved = await resolveSnapshotSpec(payload.specSnapshotRelPath);
|
|
4726
|
+
specPath = resolved.specPath;
|
|
4727
|
+
specUrl = void 0;
|
|
4728
|
+
}
|
|
4729
|
+
return snapshots.runFuzz({ ...payload, specUrl, specPath });
|
|
4730
|
+
});
|
|
4731
|
+
handle.handleIpc(ipc, handle.IPC.contract.recordResult, async (_e, opts) => {
|
|
4732
|
+
const dir = getWorkspaceDir();
|
|
4733
|
+
if (!dir) throw new Error("No workspace open — cannot record result.");
|
|
4734
|
+
const pacticipant = opts.pacticipant?.trim();
|
|
4735
|
+
const version = opts.version?.trim();
|
|
4736
|
+
if (!pacticipant || !version) throw new Error("Pacticipant and version are required.");
|
|
4737
|
+
const file = await snapshots.recordResult(dir, pacticipant, version, opts.report, (/* @__PURE__ */ new Date()).toISOString());
|
|
4738
|
+
return { file };
|
|
4739
|
+
});
|
|
4705
4740
|
}
|
|
4706
4741
|
function git() {
|
|
4707
4742
|
const dir = getWorkspaceDir();
|
|
@@ -4983,10 +5018,10 @@ function createWindow() {
|
|
|
4983
5018
|
}
|
|
4984
5019
|
electron.app.whenReady().then(async () => {
|
|
4985
5020
|
if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
|
|
4986
|
-
await
|
|
5021
|
+
await requestExec.initSecretStore(electron.app.getPath("userData"));
|
|
4987
5022
|
registerFileHandlers(electron.ipcMain);
|
|
4988
5023
|
registerRequestHandler(electron.ipcMain);
|
|
4989
|
-
|
|
5024
|
+
requestExec.registerSecretHandlers(electron.ipcMain);
|
|
4990
5025
|
registerImportHandlers(electron.ipcMain);
|
|
4991
5026
|
registerGenerateHandlers(electron.ipcMain);
|
|
4992
5027
|
registerRunnerHandler(electron.ipcMain);
|
package/out/main/record.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
const promises = require("fs/promises");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const recorder = require("./chunks/recorder-
|
|
5
|
+
const recorder = require("./chunks/recorder-0Ij921El.js");
|
|
6
6
|
const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
|
|
7
7
|
require("http");
|
|
8
8
|
require("crypto");
|
|
@@ -39,7 +39,7 @@ async function main() {
|
|
|
39
39
|
});
|
|
40
40
|
await recorder.startRecorder({ upstream, port, maskHeaders: extraMask, ignoreHeaders: extraIgnore });
|
|
41
41
|
console.log("");
|
|
42
|
-
console.log(cliCommon.color(" API Spector
|
|
42
|
+
console.log(cliCommon.color(" API Spector - Record Proxy", cliCommon.C.bold, cliCommon.C.white));
|
|
43
43
|
console.log(cliCommon.color(` Upstream: ${upstream}`, cliCommon.C.gray));
|
|
44
44
|
console.log(cliCommon.color(` Listening: http://localhost:${port}`, cliCommon.C.cyan));
|
|
45
45
|
console.log(cliCommon.color(` Output: ${outputDir}`, cliCommon.C.gray));
|
|
@@ -56,7 +56,7 @@ async function main() {
|
|
|
56
56
|
const slug = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
57
57
|
const sessionPath = path.join(outputDir, `session-${slug}.recording.json`);
|
|
58
58
|
await promises.writeFile(sessionPath, JSON.stringify(session, null, 2), "utf8");
|
|
59
|
-
const mockName = `Recorded
|
|
59
|
+
const mockName = `Recorded - ${new URL(upstream).hostname} ${slug}`;
|
|
60
60
|
const mockServer = recorder.entriesToMockServer(session.entries, upstream, mockName, port);
|
|
61
61
|
const mockPath = path.join(outputDir, `session-${slug}.mock.json`);
|
|
62
62
|
await promises.writeFile(mockPath, JSON.stringify(mockServer, null, 2), "utf8");
|