@testsmith/api-spector 0.3.5 → 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/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-BtRMtQJg.js} +3 -1
- package/out/main/chunks/{import-C5Ngq8hk.js → import-CZxL6J96.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-DbNHbA8x.js} +602 -146
- package/out/main/chunks/snapshots-rV5tPwAd.js +1658 -0
- package/out/main/chunks/{soap-handler-B9x_YCtj.js → soap-handler-CqXVreNa.js} +3 -3
- package/out/main/contract.js +508 -91
- package/out/main/index.js +95 -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 +5 -1
- package/out/renderer/assets/index-DWiG0dQo.css +2 -0
- package/out/renderer/assets/{index-BFxRlU1d.js → index-htBErIFG.js} +911 -174
- 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-BtRMtQJg.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-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-
|
|
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-
|
|
42
|
+
const snapshots = require("./chunks/snapshots-rV5tPwAd.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",
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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, () =>
|
|
320
|
+
handle.handleIpc(ipc, handle.IPC.globals.get, () => requestExec.getGlobals());
|
|
321
321
|
handle.handleIpc(ipc, handle.IPC.globals.set, async (_e, patch) => {
|
|
322
|
-
|
|
323
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
441
|
+
requestExec.applyRequestDefaults(req);
|
|
442
442
|
const start = Date.now();
|
|
443
|
-
const liveGlobals =
|
|
443
|
+
const liveGlobals = requestExec.getGlobals();
|
|
444
444
|
const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
|
|
445
|
-
const envVars = await
|
|
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
|
|
460
|
-
let vars =
|
|
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
|
|
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
|
-
|
|
478
|
-
await
|
|
479
|
-
vars =
|
|
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 =
|
|
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
|
|
516
|
-
const exchange = await
|
|
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 =
|
|
528
|
-
const maskedHeaders =
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
588
|
-
await
|
|
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}
|
|
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
|
|
614
|
-
|
|
615
|
-
await
|
|
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,
|
|
@@ -1781,14 +1781,14 @@ function buildNameMap(root, requests) {
|
|
|
1781
1781
|
function buildVariablesFile(environment) {
|
|
1782
1782
|
const lines = ["*** Variables ***"];
|
|
1783
1783
|
if (!environment) {
|
|
1784
|
-
lines.push("# No environment
|
|
1784
|
+
lines.push("# No environment - add your variables here");
|
|
1785
1785
|
lines.push("${BASE_URL} http://localhost:8080");
|
|
1786
1786
|
return lines.join("\n") + "\n";
|
|
1787
1787
|
}
|
|
1788
1788
|
for (const v of environment.variables) {
|
|
1789
1789
|
if (!v.enabled) continue;
|
|
1790
1790
|
if (v.secret) {
|
|
1791
|
-
lines.push(`# ${envVar(v.key)}
|
|
1791
|
+
lines.push(`# ${envVar(v.key)} - stored in OS keychain, never hardcoded`);
|
|
1792
1792
|
} else {
|
|
1793
1793
|
lines.push(`${robotVar(v.key)} ${v.value}`);
|
|
1794
1794
|
}
|
|
@@ -1824,9 +1824,9 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1824
1824
|
const url = interpolate(req.url, varMap);
|
|
1825
1825
|
const method = req.method.charAt(0) + req.method.slice(1).toLowerCase();
|
|
1826
1826
|
lines.push(kwName);
|
|
1827
|
-
lines.push(` [Documentation] Hook: ${req.hookType}
|
|
1827
|
+
lines.push(` [Documentation] Hook: ${req.hookType} - ${req.name}`);
|
|
1828
1828
|
if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
|
|
1829
|
-
lines.push(` Log ${req.method} is not supported by robotframework-requests
|
|
1829
|
+
lines.push(` Log ${req.method} is not supported by robotframework-requests - hook skipped WARN`);
|
|
1830
1830
|
lines.push(` RETURN \${None}`);
|
|
1831
1831
|
lines.push("");
|
|
1832
1832
|
continue;
|
|
@@ -1867,7 +1867,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1867
1867
|
lines.push(kwName);
|
|
1868
1868
|
lines.push(` [Documentation] ${req.description || req.name}`);
|
|
1869
1869
|
if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
|
|
1870
|
-
lines.push(` Log ${req.method} is not supported by robotframework-requests
|
|
1870
|
+
lines.push(` Log ${req.method} is not supported by robotframework-requests - request skipped WARN`);
|
|
1871
1871
|
lines.push(` RETURN \${None}`);
|
|
1872
1872
|
lines.push("");
|
|
1873
1873
|
continue;
|
|
@@ -2015,7 +2015,7 @@ function buildTestSuite(collection, environment, nameMap) {
|
|
|
2015
2015
|
}
|
|
2016
2016
|
function buildReadme$6(collectionName, filePaths) {
|
|
2017
2017
|
const tree = renderTree(filePaths);
|
|
2018
|
-
return `# ${collectionName}
|
|
2018
|
+
return `# ${collectionName} - API Tests (Robot Framework)
|
|
2019
2019
|
|
|
2020
2020
|
## Project structure
|
|
2021
2021
|
|
|
@@ -2024,7 +2024,7 @@ ${tree}
|
|
|
2024
2024
|
\`\`\`
|
|
2025
2025
|
|
|
2026
2026
|
> Secrets are read from OS environment variables (e.g. \`%{API_TOKEN}\`).
|
|
2027
|
-
> Never hardcode secrets
|
|
2027
|
+
> Never hardcode secrets - export them in your shell or CI environment.
|
|
2028
2028
|
|
|
2029
2029
|
## Setup
|
|
2030
2030
|
|
|
@@ -2309,7 +2309,7 @@ function buildPackageJson$3(collectionName) {
|
|
|
2309
2309
|
}
|
|
2310
2310
|
function buildReadme$5(collectionName, filePaths) {
|
|
2311
2311
|
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2312
|
-
return `# ${collectionName}
|
|
2312
|
+
return `# ${collectionName} - API Tests (Playwright TypeScript)
|
|
2313
2313
|
|
|
2314
2314
|
## Project structure
|
|
2315
2315
|
|
|
@@ -2317,7 +2317,7 @@ function buildReadme$5(collectionName, filePaths) {
|
|
|
2317
2317
|
${tree}
|
|
2318
2318
|
\`\`\`
|
|
2319
2319
|
|
|
2320
|
-
> \`.env.local\` is git-ignored
|
|
2320
|
+
> \`.env.local\` is git-ignored - fill in your secrets before running.
|
|
2321
2321
|
|
|
2322
2322
|
## Setup
|
|
2323
2323
|
|
|
@@ -2582,7 +2582,7 @@ function buildPackageJson$2(collectionName) {
|
|
|
2582
2582
|
}
|
|
2583
2583
|
function buildReadme$4(collectionName, filePaths) {
|
|
2584
2584
|
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2585
|
-
return `# ${collectionName}
|
|
2585
|
+
return `# ${collectionName} - API Tests (Playwright JavaScript)
|
|
2586
2586
|
|
|
2587
2587
|
## Project structure
|
|
2588
2588
|
|
|
@@ -2590,7 +2590,7 @@ function buildReadme$4(collectionName, filePaths) {
|
|
|
2590
2590
|
${tree}
|
|
2591
2591
|
\`\`\`
|
|
2592
2592
|
|
|
2593
|
-
> \`.env.local\` is git-ignored
|
|
2593
|
+
> \`.env.local\` is git-ignored - fill in your secrets before running.
|
|
2594
2594
|
|
|
2595
2595
|
## Setup
|
|
2596
2596
|
|
|
@@ -2842,7 +2842,7 @@ function buildTsConfig() {
|
|
|
2842
2842
|
}
|
|
2843
2843
|
function buildReadme$3(collectionName, filePaths) {
|
|
2844
2844
|
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2845
|
-
return `# ${collectionName}
|
|
2845
|
+
return `# ${collectionName} - API Tests (Supertest + Jest TypeScript)
|
|
2846
2846
|
|
|
2847
2847
|
## Project structure
|
|
2848
2848
|
|
|
@@ -2850,7 +2850,7 @@ function buildReadme$3(collectionName, filePaths) {
|
|
|
2850
2850
|
${tree}
|
|
2851
2851
|
\`\`\`
|
|
2852
2852
|
|
|
2853
|
-
> \`.env.local\` is git-ignored
|
|
2853
|
+
> \`.env.local\` is git-ignored - fill in your secrets before running.
|
|
2854
2854
|
|
|
2855
2855
|
## Setup
|
|
2856
2856
|
|
|
@@ -3088,7 +3088,7 @@ function buildPackageJson(collectionName) {
|
|
|
3088
3088
|
}
|
|
3089
3089
|
function buildReadme$2(collectionName, filePaths) {
|
|
3090
3090
|
const tree = renderTree([...filePaths, ".env.local"]);
|
|
3091
|
-
return `# ${collectionName}
|
|
3091
|
+
return `# ${collectionName} - API Tests (Supertest + Jest JavaScript)
|
|
3092
3092
|
|
|
3093
3093
|
## Project structure
|
|
3094
3094
|
|
|
@@ -3096,7 +3096,7 @@ function buildReadme$2(collectionName, filePaths) {
|
|
|
3096
3096
|
${tree}
|
|
3097
3097
|
\`\`\`
|
|
3098
3098
|
|
|
3099
|
-
> \`.env.local\` is git-ignored
|
|
3099
|
+
> \`.env.local\` is git-ignored - fill in your secrets before running.
|
|
3100
3100
|
|
|
3101
3101
|
## Setup
|
|
3102
3102
|
|
|
@@ -3427,7 +3427,7 @@ ${methods.join("\n\n")}
|
|
|
3427
3427
|
}
|
|
3428
3428
|
function buildReadme$1(collectionName, filePaths) {
|
|
3429
3429
|
const tree = renderTree(filePaths);
|
|
3430
|
-
return `# ${collectionName}
|
|
3430
|
+
return `# ${collectionName} - API Tests (REST Assured + JUnit 5)
|
|
3431
3431
|
|
|
3432
3432
|
## Project structure
|
|
3433
3433
|
|
|
@@ -3827,10 +3827,10 @@ ${scenarios.join("\n\n")}
|
|
|
3827
3827
|
}
|
|
3828
3828
|
function buildReadme(collectionName, filePaths) {
|
|
3829
3829
|
const tree = renderTree(filePaths);
|
|
3830
|
-
return `# ${collectionName}
|
|
3830
|
+
return `# ${collectionName} - API Tests (Karate + JUnit 5)
|
|
3831
3831
|
|
|
3832
3832
|
Karate is a BDD-flavoured API test framework that uses Gherkin feature files
|
|
3833
|
-
(no glue code)
|
|
3833
|
+
(no glue code) - see https://docs.karatelabs.io for the full reference.
|
|
3834
3834
|
|
|
3835
3835
|
## Project structure
|
|
3836
3836
|
|
|
@@ -4035,17 +4035,17 @@ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
|
4035
4035
|
function registerRunnerHandler(ipc) {
|
|
4036
4036
|
handle.handleIpc(ipc, handle.IPC.runner.start, async (event, payload) => {
|
|
4037
4037
|
const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
|
|
4038
|
-
const envVars = await
|
|
4039
|
-
const liveGlobals =
|
|
4038
|
+
const envVars = await requestExec.buildEnvVars(environment);
|
|
4039
|
+
const liveGlobals = requestExec.getGlobals();
|
|
4040
4040
|
const globals = { ...payloadGlobals, ...liveGlobals };
|
|
4041
|
-
const dispatcher = await
|
|
4041
|
+
const dispatcher = await requestExec.buildDispatcher(proxy, tls);
|
|
4042
4042
|
const summary = { total: items.length, passed: 0, failed: 0, errors: 0, skipped: 0, durationMs: 0 };
|
|
4043
4043
|
const totalStart = Date.now();
|
|
4044
4044
|
let runEnvVars = { ...envVars };
|
|
4045
4045
|
let runCollectionVars = {};
|
|
4046
4046
|
let runGlobals = { ...globals };
|
|
4047
4047
|
let runLocalVars = {};
|
|
4048
|
-
const skipTracker = new
|
|
4048
|
+
const skipTracker = new requestExec.HookSkipTracker();
|
|
4049
4049
|
for (const item of items) {
|
|
4050
4050
|
const skipReason = skipTracker.shouldSkip(item);
|
|
4051
4051
|
if (skipReason) {
|
|
@@ -4075,7 +4075,7 @@ function registerRunnerHandler(ipc) {
|
|
|
4075
4075
|
scopePath: item.scopePath
|
|
4076
4076
|
};
|
|
4077
4077
|
event.sender.send(handle.IPC.runner.progress, { requestId: item.request.id, ...runningUpdate });
|
|
4078
|
-
const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await
|
|
4078
|
+
const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestExec.executeRunnerRequest({
|
|
4079
4079
|
req: item.request,
|
|
4080
4080
|
collectionVars: { ...item.collectionVars, ...runCollectionVars },
|
|
4081
4081
|
envVars: runEnvVars,
|
|
@@ -4142,14 +4142,14 @@ function registerOAuth2Handlers(ipc) {
|
|
|
4142
4142
|
handle.handleIpc(ipc, handle.IPC.oauth2.startFlow, async (_e, auth, vars) => {
|
|
4143
4143
|
const port = auth.oauth2RedirectPort ?? 9876;
|
|
4144
4144
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
4145
|
-
const authUrl =
|
|
4146
|
-
const tokenUrl =
|
|
4147
|
-
const clientId =
|
|
4145
|
+
const authUrl = requestExec.interpolate(auth.oauth2AuthUrl ?? "", vars);
|
|
4146
|
+
const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
4147
|
+
const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
4148
4148
|
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
4149
4149
|
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
4150
|
-
clientSecret = await
|
|
4150
|
+
clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
4151
4151
|
}
|
|
4152
|
-
clientSecret =
|
|
4152
|
+
clientSecret = requestExec.interpolate(clientSecret, vars);
|
|
4153
4153
|
if (!authUrl) throw new Error("OAuth 2.0: authUrl is required for authorization_code flow.");
|
|
4154
4154
|
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for authorization_code flow.");
|
|
4155
4155
|
if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
|
|
@@ -4224,13 +4224,13 @@ function registerOAuth2Handlers(ipc) {
|
|
|
4224
4224
|
};
|
|
4225
4225
|
});
|
|
4226
4226
|
handle.handleIpc(ipc, handle.IPC.oauth2.refreshToken, async (_e, auth, vars, refreshToken) => {
|
|
4227
|
-
const tokenUrl =
|
|
4228
|
-
const clientId =
|
|
4227
|
+
const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
4228
|
+
const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
4229
4229
|
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
4230
4230
|
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
4231
|
-
clientSecret = await
|
|
4231
|
+
clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
4232
4232
|
}
|
|
4233
|
-
clientSecret =
|
|
4233
|
+
clientSecret = requestExec.interpolate(clientSecret, vars);
|
|
4234
4234
|
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for refresh.");
|
|
4235
4235
|
const { fetch: nodeFetch } = await import("undici");
|
|
4236
4236
|
const params = new URLSearchParams();
|
|
@@ -4638,7 +4638,7 @@ function inferSchemaFromJson(json) {
|
|
|
4638
4638
|
}
|
|
4639
4639
|
async function resolveSnapshotSpec(relPath) {
|
|
4640
4640
|
const dir = getWorkspaceDir();
|
|
4641
|
-
if (!dir) throw new Error("No workspace open
|
|
4641
|
+
if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
|
|
4642
4642
|
const snap = await snapshots.loadSnapshot(dir, relPath);
|
|
4643
4643
|
const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
|
|
4644
4644
|
await promises.writeFile(tmp, snap.spec, "utf8");
|
|
@@ -4658,7 +4658,7 @@ function registerContractHandlers(ipc) {
|
|
|
4658
4658
|
case "consumer":
|
|
4659
4659
|
return snapshots.runConsumerContracts(requests, envVars, collectionVars);
|
|
4660
4660
|
case "provider":
|
|
4661
|
-
return snapshots.runProviderVerification(requests, envVars, specUrl, specPath, requestBaseUrl);
|
|
4661
|
+
return snapshots.runProviderVerification(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
|
|
4662
4662
|
case "provider-live":
|
|
4663
4663
|
return snapshots.runLiveProviderVerification(requests, envVars, collectionVars, providerBaseUrl, stateHandlerUrl);
|
|
4664
4664
|
case "bidirectional":
|
|
@@ -4681,7 +4681,7 @@ function registerContractHandlers(ipc) {
|
|
|
4681
4681
|
});
|
|
4682
4682
|
handle.handleIpc(ipc, handle.IPC.contract.captureSnapshot, async (_e, opts) => {
|
|
4683
4683
|
const dir = getWorkspaceDir();
|
|
4684
|
-
if (!dir) throw new Error("No workspace open
|
|
4684
|
+
if (!dir) throw new Error("No workspace open - cannot capture snapshot.");
|
|
4685
4685
|
const snapshot = await snapshots.captureSnapshot(dir, opts);
|
|
4686
4686
|
const relPath = snapshots.relPathOf(snapshot);
|
|
4687
4687
|
if (!relPath) throw new Error("Snapshot created but relPath was not attached.");
|
|
@@ -4702,6 +4702,24 @@ function registerContractHandlers(ipc) {
|
|
|
4702
4702
|
if (!dir) return;
|
|
4703
4703
|
await snapshots.deleteSnapshot(dir, relPath);
|
|
4704
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
|
+
});
|
|
4705
4723
|
}
|
|
4706
4724
|
function git() {
|
|
4707
4725
|
const dir = getWorkspaceDir();
|
|
@@ -4983,10 +5001,10 @@ function createWindow() {
|
|
|
4983
5001
|
}
|
|
4984
5002
|
electron.app.whenReady().then(async () => {
|
|
4985
5003
|
if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
|
|
4986
|
-
await
|
|
5004
|
+
await requestExec.initSecretStore(electron.app.getPath("userData"));
|
|
4987
5005
|
registerFileHandlers(electron.ipcMain);
|
|
4988
5006
|
registerRequestHandler(electron.ipcMain);
|
|
4989
|
-
|
|
5007
|
+
requestExec.registerSecretHandlers(electron.ipcMain);
|
|
4990
5008
|
registerImportHandlers(electron.ipcMain);
|
|
4991
5009
|
registerGenerateHandlers(electron.ipcMain);
|
|
4992
5010
|
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");
|
package/out/main/runner.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
const promises = require("fs/promises");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const
|
|
6
|
-
const requestCollection = require("./chunks/request-collection-
|
|
5
|
+
const requestExec = require("./chunks/request-exec-DbNHbA8x.js");
|
|
6
|
+
const requestCollection = require("./chunks/request-collection-CBuPjBwa.js");
|
|
7
|
+
const environments = require("./chunks/environments-iM3SUM-4.js");
|
|
7
8
|
const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
|
|
9
|
+
require("undici");
|
|
10
|
+
require("./chunks/handle-BtRMtQJg.js");
|
|
8
11
|
require("crypto");
|
|
9
|
-
require("http");
|
|
10
|
-
require("./chunks/handle-C0IQL-Vl.js");
|
|
11
12
|
require("dayjs");
|
|
12
13
|
require("vm");
|
|
13
|
-
require("undici");
|
|
14
14
|
require("tv4");
|
|
15
15
|
require("jsonpath-plus");
|
|
16
16
|
require("@xmldom/xmldom");
|
|
17
|
+
require("http");
|
|
17
18
|
require("ajv");
|
|
18
19
|
function buildJsonReport(results, summary, meta = {}) {
|
|
19
20
|
return JSON.stringify({
|
|
@@ -349,7 +350,7 @@ function printResult(r, verbose) {
|
|
|
349
350
|
if (r.testResults?.length) {
|
|
350
351
|
for (const t of r.testResults) {
|
|
351
352
|
const ti = t.passed ? cliCommon.color(" ✓", cliCommon.C.green) : cliCommon.color(" ✗", cliCommon.C.red);
|
|
352
|
-
console.log(`${ti} ${t.name}${t.error ? cliCommon.color(`
|
|
353
|
+
console.log(`${ti} ${t.name}${t.error ? cliCommon.color(` - ${t.error}`, cliCommon.C.red) : ""}`);
|
|
353
354
|
}
|
|
354
355
|
}
|
|
355
356
|
if (r.error) console.log(cliCommon.color(` Error: ${r.error}`, cliCommon.C.red));
|
|
@@ -384,23 +385,25 @@ async function main() {
|
|
|
384
385
|
console.error(cliCommon.color(`Error: could not read workspace file: ${wsPath}`, cliCommon.C.red));
|
|
385
386
|
process.exit(1);
|
|
386
387
|
}
|
|
387
|
-
await
|
|
388
|
+
await requestExec.loadGlobals(wsDir);
|
|
388
389
|
const collections = await cliCommon.loadCollections(workspace, wsDir, {
|
|
389
390
|
onError: (relPath) => console.error(cliCommon.color(` [warn] Could not load collection: ${relPath}`, cliCommon.C.yellow))
|
|
390
391
|
});
|
|
391
|
-
const environments = await cliCommon.loadEnvironments(workspace, wsDir);
|
|
392
|
-
const env =
|
|
392
|
+
const environments$1 = await cliCommon.loadEnvironments(workspace, wsDir);
|
|
393
|
+
const env = environments.selectEnvironment(workspace, environments$1, envName);
|
|
393
394
|
if (envName && !env) {
|
|
394
395
|
console.warn(cliCommon.color(`Warning: environment "${envName}" not found. Running without environment.`, cliCommon.C.yellow));
|
|
396
|
+
} else if (!envName && workspace.settings?.defaultEnvironment && !env) {
|
|
397
|
+
console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
|
|
395
398
|
}
|
|
396
|
-
const version = `v${"0.3.
|
|
399
|
+
const version = `v${"0.3.6"}`;
|
|
397
400
|
console.log("");
|
|
398
401
|
console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
|
|
399
402
|
console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
|
|
400
403
|
console.log(cliCommon.color(` Environment: ${env?.name ?? "(none)"}`, cliCommon.C.gray));
|
|
401
404
|
if (filterTags.length) console.log(cliCommon.color(` Tags: ${filterTags.join(", ")}`, cliCommon.C.gray));
|
|
402
405
|
console.log("");
|
|
403
|
-
const envVarsSnapshot = await
|
|
406
|
+
const envVarsSnapshot = await requestExec.buildEnvVars(env);
|
|
404
407
|
const secretValuesToMask = (env?.variables ?? []).filter((v) => v.secret && v.enabled).map((v) => envVarsSnapshot[v.key]).filter((v) => typeof v === "string" && v.length > 0);
|
|
405
408
|
function redact(s) {
|
|
406
409
|
let out = s;
|
|
@@ -414,14 +417,14 @@ async function main() {
|
|
|
414
417
|
...r,
|
|
415
418
|
sentRequest: r.sentRequest ? {
|
|
416
419
|
headers: Object.fromEntries(
|
|
417
|
-
Object.entries(
|
|
420
|
+
Object.entries(requestExec.maskHeaders(r.sentRequest.headers, piiPatterns)).map(([k, v]) => [k, redact(v)])
|
|
418
421
|
),
|
|
419
|
-
body: r.sentRequest.body != null ? redact(
|
|
422
|
+
body: r.sentRequest.body != null ? redact(requestExec.maskPii(r.sentRequest.body, piiPatterns)) : void 0
|
|
420
423
|
} : void 0,
|
|
421
424
|
receivedResponse: r.receivedResponse ? {
|
|
422
425
|
...r.receivedResponse,
|
|
423
|
-
headers:
|
|
424
|
-
body: redact(
|
|
426
|
+
headers: requestExec.maskHeaders(r.receivedResponse.headers, piiPatterns),
|
|
427
|
+
body: redact(requestExec.maskPii(r.receivedResponse.body, piiPatterns))
|
|
425
428
|
} : void 0
|
|
426
429
|
};
|
|
427
430
|
}
|
|
@@ -435,8 +438,8 @@ async function main() {
|
|
|
435
438
|
const items = requestCollection.buildRunPlan(col, null, filterTags);
|
|
436
439
|
if (items.length === 0) continue;
|
|
437
440
|
if (!firstColName) firstColName = col.name;
|
|
438
|
-
let runEnvVars = await
|
|
439
|
-
let runGlobals =
|
|
441
|
+
let runEnvVars = await requestExec.buildEnvVars(env);
|
|
442
|
+
let runGlobals = requestExec.getGlobals();
|
|
440
443
|
let runCollectionVars = { ...col.collectionVariables ?? {} };
|
|
441
444
|
let runLocalVars = {};
|
|
442
445
|
console.log(cliCommon.color(` ┌ ${col.name}`, cliCommon.C.bold, cliCommon.C.white));
|
|
@@ -455,8 +458,8 @@ async function main() {
|
|
|
455
458
|
}
|
|
456
459
|
let bailed = false;
|
|
457
460
|
let lastPrintedScope = null;
|
|
458
|
-
const skipTracker = new
|
|
459
|
-
const dispatcher = await
|
|
461
|
+
const skipTracker = new requestExec.HookSkipTracker();
|
|
462
|
+
const dispatcher = await requestExec.buildDispatcher(void 0, effectiveTls);
|
|
460
463
|
const onScriptOutput = (phase, lines, error) => {
|
|
461
464
|
if (verbose && lines.length) lines.forEach((l) => console.log(cliCommon.color(` [${phase}] ${l}`, cliCommon.C.gray)));
|
|
462
465
|
if (phase === "pre" && error) console.error(cliCommon.color(` [pre-script error] ${error}`, cliCommon.C.red));
|
|
@@ -479,7 +482,7 @@ async function main() {
|
|
|
479
482
|
scopePath: item.scopePath
|
|
480
483
|
};
|
|
481
484
|
} else {
|
|
482
|
-
const out = await
|
|
485
|
+
const out = await requestExec.executeRunnerRequest({
|
|
483
486
|
req: item.request,
|
|
484
487
|
collectionVars: { ...item.collectionVars, ...runCollectionVars },
|
|
485
488
|
envVars: runEnvVars,
|