@testsmith/api-spector 0.6.3 → 0.6.5
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/agents.js +5 -5
- package/out/main/chunks/{broker-client-WEymdqAK.js → broker-client-CvRuiX3o.js} +26 -23
- package/out/main/chunks/{cli-common-vASoDfQH.js → cli-common-Cq_cphpZ.js} +4 -4
- package/out/main/chunks/{coverage-C54mZEh_.js → coverage-C0U4QM83.js} +8 -10
- package/out/main/chunks/{data-files-Br2h3Dt2.js → data-files-BYqhsoPe.js} +4 -4
- package/out/main/chunks/escape-DDVAlOvZ.js +9 -0
- package/out/main/chunks/{import-DdXhO56X.js → import-DYssQzex.js} +4 -4
- package/out/main/chunks/{mock-server-BxiXhP1m.js → mock-server-C4kTFkod.js} +9 -11
- package/out/main/chunks/{recorder-0Ij921El.js → recorder-BkO8G_mB.js} +2 -2
- package/out/main/chunks/{request-collection-GzK4QgvT.js → request-collection-KGPuvN7o.js} +2 -2
- package/out/main/chunks/{request-exec-DAFA6Fap.js → request-exec-BRmVweue.js} +41 -41
- package/out/main/chunks/{soap-handler-BAPCALkN.js → soap-handler-BznuMFIP.js} +7 -9
- package/out/main/compare.js +10 -10
- package/out/main/contract.js +18 -20
- package/out/main/coverage.js +7 -7
- package/out/main/generate-tests.js +9 -9
- package/out/main/index.js +163 -263
- package/out/main/lib.js +6 -7
- package/out/main/mock.js +8 -8
- package/out/main/record.js +9 -9
- package/out/main/runner.js +14 -15
- package/out/main/wsdl.js +9 -9
- package/out/preload/index.js +1 -1
- package/out/renderer/assets/{GraphQLEditor-MImdgXb4.js → GraphQLEditor-u5atf7vH.js} +6 -6
- package/out/renderer/assets/{SoapEditor-rxb3heue.js → SoapEditor-D8MpL0vd.js} +3 -3
- package/out/renderer/assets/{index-ByGIqJ2H.js → index-C0kKFo3R.js} +380 -269
- package/out/renderer/index.html +1 -1
- package/package.json +1 -1
- package/out/main/chunks/escape-BwPM4Tf6.js +0 -9
package/out/main/index.js
CHANGED
|
@@ -23,42 +23,38 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
const electron = require("electron");
|
|
25
25
|
const handle = require("./chunks/handle-CL-nWtYA.js");
|
|
26
|
-
const path = require("path");
|
|
27
|
-
const fs
|
|
28
|
-
const promises = require("fs/promises");
|
|
29
|
-
const crypto = require("crypto");
|
|
26
|
+
const path = require("node:path");
|
|
27
|
+
const fs = require("node:fs");
|
|
28
|
+
const promises = require("node:fs/promises");
|
|
29
|
+
const crypto = require("node:crypto");
|
|
30
30
|
const JSZip = require("jszip");
|
|
31
|
-
const dataFiles = require("./chunks/data-files-
|
|
32
|
-
const requestExec = require("./chunks/request-exec-
|
|
31
|
+
const dataFiles = require("./chunks/data-files-BYqhsoPe.js");
|
|
32
|
+
const requestExec = require("./chunks/request-exec-BRmVweue.js");
|
|
33
33
|
const ipcValidate = require("./chunks/ipc-validate-sqUFqRmE.js");
|
|
34
34
|
const uuid = require("uuid");
|
|
35
35
|
const jsYaml = require("js-yaml");
|
|
36
36
|
const undici = require("undici");
|
|
37
|
-
const requestCollection = require("./chunks/request-collection-
|
|
38
|
-
const mockServer = require("./chunks/mock-server-
|
|
39
|
-
const http = require("http");
|
|
37
|
+
const requestCollection = require("./chunks/request-collection-KGPuvN7o.js");
|
|
38
|
+
const mockServer = require("./chunks/mock-server-C4kTFkod.js");
|
|
39
|
+
const http = require("node:http");
|
|
40
40
|
const WebSocket = require("ws");
|
|
41
|
-
const fs = require("node:fs");
|
|
42
41
|
const os = require("node:os");
|
|
43
|
-
const path$1 = require("node:path");
|
|
44
42
|
const grpc = require("@grpc/grpc-js");
|
|
45
43
|
const protoLoader = require("@grpc/proto-loader");
|
|
46
|
-
const soapHandler = require("./chunks/soap-handler-
|
|
47
|
-
const escape = require("./chunks/escape-
|
|
48
|
-
const
|
|
49
|
-
const os$1 = require("os");
|
|
50
|
-
const brokerClient = require("./chunks/broker-client-WEymdqAK.js");
|
|
44
|
+
const soapHandler = require("./chunks/soap-handler-BznuMFIP.js");
|
|
45
|
+
const escape = require("./chunks/escape-DDVAlOvZ.js");
|
|
46
|
+
const brokerClient = require("./chunks/broker-client-CvRuiX3o.js");
|
|
51
47
|
const simpleGit = require("simple-git");
|
|
52
|
-
const recorder = require("./chunks/recorder-
|
|
48
|
+
const recorder = require("./chunks/recorder-BkO8G_mB.js");
|
|
49
|
+
require("fs/promises");
|
|
53
50
|
require("tls");
|
|
54
|
-
require("node:crypto");
|
|
55
51
|
require("dayjs");
|
|
56
|
-
require("vm");
|
|
52
|
+
require("node:vm");
|
|
57
53
|
require("tv4");
|
|
58
54
|
require("jsonpath-plus");
|
|
59
55
|
require("@xmldom/xmldom");
|
|
60
56
|
require("ajv");
|
|
61
|
-
require("https");
|
|
57
|
+
require("node:https");
|
|
62
58
|
function _interopNamespaceDefault(e) {
|
|
63
59
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
64
60
|
if (e) {
|
|
@@ -75,9 +71,9 @@ function _interopNamespaceDefault(e) {
|
|
|
75
71
|
n.default = e;
|
|
76
72
|
return Object.freeze(n);
|
|
77
73
|
}
|
|
74
|
+
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
|
|
78
75
|
const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
|
|
79
76
|
const os__namespace = /* @__PURE__ */ _interopNamespaceDefault(os);
|
|
80
|
-
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path$1);
|
|
81
77
|
const grpc__namespace = /* @__PURE__ */ _interopNamespaceDefault(grpc);
|
|
82
78
|
const protoLoader__namespace = /* @__PURE__ */ _interopNamespaceDefault(protoLoader);
|
|
83
79
|
const LAST_WS_FILE = path.join(electron.app.getPath("userData"), "last-workspace.json");
|
|
@@ -205,29 +201,29 @@ function readmeContents(workspaceFileName) {
|
|
|
205
201
|
].join("\n");
|
|
206
202
|
}
|
|
207
203
|
async function ensureReadme(workspaceDir2, workspaceFileName) {
|
|
208
|
-
const path$
|
|
204
|
+
const path$1 = path.join(workspaceDir2, "README.md");
|
|
209
205
|
try {
|
|
210
|
-
await promises.readFile(path$
|
|
206
|
+
await promises.readFile(path$1, "utf8");
|
|
211
207
|
return;
|
|
212
208
|
} catch (err) {
|
|
213
209
|
if (err.code !== "ENOENT") {
|
|
214
210
|
console.warn("file-handler: could not check README.md", err);
|
|
215
211
|
return;
|
|
216
212
|
}
|
|
217
|
-
await atomicWrite(path$
|
|
213
|
+
await atomicWrite(path$1, readmeContents(workspaceFileName));
|
|
218
214
|
}
|
|
219
215
|
}
|
|
220
216
|
async function ensureGitignore(workspaceDir2) {
|
|
221
|
-
const path$
|
|
217
|
+
const path$1 = path.join(workspaceDir2, ".gitignore");
|
|
222
218
|
try {
|
|
223
|
-
await promises.readFile(path$
|
|
219
|
+
await promises.readFile(path$1, "utf8");
|
|
224
220
|
return;
|
|
225
221
|
} catch (err) {
|
|
226
222
|
if (err.code !== "ENOENT") {
|
|
227
223
|
console.warn("file-handler: could not check .gitignore", err);
|
|
228
224
|
return;
|
|
229
225
|
}
|
|
230
|
-
await atomicWrite(path$
|
|
226
|
+
await atomicWrite(path$1, SPECTOR_GITIGNORE);
|
|
231
227
|
}
|
|
232
228
|
}
|
|
233
229
|
async function ensureVscodeFileAssociation(workspaceDir2) {
|
|
@@ -261,7 +257,7 @@ function dialogStartDir() {
|
|
|
261
257
|
function defaultWorkspaceName() {
|
|
262
258
|
const cwd = process.env.API_SPECTOR_LAUNCH_CWD;
|
|
263
259
|
if (cwd) {
|
|
264
|
-
const base = cwd.split(/[\\/]/).
|
|
260
|
+
const base = cwd.split(/[\\/]/).findLast(Boolean);
|
|
265
261
|
if (base && /^[a-zA-Z0-9._-]+$/.test(base)) return `${base}.spector`;
|
|
266
262
|
}
|
|
267
263
|
return "my-workspace.spector";
|
|
@@ -477,7 +473,7 @@ Create a workspace in this folder and add "${path.basename(wsPath)}" to it? The
|
|
|
477
473
|
});
|
|
478
474
|
if (pick.canceled || pick.filePaths.length === 0) return null;
|
|
479
475
|
const parent = pick.filePaths[0];
|
|
480
|
-
const repoName = (
|
|
476
|
+
const repoName = (/github\.com\/[^/]+\/([^/#?]+)/i.exec(repoUrl)?.[1] ?? "repository").replace(/\.git$/i, "").replaceAll(/[^a-zA-Z0-9._-]+/g, "-") || "repository";
|
|
481
477
|
const destDir = path.join(parent, repoName);
|
|
482
478
|
try {
|
|
483
479
|
const existing = await promises.readdir(destDir);
|
|
@@ -541,7 +537,7 @@ async function resolveArchiveUrl(input) {
|
|
|
541
537
|
if (url.protocol !== "https:") return null;
|
|
542
538
|
if (url.pathname.toLowerCase().endsWith(".zip")) return url.toString();
|
|
543
539
|
if (url.hostname === "github.com" || url.hostname === "www.github.com") {
|
|
544
|
-
const parts = url.pathname.
|
|
540
|
+
const parts = url.pathname.replaceAll(/^\/+|\/+$/g, "").split("/");
|
|
545
541
|
if (parts.length < 2) return null;
|
|
546
542
|
const owner = parts[0];
|
|
547
543
|
const repo = parts[1].replace(/\.git$/i, "");
|
|
@@ -630,7 +626,7 @@ async function findWorkspaceSpector(dir) {
|
|
|
630
626
|
async function wrapCollectionAsWorkspace(collFilePath, collection) {
|
|
631
627
|
const dir = path.dirname(collFilePath);
|
|
632
628
|
const base = path.basename(collFilePath).replace(/\.(spector|json)$/i, "");
|
|
633
|
-
const safe = base.
|
|
629
|
+
const safe = base.replaceAll(/[^a-zA-Z0-9._-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "collection";
|
|
634
630
|
const collRel = `collections/${safe}.json`;
|
|
635
631
|
if (!collection.id) collection.id = crypto.randomUUID();
|
|
636
632
|
await promises.mkdir(path.join(dir, "collections"), { recursive: true });
|
|
@@ -707,8 +703,7 @@ function formatRequestError(err, context) {
|
|
|
707
703
|
}
|
|
708
704
|
if (stack) {
|
|
709
705
|
const preview = stack.split("\n").slice(0, 6).join("\n");
|
|
710
|
-
lines.push("[request:send] stack:");
|
|
711
|
-
lines.push(preview);
|
|
706
|
+
lines.push("[request:send] stack:", preview);
|
|
712
707
|
}
|
|
713
708
|
return lines.join("\n");
|
|
714
709
|
}
|
|
@@ -993,7 +988,7 @@ function registerAiHandlers(ipc) {
|
|
|
993
988
|
}),
|
|
994
989
|
signal: controller.signal,
|
|
995
990
|
// @ts-expect-error undici accepts a dispatcher for proxy/TLS support
|
|
996
|
-
dispatcher: await requestExec.buildDispatcher(
|
|
991
|
+
dispatcher: await requestExec.buildDispatcher()
|
|
997
992
|
});
|
|
998
993
|
if (!res.ok) {
|
|
999
994
|
let detail = `HTTP ${res.status}`;
|
|
@@ -1308,7 +1303,7 @@ async function loadSpecFromUrl(url) {
|
|
|
1308
1303
|
}
|
|
1309
1304
|
function resolveRef(spec, ref) {
|
|
1310
1305
|
const parts = ref.replace(/^#\//, "").split("/");
|
|
1311
|
-
return parts.reduce((obj, key) => obj?.[decodeURIComponent(key.
|
|
1306
|
+
return parts.reduce((obj, key) => obj?.[decodeURIComponent(key.replaceAll("~1", "/").replaceAll("~0", "~"))], spec);
|
|
1312
1307
|
}
|
|
1313
1308
|
function resolve(spec, obj, seen = /* @__PURE__ */ new Set()) {
|
|
1314
1309
|
if (!obj || typeof obj !== "object") return obj;
|
|
@@ -1355,7 +1350,8 @@ function buildResponseSchema(operation, spec) {
|
|
|
1355
1350
|
const ordered = [];
|
|
1356
1351
|
if (codes.includes("200")) ordered.push("200");
|
|
1357
1352
|
if (codes.includes("201")) ordered.push("201");
|
|
1358
|
-
|
|
1353
|
+
codes.sort((a, b) => a.localeCompare(b));
|
|
1354
|
+
for (const code of codes) {
|
|
1359
1355
|
if (/^2\d\d$/.test(code) && !ordered.includes(code)) ordered.push(code);
|
|
1360
1356
|
}
|
|
1361
1357
|
for (const code of codes) {
|
|
@@ -1468,7 +1464,7 @@ function buildPathParamRows(operation) {
|
|
|
1468
1464
|
});
|
|
1469
1465
|
}
|
|
1470
1466
|
function rewritePathTemplate(url) {
|
|
1471
|
-
return url.
|
|
1467
|
+
return url.replaceAll(/\{([^/{}]+)\}/g, (_m, name) => `{{${name}}}`);
|
|
1472
1468
|
}
|
|
1473
1469
|
function buildHeaders(operation) {
|
|
1474
1470
|
return (operation.parameters ?? []).filter((p) => p.in === "header").map((p) => ({
|
|
@@ -1638,7 +1634,7 @@ function parseBody(body) {
|
|
|
1638
1634
|
return { mode: "none" };
|
|
1639
1635
|
}
|
|
1640
1636
|
function parseAuth(auth) {
|
|
1641
|
-
if (!auth
|
|
1637
|
+
if (!auth?.type || auth.type === "none") return { type: "none" };
|
|
1642
1638
|
if (auth.type === "bearer") {
|
|
1643
1639
|
return { type: "bearer", token: auth.token ?? "" };
|
|
1644
1640
|
}
|
|
@@ -1883,7 +1879,7 @@ const SPECTOR_TO_HTTP = {
|
|
|
1883
1879
|
$isoTimestamp: "$datetime iso8601"
|
|
1884
1880
|
};
|
|
1885
1881
|
function mapDynamicVars(s) {
|
|
1886
|
-
return s.
|
|
1882
|
+
return s.replaceAll(/\{\{\s*(\$[A-Za-z]+)[^}]*\}\}/g, (_m, name) => `{{${HTTP_TO_SPECTOR[name] ?? name}}}`);
|
|
1887
1883
|
}
|
|
1888
1884
|
function contentTypeOf(headers) {
|
|
1889
1885
|
return headers.find((h) => h.key.toLowerCase() === "content-type")?.value.toLowerCase() ?? "";
|
|
@@ -1921,7 +1917,7 @@ function extractAuth(headers) {
|
|
|
1921
1917
|
}
|
|
1922
1918
|
function stripScriptBlocks(bodyLines2) {
|
|
1923
1919
|
const joined = bodyLines2.join("\n");
|
|
1924
|
-
return joined.
|
|
1920
|
+
return joined.replaceAll(/[<>]\s*\{%[\s\S]*?%\}/g, "").replaceAll(/^\s*>\s+\S.*$/gm, "").replaceAll(/^\s*<\s+\S.*$/gm, "").trim();
|
|
1925
1921
|
}
|
|
1926
1922
|
function splitBlocks(text) {
|
|
1927
1923
|
const blocks = [];
|
|
@@ -1953,7 +1949,7 @@ function parseBlock(block) {
|
|
|
1953
1949
|
continue;
|
|
1954
1950
|
}
|
|
1955
1951
|
if (t.startsWith("#") || t.startsWith("//")) continue;
|
|
1956
|
-
if (
|
|
1952
|
+
if (/^@\w+\s*=/.test(t)) continue;
|
|
1957
1953
|
break;
|
|
1958
1954
|
}
|
|
1959
1955
|
if (i >= lines.length) return null;
|
|
@@ -1994,7 +1990,7 @@ function parseBlock(block) {
|
|
|
1994
1990
|
function parseHttpFile(text, name) {
|
|
1995
1991
|
const collectionVariables = {};
|
|
1996
1992
|
for (const line of text.split(/\r?\n/)) {
|
|
1997
|
-
const m = /^@(
|
|
1993
|
+
const m = /^@(\w+)\s*=\s*(.*)$/.exec(line);
|
|
1998
1994
|
if (m) collectionVariables[m[1]] = mapDynamicVars(m[2].trim());
|
|
1999
1995
|
}
|
|
2000
1996
|
const rootFolder = { id: uuid.v4(), name: "root", description: "", folders: [], requestIds: [] };
|
|
@@ -2084,7 +2080,7 @@ function registerImportHandlers(ipc) {
|
|
|
2084
2080
|
throw new Error("Not a valid JSON file.");
|
|
2085
2081
|
}
|
|
2086
2082
|
if (Array.isArray(parsed["collections"])) {
|
|
2087
|
-
throw new
|
|
2083
|
+
throw new TypeError("This is a workspace file, not a collection. Open it with File > Open Workspace instead.");
|
|
2088
2084
|
}
|
|
2089
2085
|
if (!parsed["rootFolder"] || !parsed["requests"]) {
|
|
2090
2086
|
throw new Error("This does not look like an API Spector collection (no rootFolder / requests).");
|
|
@@ -2115,34 +2111,34 @@ function parsePostScript(script) {
|
|
|
2115
2111
|
while (match = testBlockRegex.exec(script)) {
|
|
2116
2112
|
const name = match[2];
|
|
2117
2113
|
const body = match[3];
|
|
2118
|
-
const equalMatch =
|
|
2114
|
+
const equalMatch = /sp\.expect\(([^)]+)\)\.to(?:\.be)?\.equal\(([^)]+)\)/.exec(body);
|
|
2119
2115
|
if (equalMatch) {
|
|
2120
2116
|
assertions.push({ name, accessor: equalMatch[1].trim(), kind: "equals", expected: equalMatch[2].trim() });
|
|
2121
2117
|
continue;
|
|
2122
2118
|
}
|
|
2123
|
-
const includeMatch =
|
|
2119
|
+
const includeMatch = /sp\.expect\(([^)]+)\)\.to\.(?:include|contain)\(([^)]+)\)/.exec(body);
|
|
2124
2120
|
if (includeMatch) {
|
|
2125
2121
|
assertions.push({ name, accessor: includeMatch[1].trim(), kind: "contains", expected: includeMatch[2].trim() });
|
|
2126
2122
|
continue;
|
|
2127
2123
|
}
|
|
2128
|
-
const existsMatch =
|
|
2124
|
+
const existsMatch = /sp\.expect\(([^)]+)\)\.to\.not\.be\.oneOf\(\[null,\s*undefined\]\)/.exec(body);
|
|
2129
2125
|
if (existsMatch) {
|
|
2130
2126
|
assertions.push({ name, accessor: existsMatch[1].trim(), kind: "exists" });
|
|
2131
2127
|
continue;
|
|
2132
2128
|
}
|
|
2133
|
-
const typeMatch =
|
|
2129
|
+
const typeMatch = /sp\.expect\(([^)]+)\)\.to\.be\.a\(\s*"([^"]+)"\s*\)/.exec(body);
|
|
2134
2130
|
if (typeMatch) {
|
|
2135
2131
|
assertions.push({ name, accessor: typeMatch[1].trim(), kind: "type", expected: `"${typeMatch[2]}"` });
|
|
2136
2132
|
continue;
|
|
2137
2133
|
}
|
|
2138
|
-
const aboveMatch =
|
|
2134
|
+
const aboveMatch = /sp\.expect\(([^)]+)\)\.to\.be\.above\((\d+)\)/.exec(body);
|
|
2139
2135
|
if (aboveMatch) {
|
|
2140
2136
|
assertions.push({ name, accessor: aboveMatch[1].trim(), kind: "above", expected: aboveMatch[2] });
|
|
2141
2137
|
continue;
|
|
2142
2138
|
}
|
|
2143
2139
|
assertions.push({ name, accessor: "", kind: "status" });
|
|
2144
2140
|
}
|
|
2145
|
-
const statusMatch =
|
|
2141
|
+
const statusMatch = /sp\.response\.to\.have\.status\((\d+)\)/.exec(script);
|
|
2146
2142
|
if (statusMatch) {
|
|
2147
2143
|
assertions.push({ name: `status is ${statusMatch[1]}`, accessor: "", kind: "status", expected: statusMatch[1] });
|
|
2148
2144
|
}
|
|
@@ -2151,23 +2147,23 @@ function parsePostScript(script) {
|
|
|
2151
2147
|
const target = match[1];
|
|
2152
2148
|
const varName = match[2];
|
|
2153
2149
|
let accessor = match[3].trim();
|
|
2154
|
-
const stringWrap =
|
|
2150
|
+
const stringWrap = /^String\((.+)\)$/.exec(accessor);
|
|
2155
2151
|
if (stringWrap) accessor = stringWrap[1];
|
|
2156
2152
|
extractions.push({ varName, accessor, target });
|
|
2157
2153
|
}
|
|
2158
2154
|
return { assertions, extractions };
|
|
2159
2155
|
}
|
|
2160
2156
|
function accessorToJsonPath(accessor) {
|
|
2161
|
-
return accessor.replace(/^json\.?/, "").
|
|
2157
|
+
return accessor.replace(/^json\.?/, "").replaceAll(/\["([^"]+)"\]/g, "['$1']");
|
|
2162
2158
|
}
|
|
2163
2159
|
function slug(name) {
|
|
2164
|
-
return name.
|
|
2160
|
+
return name.replaceAll(/\W+/g, "-").toLowerCase().replaceAll(/^-|-$/g, "");
|
|
2165
2161
|
}
|
|
2166
2162
|
function toEnvVar(key) {
|
|
2167
|
-
return key.
|
|
2163
|
+
return key.replaceAll(/\W+/g, "_").toUpperCase();
|
|
2168
2164
|
}
|
|
2169
2165
|
function interpolateEnvVars(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
2170
|
-
return value.
|
|
2166
|
+
return value.replaceAll(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
2171
2167
|
const envKey = toEnvVar(key.trim());
|
|
2172
2168
|
return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
|
|
2173
2169
|
});
|
|
@@ -2216,7 +2212,7 @@ function buildNameMap$1(folder, requests) {
|
|
|
2216
2212
|
return map;
|
|
2217
2213
|
}
|
|
2218
2214
|
function javaClass(name) {
|
|
2219
|
-
return name.
|
|
2215
|
+
return name.replaceAll(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
2220
2216
|
}
|
|
2221
2217
|
function resolveEffectiveAuth(req, inherited) {
|
|
2222
2218
|
return requestCollection.authIsConfigured(req.auth) ? req.auth : inherited.auth ?? req.auth;
|
|
@@ -2234,7 +2230,7 @@ function getEnvBaseUrl(environment, fallback) {
|
|
|
2234
2230
|
}
|
|
2235
2231
|
function renderTree(paths) {
|
|
2236
2232
|
const root = {};
|
|
2237
|
-
for (const p of [...paths].sort()) {
|
|
2233
|
+
for (const p of [...paths].sort((a, b) => a.localeCompare(b))) {
|
|
2238
2234
|
let cur = root;
|
|
2239
2235
|
for (const part of p.split("/")) {
|
|
2240
2236
|
cur = cur[part] ??= {};
|
|
@@ -2259,16 +2255,16 @@ function restAssuredCall(method, pathArg) {
|
|
|
2259
2255
|
}
|
|
2260
2256
|
const ROBOT_REQUESTS_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
2261
2257
|
function safeName(name) {
|
|
2262
|
-
return name.
|
|
2258
|
+
return name.replaceAll(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2263
2259
|
}
|
|
2264
2260
|
function robotVar(key) {
|
|
2265
|
-
return "${" + key.
|
|
2261
|
+
return "${" + key.replaceAll(/\W+/g, "_").toUpperCase() + "}";
|
|
2266
2262
|
}
|
|
2267
2263
|
function envVar(key) {
|
|
2268
|
-
return "%{" + key.
|
|
2264
|
+
return "%{" + key.replaceAll(/\W+/g, "_").toUpperCase() + "}";
|
|
2269
2265
|
}
|
|
2270
2266
|
function interpolate(value, vars) {
|
|
2271
|
-
return value.
|
|
2267
|
+
return value.replaceAll(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
2272
2268
|
const v = vars.get(key.trim());
|
|
2273
2269
|
return v?.secret ? envVar(key.trim()) : robotVar(key.trim());
|
|
2274
2270
|
});
|
|
@@ -2298,8 +2294,7 @@ function buildNameMap(root, requests) {
|
|
|
2298
2294
|
function buildVariablesFile(environment) {
|
|
2299
2295
|
const lines = ["*** Variables ***"];
|
|
2300
2296
|
if (!environment) {
|
|
2301
|
-
lines.push("# No environment - add your variables here");
|
|
2302
|
-
lines.push("${BASE_URL} http://localhost:8080");
|
|
2297
|
+
lines.push("# No environment - add your variables here", "${BASE_URL} http://localhost:8080");
|
|
2303
2298
|
return lines.join("\n") + "\n";
|
|
2304
2299
|
}
|
|
2305
2300
|
for (const v of environment.variables) {
|
|
@@ -2340,12 +2335,9 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
2340
2335
|
if (!kwName) continue;
|
|
2341
2336
|
const url = interpolate(req.url, varMap);
|
|
2342
2337
|
const method = req.method.charAt(0) + req.method.slice(1).toLowerCase();
|
|
2343
|
-
lines.push(kwName);
|
|
2344
|
-
lines.push(` [Documentation] Hook: ${req.hookType} - ${req.name}`);
|
|
2338
|
+
lines.push(kwName, ` [Documentation] Hook: ${req.hookType} - ${req.name}`);
|
|
2345
2339
|
if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
|
|
2346
|
-
lines.push(` Log ${req.method} is not supported by robotframework-requests - hook skipped WARN`);
|
|
2347
|
-
lines.push(` RETURN \${None}`);
|
|
2348
|
-
lines.push("");
|
|
2340
|
+
lines.push(` Log ${req.method} is not supported by robotframework-requests - hook skipped WARN`, ` RETURN \${None}`, "");
|
|
2349
2341
|
continue;
|
|
2350
2342
|
}
|
|
2351
2343
|
const { body } = req;
|
|
@@ -2364,13 +2356,11 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
2364
2356
|
const parsed = parsePostScript(req.postRequestScript);
|
|
2365
2357
|
for (const e of parsed.extractions) {
|
|
2366
2358
|
const jp = accessorToJsonPath(e.accessor);
|
|
2367
|
-
const rfVar = e.varName.
|
|
2359
|
+
const rfVar = e.varName.replaceAll(/\W+/g, "_").toUpperCase();
|
|
2368
2360
|
hookExtractedVars.add(rfVar);
|
|
2369
|
-
lines.push(` \${${rfVar}}= Evaluate str($response.json().get('${jp}', ''))`);
|
|
2370
|
-
lines.push(` Set Suite Variable \${${rfVar}}`);
|
|
2361
|
+
lines.push(` \${${rfVar}}= Evaluate str($response.json().get('${jp}', ''))`, ` Set Suite Variable \${${rfVar}}`);
|
|
2371
2362
|
}
|
|
2372
|
-
lines.push(` RETURN \${response}
|
|
2373
|
-
lines.push("");
|
|
2363
|
+
lines.push(` RETURN \${response}`, "");
|
|
2374
2364
|
}
|
|
2375
2365
|
for (const sub of folder.folders) processHooks(sub);
|
|
2376
2366
|
}
|
|
@@ -2381,12 +2371,9 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
2381
2371
|
if (!req || req.disabled || req.hookType) continue;
|
|
2382
2372
|
const kwName = nameMap.get(reqId);
|
|
2383
2373
|
const url = interpolate(req.url, varMap);
|
|
2384
|
-
lines.push(kwName);
|
|
2385
|
-
lines.push(` [Documentation] ${req.description || req.name}`);
|
|
2374
|
+
lines.push(kwName, ` [Documentation] ${req.description || req.name}`);
|
|
2386
2375
|
if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
|
|
2387
|
-
lines.push(` Log ${req.method} is not supported by robotframework-requests - request skipped WARN`);
|
|
2388
|
-
lines.push(` RETURN \${None}`);
|
|
2389
|
-
lines.push("");
|
|
2376
|
+
lines.push(` Log ${req.method} is not supported by robotframework-requests - request skipped WARN`, ` RETURN \${None}`, "");
|
|
2390
2377
|
continue;
|
|
2391
2378
|
}
|
|
2392
2379
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
|
|
@@ -2396,8 +2383,8 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
2396
2383
|
if (effectiveAuth.type === "bearer") {
|
|
2397
2384
|
const token = effectiveAuth.token ?? "";
|
|
2398
2385
|
if (token.includes("{{")) {
|
|
2399
|
-
const varRef =
|
|
2400
|
-
const rfVar = varRef ? varRef.
|
|
2386
|
+
const varRef = /\{\{([^}]+)\}\}/.exec(token)?.[1]?.trim();
|
|
2387
|
+
const rfVar = varRef ? varRef.replaceAll(/\W+/g, "_").toUpperCase() : "";
|
|
2401
2388
|
if (rfVar && hookExtractedVars.has(rfVar)) {
|
|
2402
2389
|
headerPairs.push(`Authorization=Bearer \${${rfVar}}`);
|
|
2403
2390
|
} else {
|
|
@@ -2446,8 +2433,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
2446
2433
|
if (callArgs.length) {
|
|
2447
2434
|
lines.push(` ... ${callArgs.join(" ")}`);
|
|
2448
2435
|
}
|
|
2449
|
-
lines.push(` RETURN \${response}
|
|
2450
|
-
lines.push("");
|
|
2436
|
+
lines.push(` RETURN \${response}`, "");
|
|
2451
2437
|
}
|
|
2452
2438
|
for (const sub of folder.folders) processFolder(sub);
|
|
2453
2439
|
}
|
|
@@ -2485,9 +2471,7 @@ function buildTestSuite(collection, environment, nameMap) {
|
|
|
2485
2471
|
const req = collection.requests[reqId];
|
|
2486
2472
|
if (!req || req.disabled || req.hookType) continue;
|
|
2487
2473
|
const kwName = nameMap.get(reqId);
|
|
2488
|
-
lines.push(kwName);
|
|
2489
|
-
lines.push(` [Documentation] ${req.description || req.name}`);
|
|
2490
|
-
lines.push(` \${response}= ${kwName}`);
|
|
2474
|
+
lines.push(kwName, ` [Documentation] ${req.description || req.name}`, ` \${response}= ${kwName}`);
|
|
2491
2475
|
const parsed = parsePostScript(req.postRequestScript);
|
|
2492
2476
|
if (parsed.assertions.length > 0) {
|
|
2493
2477
|
for (const a of parsed.assertions) {
|
|
@@ -2497,15 +2481,13 @@ function buildTestSuite(collection, environment, nameMap) {
|
|
|
2497
2481
|
lines.push(` Status Should Be ${a.expected ?? 200} \${response}`);
|
|
2498
2482
|
break;
|
|
2499
2483
|
case "equals": {
|
|
2500
|
-
const expected = a.expected?.
|
|
2501
|
-
lines.push(` \${value}= Get From Dictionary \${response.json()} ${jp}`);
|
|
2502
|
-
lines.push(` Should Be Equal As Strings \${value} ${expected}`);
|
|
2484
|
+
const expected = a.expected?.replaceAll(/^"|"$/g, "") ?? "";
|
|
2485
|
+
lines.push(` \${value}= Get From Dictionary \${response.json()} ${jp}`, ` Should Be Equal As Strings \${value} ${expected}`);
|
|
2503
2486
|
break;
|
|
2504
2487
|
}
|
|
2505
2488
|
case "contains": {
|
|
2506
|
-
const expected = a.expected?.
|
|
2507
|
-
lines.push(` \${value}= Get From Dictionary \${response.json()} ${jp}`);
|
|
2508
|
-
lines.push(` Should Contain \${value} ${expected}`);
|
|
2489
|
+
const expected = a.expected?.replaceAll(/^"|"$/g, "") ?? "";
|
|
2490
|
+
lines.push(` \${value}= Get From Dictionary \${response.json()} ${jp}`, ` Should Contain \${value} ${expected}`);
|
|
2509
2491
|
break;
|
|
2510
2492
|
}
|
|
2511
2493
|
case "exists":
|
|
@@ -2520,8 +2502,7 @@ function buildTestSuite(collection, environment, nameMap) {
|
|
|
2520
2502
|
}
|
|
2521
2503
|
for (const e of parsed.extractions) {
|
|
2522
2504
|
const jp = accessorToJsonPath(e.accessor);
|
|
2523
|
-
lines.push(` \${${e.varName}}= Get From Dictionary \${response.json()} ${jp}`);
|
|
2524
|
-
lines.push(` Set Suite Variable \${${e.varName}}`);
|
|
2505
|
+
lines.push(` \${${e.varName}}= Get From Dictionary \${response.json()} ${jp}`, ` Set Suite Variable \${${e.varName}}`);
|
|
2525
2506
|
}
|
|
2526
2507
|
lines.push("");
|
|
2527
2508
|
}
|
|
@@ -2561,7 +2542,7 @@ function generateRobotFramework(collection, environment) {
|
|
|
2561
2542
|
(environment?.variables ?? []).map((v) => [v.key, v])
|
|
2562
2543
|
);
|
|
2563
2544
|
const nameMap = buildNameMap(collection.rootFolder, collection.requests);
|
|
2564
|
-
const slug2 = collection.name.
|
|
2545
|
+
const slug2 = collection.name.replaceAll(/\W+/g, "_").toLowerCase();
|
|
2565
2546
|
const hookExtractedVars = /* @__PURE__ */ new Set();
|
|
2566
2547
|
const contentFiles = [
|
|
2567
2548
|
{ path: "resources/variables.resource", content: buildVariablesFile(environment) },
|
|
@@ -2628,8 +2609,7 @@ function buildHookLines$1(req, sharedVars) {
|
|
|
2628
2609
|
lines.push(` // ${req.name}`);
|
|
2629
2610
|
const parsed = parsePostScript(req.postRequestScript);
|
|
2630
2611
|
if (parsed.extractions.length > 0) {
|
|
2631
|
-
lines.push(` const hookResponse = await ${hookCall};`);
|
|
2632
|
-
lines.push(` const hookJson = await hookResponse.json();`);
|
|
2612
|
+
lines.push(` const hookResponse = await ${hookCall};`, ` const hookJson = await hookResponse.json();`);
|
|
2633
2613
|
for (const e of parsed.extractions) {
|
|
2634
2614
|
const jsonPath = e.accessor.replace(/^json\.?/, "");
|
|
2635
2615
|
const expr = jsonPath ? `hookJson.${jsonPath}` : "hookJson";
|
|
@@ -2754,8 +2734,7 @@ ${optionParts.join(",\n")},
|
|
|
2754
2734
|
lines.push(` const json = await response.json();`);
|
|
2755
2735
|
}
|
|
2756
2736
|
if (req.schema?.trim()) {
|
|
2757
|
-
lines.push(` // JSON Schema validation`);
|
|
2758
|
-
lines.push(` // Schema: ${req.schema.replace(/\n/g, " ").slice(0, 80)}...`);
|
|
2737
|
+
lines.push(` // JSON Schema validation`, ` // Schema: ${req.schema.replaceAll("\n", " ").slice(0, 80)}...`);
|
|
2759
2738
|
}
|
|
2760
2739
|
if (parsed.assertions.length > 0) {
|
|
2761
2740
|
for (const a of parsed.assertions) {
|
|
@@ -2792,8 +2771,7 @@ ${optionParts.join(",\n")},
|
|
|
2792
2771
|
for (const e of parsed.extractions) {
|
|
2793
2772
|
const path22 = e.accessor.replace(/^json\.?/, "");
|
|
2794
2773
|
const expr = path22 ? `json.${path22}` : "json";
|
|
2795
|
-
lines.push(` // Extract: ${e.varName} = ${expr}`);
|
|
2796
|
-
lines.push(` process.env.${toEnvVar(e.varName)} = String(${expr});`);
|
|
2774
|
+
lines.push(` // Extract: ${e.varName} = ${expr}`, ` process.env.${toEnvVar(e.varName)} = String(${expr});`);
|
|
2797
2775
|
}
|
|
2798
2776
|
lines.push(` });`);
|
|
2799
2777
|
tests.push(lines.join("\n"));
|
|
@@ -2808,7 +2786,7 @@ ${tests.join("\n\n")}
|
|
|
2808
2786
|
`;
|
|
2809
2787
|
}
|
|
2810
2788
|
function buildPackageJson$3(collectionName) {
|
|
2811
|
-
const name = collectionName.
|
|
2789
|
+
const name = collectionName.replaceAll(/\W+/g, "-").toLowerCase();
|
|
2812
2790
|
return JSON.stringify({
|
|
2813
2791
|
name: `${name}-api-tests`,
|
|
2814
2792
|
version: "1.0.0",
|
|
@@ -2913,8 +2891,7 @@ function buildHookLines(req, sharedVars) {
|
|
|
2913
2891
|
const lines = [` // ${req.name}`];
|
|
2914
2892
|
const parsed = parsePostScript(req.postRequestScript);
|
|
2915
2893
|
if (parsed.extractions.length > 0) {
|
|
2916
|
-
lines.push(` const hookResponse = await ${hookCall};`);
|
|
2917
|
-
lines.push(` const hookJson = await hookResponse.json();`);
|
|
2894
|
+
lines.push(` const hookResponse = await ${hookCall};`, ` const hookJson = await hookResponse.json();`);
|
|
2918
2895
|
for (const e of parsed.extractions) {
|
|
2919
2896
|
const jp = e.accessor.replace(/^json\.?/, "");
|
|
2920
2897
|
const expr = jp ? `hookJson.${jp}` : "hookJson";
|
|
@@ -3082,7 +3059,7 @@ ${tests.join("\n\n")}
|
|
|
3082
3059
|
`;
|
|
3083
3060
|
}
|
|
3084
3061
|
function buildPackageJson$2(collectionName) {
|
|
3085
|
-
const name = collectionName.
|
|
3062
|
+
const name = collectionName.replaceAll(/\W+/g, "-").toLowerCase();
|
|
3086
3063
|
return JSON.stringify({
|
|
3087
3064
|
name: `${name}-api-tests`,
|
|
3088
3065
|
version: "1.0.0",
|
|
@@ -3160,7 +3137,7 @@ import * as dotenv from 'dotenv'
|
|
|
3160
3137
|
dotenv.config({ path: '.env.local' });
|
|
3161
3138
|
|
|
3162
3139
|
// Add secret env vars to .env.local:
|
|
3163
|
-
${envComments ? `// ${envComments.
|
|
3140
|
+
${envComments ? `// ${envComments.replaceAll("\n", "\n// ")}
|
|
3164
3141
|
` : ""}export const BASE_URL = process.env.BASE_URL ?? '${baseUrl}';
|
|
3165
3142
|
|
|
3166
3143
|
export const api = supertest(BASE_URL);
|
|
@@ -3240,15 +3217,11 @@ ${lines.join("\n")}
|
|
|
3240
3217
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
3241
3218
|
const lines = [];
|
|
3242
3219
|
if (!SUPERTEST_VERBS.includes(method)) {
|
|
3243
|
-
lines.push(` // ${req.method} (RFC 10008) is not supported by supertest`);
|
|
3244
|
-
lines.push(` it.skip('${nameMap.get(reqId)} [${req.method} unsupported]', () => {});`);
|
|
3245
|
-
lines.push("");
|
|
3220
|
+
lines.push(` // ${req.method} (RFC 10008) is not supported by supertest`, ` it.skip('${nameMap.get(reqId)} [${req.method} unsupported]', () => {});`, "");
|
|
3246
3221
|
tests.push(...lines);
|
|
3247
3222
|
continue;
|
|
3248
3223
|
}
|
|
3249
|
-
lines.push(` it('${nameMap.get(reqId)}', async () => {`);
|
|
3250
|
-
lines.push(` const res = await api`);
|
|
3251
|
-
lines.push(` .${method}(\`${path2}\`)`);
|
|
3224
|
+
lines.push(` it('${nameMap.get(reqId)}', async () => {`, ` const res = await api`, ` .${method}(\`${path2}\`)`);
|
|
3252
3225
|
if (effectiveAuth.type === "bearer") {
|
|
3253
3226
|
const token = effectiveAuth.token ?? "";
|
|
3254
3227
|
if (token.includes("{{")) {
|
|
@@ -3275,8 +3248,7 @@ ${lines.join("\n")}
|
|
|
3275
3248
|
}
|
|
3276
3249
|
} else if (req.body.mode === "form" && req.body.form) {
|
|
3277
3250
|
const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${interpolateEnvVars(p.value, sharedVars)}\``).join(", ");
|
|
3278
|
-
lines.push(` .type('form')`);
|
|
3279
|
-
lines.push(` .send({ ${pairs} })`);
|
|
3251
|
+
lines.push(` .type('form')`, ` .send({ ${pairs} })`);
|
|
3280
3252
|
}
|
|
3281
3253
|
}
|
|
3282
3254
|
lines[lines.length - 1] += ";";
|
|
@@ -3326,7 +3298,7 @@ ${tests.join("\n\n")}
|
|
|
3326
3298
|
`;
|
|
3327
3299
|
}
|
|
3328
3300
|
function buildPackageJson$1(collectionName) {
|
|
3329
|
-
const name = collectionName.
|
|
3301
|
+
const name = collectionName.replaceAll(/\W+/g, "-").toLowerCase();
|
|
3330
3302
|
return JSON.stringify({
|
|
3331
3303
|
name: `${name}-api-tests`,
|
|
3332
3304
|
version: "1.0.0",
|
|
@@ -3424,7 +3396,7 @@ function buildClient(environment) {
|
|
|
3424
3396
|
require('dotenv').config({ path: '.env.local' });
|
|
3425
3397
|
|
|
3426
3398
|
// Add secret env vars to .env.local:
|
|
3427
|
-
${envComments ? `// ${envComments.
|
|
3399
|
+
${envComments ? `// ${envComments.replaceAll("\n", "\n// ")}
|
|
3428
3400
|
` : ""}const BASE_URL = process.env.BASE_URL ?? '${baseUrl}';
|
|
3429
3401
|
|
|
3430
3402
|
module.exports.api = supertest(BASE_URL);
|
|
@@ -3504,15 +3476,11 @@ ${lines.join("\n")}
|
|
|
3504
3476
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
3505
3477
|
const lines = [];
|
|
3506
3478
|
if (!SUPERTEST_VERBS.includes(method)) {
|
|
3507
|
-
lines.push(` // ${req.method} (RFC 10008) is not supported by supertest`);
|
|
3508
|
-
lines.push(` it.skip('${nameMap.get(reqId)} [${req.method} unsupported]', () => {});`);
|
|
3509
|
-
lines.push("");
|
|
3479
|
+
lines.push(` // ${req.method} (RFC 10008) is not supported by supertest`, ` it.skip('${nameMap.get(reqId)} [${req.method} unsupported]', () => {});`, "");
|
|
3510
3480
|
tests.push(...lines);
|
|
3511
3481
|
continue;
|
|
3512
3482
|
}
|
|
3513
|
-
lines.push(` it('${nameMap.get(reqId)}', async () => {`);
|
|
3514
|
-
lines.push(` const res = await api`);
|
|
3515
|
-
lines.push(` .${method}(\`${path2}\`)`);
|
|
3483
|
+
lines.push(` it('${nameMap.get(reqId)}', async () => {`, ` const res = await api`, ` .${method}(\`${path2}\`)`);
|
|
3516
3484
|
if (effectiveAuth.type === "bearer") {
|
|
3517
3485
|
const token = effectiveAuth.token ?? "";
|
|
3518
3486
|
if (token.includes("{{")) {
|
|
@@ -3539,8 +3507,7 @@ ${lines.join("\n")}
|
|
|
3539
3507
|
}
|
|
3540
3508
|
} else if (req.body.mode === "form" && req.body.form) {
|
|
3541
3509
|
const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${interpolateEnvVars(p.value, sharedVars)}\``).join(", ");
|
|
3542
|
-
lines.push(` .type('form')`);
|
|
3543
|
-
lines.push(` .send({ ${pairs} })`);
|
|
3510
|
+
lines.push(` .type('form')`, ` .send({ ${pairs} })`);
|
|
3544
3511
|
}
|
|
3545
3512
|
}
|
|
3546
3513
|
lines[lines.length - 1] += ";";
|
|
@@ -3590,7 +3557,7 @@ ${tests.join("\n\n")}
|
|
|
3590
3557
|
`;
|
|
3591
3558
|
}
|
|
3592
3559
|
function buildPackageJson(collectionName) {
|
|
3593
|
-
const name = collectionName.
|
|
3560
|
+
const name = collectionName.replaceAll(/\W+/g, "-").toLowerCase();
|
|
3594
3561
|
return JSON.stringify({
|
|
3595
3562
|
name: `${name}-api-tests`,
|
|
3596
3563
|
version: "1.0.0",
|
|
@@ -3653,11 +3620,11 @@ function generateSupertestJs(collection, environment) {
|
|
|
3653
3620
|
return files;
|
|
3654
3621
|
}
|
|
3655
3622
|
function javaMethod(name) {
|
|
3656
|
-
const parts = name.
|
|
3623
|
+
const parts = name.replaceAll(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean);
|
|
3657
3624
|
return parts[0].toLowerCase() + parts.slice(1).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
3658
3625
|
}
|
|
3659
3626
|
function javaTypeFor(expected) {
|
|
3660
|
-
switch (expected?.
|
|
3627
|
+
switch (expected?.replaceAll('"', "")) {
|
|
3661
3628
|
case "string":
|
|
3662
3629
|
return "String.class";
|
|
3663
3630
|
case "number":
|
|
@@ -3669,7 +3636,7 @@ function javaTypeFor(expected) {
|
|
|
3669
3636
|
}
|
|
3670
3637
|
}
|
|
3671
3638
|
function interpolateJava(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
3672
|
-
return '"' + value.
|
|
3639
|
+
return '"' + value.replaceAll(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
3673
3640
|
const envKey = toEnvVar(key.trim());
|
|
3674
3641
|
if (sharedVars.has(envKey)) {
|
|
3675
3642
|
return `" + ${envKey} + "`;
|
|
@@ -3678,7 +3645,7 @@ function interpolateJava(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
|
3678
3645
|
}) + '"';
|
|
3679
3646
|
}
|
|
3680
3647
|
function buildPom$1(collectionName) {
|
|
3681
|
-
const artifact = collectionName.
|
|
3648
|
+
const artifact = collectionName.replaceAll(/\W+/g, "-").toLowerCase();
|
|
3682
3649
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3683
3650
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
3684
3651
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
@@ -3801,7 +3768,7 @@ function buildTestClass(folderName, folder, collection) {
|
|
|
3801
3768
|
if (parsed.extractions.length > 0) {
|
|
3802
3769
|
lines.push(` var hookResponse = given().spec(requestSpec)`);
|
|
3803
3770
|
if (h.body.mode === "json" && h.body.json) {
|
|
3804
|
-
const escaped = h.body.json.
|
|
3771
|
+
const escaped = h.body.json.replaceAll('"', '\\"').replaceAll("\n", "\\n");
|
|
3805
3772
|
lines.push(` .body("${escaped}")`);
|
|
3806
3773
|
}
|
|
3807
3774
|
lines.push(` .when()${restAssuredCall(method, `"${path2}"`)};`);
|
|
@@ -3857,14 +3824,11 @@ ${lines.join("\n")}
|
|
|
3857
3824
|
const allHeaders = mergeHeaders(req, inherited);
|
|
3858
3825
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
3859
3826
|
const lines = [];
|
|
3860
|
-
lines.push(` @Test`);
|
|
3861
|
-
lines.push(` public void ${methodName}() {`);
|
|
3862
|
-
lines.push(` given()`);
|
|
3863
|
-
lines.push(` .spec(requestSpec)`);
|
|
3827
|
+
lines.push(` @Test`, ` public void ${methodName}() {`, ` given()`, ` .spec(requestSpec)`);
|
|
3864
3828
|
if (effectiveAuth.type === "bearer") {
|
|
3865
3829
|
const token = effectiveAuth.token ?? "";
|
|
3866
3830
|
if (token.includes("{{")) {
|
|
3867
|
-
const varRef =
|
|
3831
|
+
const varRef = /\{\{([^}]+)\}\}/.exec(token)?.[1]?.trim();
|
|
3868
3832
|
const envKey = varRef ? toEnvVar(varRef) : "";
|
|
3869
3833
|
if (envKey && sharedVars.has(envKey)) {
|
|
3870
3834
|
lines.push(` .header("Authorization", "Bearer " + ${envKey})`);
|
|
@@ -3883,13 +3847,11 @@ ${lines.join("\n")}
|
|
|
3883
3847
|
}
|
|
3884
3848
|
if (hasBody(req)) {
|
|
3885
3849
|
if (req.body.mode === "json" && req.body.json) {
|
|
3886
|
-
const escaped = req.body.json.
|
|
3850
|
+
const escaped = req.body.json.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n");
|
|
3887
3851
|
lines.push(` .body(${interpolateJava(escaped, sharedVars)})`);
|
|
3888
3852
|
}
|
|
3889
3853
|
}
|
|
3890
|
-
lines.push(` .when()`);
|
|
3891
|
-
lines.push(` ${restAssuredCall(method, javaPath)}`);
|
|
3892
|
-
lines.push(` .then()`);
|
|
3854
|
+
lines.push(` .when()`, ` ${restAssuredCall(method, javaPath)}`, ` .then()`);
|
|
3893
3855
|
const parsed = parsePostScript(req.postRequestScript);
|
|
3894
3856
|
if (parsed.assertions.length > 0) {
|
|
3895
3857
|
for (const a of parsed.assertions) {
|
|
@@ -3899,11 +3861,7 @@ ${lines.join("\n")}
|
|
|
3899
3861
|
lines.push(` .statusCode(${a.expected ?? 200})`);
|
|
3900
3862
|
break;
|
|
3901
3863
|
case "equals":
|
|
3902
|
-
|
|
3903
|
-
lines.push(` .body("${jp}", equalTo(${a.expected}))`);
|
|
3904
|
-
} else {
|
|
3905
|
-
lines.push(` .body("${jp}", equalTo(${a.expected}))`);
|
|
3906
|
-
}
|
|
3864
|
+
lines.push(` .body("${jp}", equalTo(${a.expected}))`);
|
|
3907
3865
|
break;
|
|
3908
3866
|
case "contains":
|
|
3909
3867
|
lines.push(` .body("${jp}", containsString(${a.expected}))`);
|
|
@@ -3992,19 +3950,19 @@ function generateRestAssured(collection, environment) {
|
|
|
3992
3950
|
return files;
|
|
3993
3951
|
}
|
|
3994
3952
|
function jsVar(name) {
|
|
3995
|
-
const parts = name.
|
|
3953
|
+
const parts = name.replaceAll(/[^a-zA-Z0-9]+/g, " ").split(/\s+/).filter(Boolean).map((p) => p.toLowerCase());
|
|
3996
3954
|
if (parts.length === 0) return "_";
|
|
3997
3955
|
return parts[0] + parts.slice(1).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
3998
3956
|
}
|
|
3999
3957
|
function featureFileName(name) {
|
|
4000
|
-
const slug2 = name.
|
|
3958
|
+
const slug2 = name.replaceAll(/[^\w\s-]/g, "").trim().replaceAll(/\s+/g, "-").toLowerCase();
|
|
4001
3959
|
return slug2 || "tests";
|
|
4002
3960
|
}
|
|
4003
3961
|
function configKey(envKey) {
|
|
4004
3962
|
return jsVar(envKey);
|
|
4005
3963
|
}
|
|
4006
3964
|
function escSingle(s) {
|
|
4007
|
-
return s.
|
|
3965
|
+
return s.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
4008
3966
|
}
|
|
4009
3967
|
function interpolateKarate(value) {
|
|
4010
3968
|
if (!value.includes("{{")) return `'${escSingle(value)}'`;
|
|
@@ -4021,7 +3979,7 @@ function interpolateKarate(value) {
|
|
|
4021
3979
|
return parts.length === 1 ? parts[0] : parts.join(" + ");
|
|
4022
3980
|
}
|
|
4023
3981
|
function urlSteps(url) {
|
|
4024
|
-
const leadingVar =
|
|
3982
|
+
const leadingVar = /^\{\{([^}]+)\}\}(.*)$/.exec(url);
|
|
4025
3983
|
if (leadingVar) {
|
|
4026
3984
|
const baseVar = configKey(leadingVar[1].trim());
|
|
4027
3985
|
const rest2 = leadingVar[2].replace(/^\//, "");
|
|
@@ -4038,7 +3996,7 @@ function urlSteps(url) {
|
|
|
4038
3996
|
return steps;
|
|
4039
3997
|
}
|
|
4040
3998
|
function buildPom(collectionName) {
|
|
4041
|
-
const artifact = collectionName.
|
|
3999
|
+
const artifact = collectionName.replaceAll(/\W+/g, "-").toLowerCase();
|
|
4042
4000
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4043
4001
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
4044
4002
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
@@ -4090,12 +4048,7 @@ function buildPom(collectionName) {
|
|
|
4090
4048
|
function buildKarateConfig(environment) {
|
|
4091
4049
|
const baseUrl = getEnvBaseUrl(environment, "http://localhost:8080");
|
|
4092
4050
|
const lines = [];
|
|
4093
|
-
lines.push(`function fn() {`);
|
|
4094
|
-
lines.push(` var env = karate.env || 'dev';`);
|
|
4095
|
-
lines.push(` karate.log('karate env:', env);`);
|
|
4096
|
-
lines.push(``);
|
|
4097
|
-
lines.push(` var config = {`);
|
|
4098
|
-
lines.push(` baseUrl: '${escSingle(baseUrl)}'`);
|
|
4051
|
+
lines.push(`function fn() {`, ` var env = karate.env || 'dev';`, ` karate.log('karate env:', env);`, ``, ` var config = {`, ` baseUrl: '${escSingle(baseUrl)}'`);
|
|
4099
4052
|
const otherVars = (environment?.variables ?? []).filter((v) => {
|
|
4100
4053
|
const k = v.key.toLowerCase();
|
|
4101
4054
|
return v.enabled && !["base_url", "baseurl", "base-url"].includes(k);
|
|
@@ -4103,24 +4056,13 @@ function buildKarateConfig(environment) {
|
|
|
4103
4056
|
for (const v of otherVars) {
|
|
4104
4057
|
const key = configKey(v.key);
|
|
4105
4058
|
const def = v.secret ? "''" : `'${escSingle(v.value ?? "")}'`;
|
|
4106
|
-
lines.push(
|
|
4107
|
-
|
|
4108
|
-
}
|
|
4109
|
-
lines.push(` };`);
|
|
4110
|
-
lines.push(``);
|
|
4111
|
-
lines.push(` // Allow each variable to be overridden via a process env var of the same`);
|
|
4112
|
-
lines.push(` // SHOUTY_SNAKE_CASE name (e.g. AUTH_TOKEN populates config.authToken).`);
|
|
4113
|
-
lines.push(` function envOverride(name, key) {`);
|
|
4114
|
-
lines.push(` var v = java.lang.System.getenv(name);`);
|
|
4115
|
-
lines.push(` if (v) config[key] = v;`);
|
|
4116
|
-
lines.push(` }`);
|
|
4117
|
-
lines.push(` envOverride('BASE_URL', 'baseUrl');`);
|
|
4059
|
+
lines.push(`,`, ` ${key}: ${def}`);
|
|
4060
|
+
}
|
|
4061
|
+
lines.push(` };`, ``, ` // Allow each variable to be overridden via a process env var of the same`, ` // SHOUTY_SNAKE_CASE name (e.g. AUTH_TOKEN populates config.authToken).`, ` function envOverride(name, key) {`, ` var v = java.lang.System.getenv(name);`, ` if (v) config[key] = v;`, ` }`, ` envOverride('BASE_URL', 'baseUrl');`);
|
|
4118
4062
|
for (const v of otherVars) {
|
|
4119
4063
|
lines.push(` envOverride('${v.key}', '${configKey(v.key)}');`);
|
|
4120
4064
|
}
|
|
4121
|
-
lines.push(
|
|
4122
|
-
lines.push(` return config;`);
|
|
4123
|
-
lines.push(`}`);
|
|
4065
|
+
lines.push(``, ` return config;`, `}`);
|
|
4124
4066
|
return lines.join("\n") + "\n";
|
|
4125
4067
|
}
|
|
4126
4068
|
function buildRunner(collectionName) {
|
|
@@ -4179,7 +4121,7 @@ function buildBackground(folderId, collection) {
|
|
|
4179
4121
|
return { lines, defined };
|
|
4180
4122
|
}
|
|
4181
4123
|
function bodyDocstring(json) {
|
|
4182
|
-
const expanded = json.
|
|
4124
|
+
const expanded = json.replaceAll(/\{\{([^}]+)\}\}/g, (_, k) => `#(${configKey(k.trim())})`);
|
|
4183
4125
|
let pretty = expanded.trim();
|
|
4184
4126
|
try {
|
|
4185
4127
|
pretty = JSON.stringify(JSON.parse(expanded), null, 2);
|
|
@@ -4191,9 +4133,9 @@ function bodyDocstring(json) {
|
|
|
4191
4133
|
return out;
|
|
4192
4134
|
}
|
|
4193
4135
|
function rawDocstring(text) {
|
|
4194
|
-
const expanded = text.
|
|
4136
|
+
const expanded = text.replaceAll(/\{\{([^}]+)\}\}/g, (_, k) => `#(${configKey(k.trim())})`);
|
|
4195
4137
|
const out = [' """'];
|
|
4196
|
-
for (const l of expanded.
|
|
4138
|
+
for (const l of expanded.replaceAll("\r\n", "\n").split("\n")) out.push(` ${l}`);
|
|
4197
4139
|
out.push(' """');
|
|
4198
4140
|
return out;
|
|
4199
4141
|
}
|
|
@@ -4254,13 +4196,12 @@ function buildFeature(folderName, folder, collection) {
|
|
|
4254
4196
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
4255
4197
|
const method = req.method.toLowerCase();
|
|
4256
4198
|
const lines = [];
|
|
4257
|
-
lines.push(`@${tag}`);
|
|
4258
|
-
lines.push(`Scenario: ${req.name}`);
|
|
4199
|
+
lines.push(`@${tag}`, `Scenario: ${req.name}`);
|
|
4259
4200
|
const setup = [...urlSteps(req.url)];
|
|
4260
4201
|
if (effectiveAuth.type === "bearer") {
|
|
4261
4202
|
const token = effectiveAuth.token ?? "";
|
|
4262
4203
|
if (token.includes("{{")) {
|
|
4263
|
-
const single =
|
|
4204
|
+
const single = /^\{\{([^}]+)\}\}$/.exec(token);
|
|
4264
4205
|
if (single) {
|
|
4265
4206
|
setup.push([`header Authorization = 'Bearer ' + ${configKey(single[1].trim())}`]);
|
|
4266
4207
|
} else {
|
|
@@ -4315,7 +4256,7 @@ function buildFeature(folderName, folder, collection) {
|
|
|
4315
4256
|
asserts.push(`match ${target} != null`);
|
|
4316
4257
|
break;
|
|
4317
4258
|
case "type": {
|
|
4318
|
-
const t = (a.expected ?? "").
|
|
4259
|
+
const t = (a.expected ?? "").replaceAll('"', "");
|
|
4319
4260
|
const fuzzy = ["string", "number", "boolean", "array", "object"].includes(t) ? `'#${t}'` : `'#notnull'`;
|
|
4320
4261
|
asserts.push(`match ${target} == ${fuzzy}`);
|
|
4321
4262
|
break;
|
|
@@ -4403,7 +4344,7 @@ function generateKarate(collection, environment) {
|
|
|
4403
4344
|
return files;
|
|
4404
4345
|
}
|
|
4405
4346
|
function mapOut$1(s) {
|
|
4406
|
-
return s.
|
|
4347
|
+
return s.replaceAll(/\{\{\s*(\$[A-Za-z]+)\s*\}\}/g, (_m, name) => `{{${SPECTOR_TO_HTTP[name] ?? name}}}`);
|
|
4407
4348
|
}
|
|
4408
4349
|
function orderedRequests$1(collection) {
|
|
4409
4350
|
const out = [];
|
|
@@ -4493,21 +4434,20 @@ function generateHttpFile(collection, environment) {
|
|
|
4493
4434
|
}
|
|
4494
4435
|
for (const h of headers) lines.push(`${h.key}: ${mapOut$1(h.value)}`);
|
|
4495
4436
|
if (body.text) {
|
|
4496
|
-
lines.push("");
|
|
4497
|
-
lines.push(mapOut$1(body.text));
|
|
4437
|
+
lines.push("", mapOut$1(body.text));
|
|
4498
4438
|
}
|
|
4499
4439
|
});
|
|
4500
|
-
const slug2 = collection.name.
|
|
4440
|
+
const slug2 = collection.name.replaceAll(/\W+/g, "-").replaceAll(/^-+|-+$/g, "").toLowerCase() || "collection";
|
|
4501
4441
|
return [{ path: `${slug2}.http`, content: lines.join("\n") + "\n" }];
|
|
4502
4442
|
}
|
|
4503
4443
|
function mapOut(s) {
|
|
4504
|
-
return s.
|
|
4444
|
+
return s.replaceAll(/\{\{\s*([A-Za-z_]\w*)\s*\}\}/g, (_m, name) => `\${${name}}`);
|
|
4505
4445
|
}
|
|
4506
4446
|
function shq(s) {
|
|
4507
|
-
return `'${s.
|
|
4447
|
+
return `'${s.replaceAll("'", `'\\''`)}'`;
|
|
4508
4448
|
}
|
|
4509
4449
|
function dq(s) {
|
|
4510
|
-
return `"${s.
|
|
4450
|
+
return `"${s.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
4511
4451
|
}
|
|
4512
4452
|
function orderedRequests(collection) {
|
|
4513
4453
|
const out = [];
|
|
@@ -4605,7 +4545,7 @@ function generateCurl(collection, environment) {
|
|
|
4605
4545
|
if (body.text) args.push(`--data ${dq(mapOut(body.text))}`);
|
|
4606
4546
|
lines.push(args.join(" \\\n "));
|
|
4607
4547
|
});
|
|
4608
|
-
const slug2 = collection.name.
|
|
4548
|
+
const slug2 = collection.name.replaceAll(/\W+/g, "-").replaceAll(/^-+|-+$/g, "").toLowerCase() || "collection";
|
|
4609
4549
|
return [{ path: `${slug2}.sh`, content: lines.join("\n") + "\n" }];
|
|
4610
4550
|
}
|
|
4611
4551
|
function registerGenerateHandlers(ipc) {
|
|
@@ -4642,8 +4582,8 @@ function registerGenerateHandlers(ipc) {
|
|
|
4642
4582
|
}
|
|
4643
4583
|
});
|
|
4644
4584
|
handle.handleIpc(ipc, handle.IPC.generate.saveZip, async (_e, files, collectionName, target) => {
|
|
4645
|
-
const colSlug = collectionName.
|
|
4646
|
-
const targetSlug = target.
|
|
4585
|
+
const colSlug = collectionName.replaceAll(/\W+/g, "-").toLowerCase();
|
|
4586
|
+
const targetSlug = target.replaceAll("_", "-");
|
|
4647
4587
|
const defaultName = `${colSlug}-${targetSlug}.zip`;
|
|
4648
4588
|
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
4649
4589
|
title: "Save as ZIP",
|
|
@@ -4709,7 +4649,7 @@ function registerRunnerHandler(ipc) {
|
|
|
4709
4649
|
collectionVars: { ...item.collectionVars, ...runCollectionVars },
|
|
4710
4650
|
envVars: runEnvVars,
|
|
4711
4651
|
globals: runGlobals,
|
|
4712
|
-
localVars: { ...runLocalVars, ...item.dataRow
|
|
4652
|
+
localVars: { ...runLocalVars, ...item.dataRow },
|
|
4713
4653
|
dispatcher,
|
|
4714
4654
|
piiMaskPatterns,
|
|
4715
4655
|
proxy,
|
|
@@ -5024,7 +4964,7 @@ function registerWsHandlers(ipc) {
|
|
|
5024
4964
|
});
|
|
5025
4965
|
handle.handleIpc(ipc, handle.IPC.ws.send, async (_event, requestId, data) => {
|
|
5026
4966
|
const ws = connections.get(requestId);
|
|
5027
|
-
if (
|
|
4967
|
+
if (ws?.readyState !== WebSocket.OPEN) {
|
|
5028
4968
|
throw new Error("WebSocket is not connected");
|
|
5029
4969
|
}
|
|
5030
4970
|
ws.send(data);
|
|
@@ -5059,7 +4999,7 @@ function resolveProtoFile(src) {
|
|
|
5059
4999
|
if (src.protoPath) {
|
|
5060
5000
|
return { file: src.protoPath, includeDirs: [path__namespace.dirname(src.protoPath), ...importPaths] };
|
|
5061
5001
|
}
|
|
5062
|
-
if (src.protoSource
|
|
5002
|
+
if (src.protoSource?.trim()) {
|
|
5063
5003
|
const dir = fs__namespace.mkdtempSync(path__namespace.join(os__namespace.tmpdir(), "apispector-proto-"));
|
|
5064
5004
|
const file = path__namespace.join(dir, "main.proto");
|
|
5065
5005
|
fs__namespace.writeFileSync(file, src.protoSource, "utf8");
|
|
@@ -5205,28 +5145,19 @@ function langForContentType(ct) {
|
|
|
5205
5145
|
return "";
|
|
5206
5146
|
}
|
|
5207
5147
|
function escMd(s) {
|
|
5208
|
-
return s.
|
|
5148
|
+
return s.replaceAll(/[|\\`*_{}[\]()#+\-.!]/g, (c) => `\\${c}`);
|
|
5209
5149
|
}
|
|
5210
5150
|
function requestToMarkdown(req, example) {
|
|
5211
5151
|
const lines = [];
|
|
5212
5152
|
const methodLabel = req.protocol === "websocket" ? "WS" : req.method;
|
|
5213
|
-
lines.push(`#### ${methodLabel} ${escMd(req.name)}
|
|
5214
|
-
lines.push("");
|
|
5153
|
+
lines.push(`#### ${methodLabel} ${escMd(req.name)}`, "");
|
|
5215
5154
|
if (req.description?.trim()) {
|
|
5216
|
-
lines.push(req.description.trim());
|
|
5217
|
-
lines.push("");
|
|
5155
|
+
lines.push(req.description.trim(), "");
|
|
5218
5156
|
}
|
|
5219
|
-
lines.push("**URL**");
|
|
5220
|
-
lines.push("```");
|
|
5221
|
-
lines.push(req.url || "(no url)");
|
|
5222
|
-
lines.push("```");
|
|
5223
|
-
lines.push("");
|
|
5157
|
+
lines.push("**URL**", "```", req.url || "(no url)", "```", "");
|
|
5224
5158
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
5225
5159
|
if (enabledParams.length) {
|
|
5226
|
-
lines.push("**Query Parameters**");
|
|
5227
|
-
lines.push("");
|
|
5228
|
-
lines.push("| Key | Value | Description |");
|
|
5229
|
-
lines.push("|-----|-------|-------------|");
|
|
5160
|
+
lines.push("**Query Parameters**", "", "| Key | Value | Description |", "|-----|-------|-------------|");
|
|
5230
5161
|
for (const p of enabledParams) {
|
|
5231
5162
|
lines.push(`| ${escMd(p.key)} | ${escMd(p.value)} | ${escMd(p.description ?? "")} |`);
|
|
5232
5163
|
}
|
|
@@ -5234,63 +5165,37 @@ function requestToMarkdown(req, example) {
|
|
|
5234
5165
|
}
|
|
5235
5166
|
const enabledHeaders = req.headers.filter((h) => h.enabled && h.key);
|
|
5236
5167
|
if (enabledHeaders.length) {
|
|
5237
|
-
lines.push("**Headers**");
|
|
5238
|
-
lines.push("");
|
|
5239
|
-
lines.push("| Key | Value |");
|
|
5240
|
-
lines.push("|-----|-------|");
|
|
5168
|
+
lines.push("**Headers**", "", "| Key | Value |", "|-----|-------|");
|
|
5241
5169
|
for (const h of enabledHeaders) {
|
|
5242
5170
|
lines.push(`| ${escMd(h.key)} | ${escMd(h.value)} |`);
|
|
5243
5171
|
}
|
|
5244
5172
|
lines.push("");
|
|
5245
5173
|
}
|
|
5246
5174
|
if (req.auth.type !== "none") {
|
|
5247
|
-
lines.push(`**Auth**: ${req.auth.type}
|
|
5248
|
-
lines.push("");
|
|
5175
|
+
lines.push(`**Auth**: ${req.auth.type}`, "");
|
|
5249
5176
|
}
|
|
5250
5177
|
const mode = req.body.mode;
|
|
5251
5178
|
if (mode === "json" && req.body.json?.trim()) {
|
|
5252
|
-
lines.push("**Body** (JSON)");
|
|
5253
|
-
lines.push("```json");
|
|
5254
|
-
lines.push(req.body.json.trim());
|
|
5255
|
-
lines.push("```");
|
|
5256
|
-
lines.push("");
|
|
5179
|
+
lines.push("**Body** (JSON)", "```json", req.body.json.trim(), "```", "");
|
|
5257
5180
|
} else if (mode === "raw" && req.body.raw?.trim()) {
|
|
5258
5181
|
const ct = req.body.rawContentType ?? "text";
|
|
5259
|
-
lines.push(`**Body** (${ct})
|
|
5260
|
-
lines.push("```");
|
|
5261
|
-
lines.push(req.body.raw.trim());
|
|
5262
|
-
lines.push("```");
|
|
5263
|
-
lines.push("");
|
|
5182
|
+
lines.push(`**Body** (${ct})`, "```", req.body.raw.trim(), "```", "");
|
|
5264
5183
|
} else if (mode === "graphql" && req.body.graphql?.query?.trim()) {
|
|
5265
|
-
lines.push("**Body** (GraphQL)");
|
|
5266
|
-
lines.push("```graphql");
|
|
5267
|
-
lines.push(req.body.graphql.query.trim());
|
|
5268
|
-
lines.push("```");
|
|
5269
|
-
lines.push("");
|
|
5184
|
+
lines.push("**Body** (GraphQL)", "```graphql", req.body.graphql.query.trim(), "```", "");
|
|
5270
5185
|
} else if (mode === "soap" && req.body.soap?.envelope?.trim()) {
|
|
5271
|
-
lines.push("**Body** (SOAP)");
|
|
5272
|
-
lines.push("```xml");
|
|
5273
|
-
lines.push(req.body.soap.envelope.trim());
|
|
5274
|
-
lines.push("```");
|
|
5275
|
-
lines.push("");
|
|
5186
|
+
lines.push("**Body** (SOAP)", "```xml", req.body.soap.envelope.trim(), "```", "");
|
|
5276
5187
|
}
|
|
5277
5188
|
if (example?.sent?.body?.trim()) {
|
|
5278
5189
|
const ct = example.sent.headers?.["Content-Type"] ?? example.sent.headers?.["content-type"];
|
|
5279
5190
|
const lang = langForContentType(ct);
|
|
5280
|
-
lines.push("**Example Request Body**");
|
|
5281
|
-
lines.push("```" + lang);
|
|
5282
|
-
lines.push(formatBody(example.sent.body, ct));
|
|
5283
|
-
lines.push("```");
|
|
5284
|
-
lines.push("");
|
|
5191
|
+
lines.push("**Example Request Body**", "```" + lang, formatBody(example.sent.body, ct), "```", "");
|
|
5285
5192
|
}
|
|
5286
5193
|
if (example?.response) {
|
|
5287
5194
|
const ct = example.response.headers["content-type"] ?? example.response.headers["Content-Type"];
|
|
5288
5195
|
const lang = langForContentType(ct);
|
|
5289
5196
|
lines.push(`**Example Response** (${example.response.status})`);
|
|
5290
5197
|
if (example.response.body?.trim()) {
|
|
5291
|
-
lines.push("```" + lang);
|
|
5292
|
-
lines.push(formatBody(example.response.body, ct));
|
|
5293
|
-
lines.push("```");
|
|
5198
|
+
lines.push("```" + lang, formatBody(example.response.body, ct), "```");
|
|
5294
5199
|
} else {
|
|
5295
5200
|
lines.push("_(empty body)_");
|
|
5296
5201
|
}
|
|
@@ -5302,11 +5207,9 @@ function folderToMarkdown(folder, requests, depth, examples) {
|
|
|
5302
5207
|
const lines = [];
|
|
5303
5208
|
const heading = "#".repeat(depth);
|
|
5304
5209
|
if (folder.name !== "root") {
|
|
5305
|
-
lines.push(`${heading} ${escMd(folder.name)}
|
|
5306
|
-
lines.push("");
|
|
5210
|
+
lines.push(`${heading} ${escMd(folder.name)}`, "");
|
|
5307
5211
|
if (folder.description?.trim()) {
|
|
5308
|
-
lines.push(folder.description.trim());
|
|
5309
|
-
lines.push("");
|
|
5212
|
+
lines.push(folder.description.trim(), "");
|
|
5310
5213
|
}
|
|
5311
5214
|
}
|
|
5312
5215
|
for (const reqId of folder.requestIds) {
|
|
@@ -5322,15 +5225,12 @@ function folderToMarkdown(folder, requests, depth, examples) {
|
|
|
5322
5225
|
}
|
|
5323
5226
|
function generateMarkdown(payload) {
|
|
5324
5227
|
const lines = [];
|
|
5325
|
-
lines.push("# API Documentation");
|
|
5326
|
-
lines.push("");
|
|
5228
|
+
lines.push("# API Documentation", "");
|
|
5327
5229
|
const examples = payload.examples ?? {};
|
|
5328
5230
|
for (const { collection, requests } of payload.collections) {
|
|
5329
|
-
lines.push(`## ${escMd(collection.name)}
|
|
5330
|
-
lines.push("");
|
|
5231
|
+
lines.push(`## ${escMd(collection.name)}`, "");
|
|
5331
5232
|
if (collection.description?.trim()) {
|
|
5332
|
-
lines.push(collection.description.trim());
|
|
5333
|
-
lines.push("");
|
|
5233
|
+
lines.push(collection.description.trim(), "");
|
|
5334
5234
|
}
|
|
5335
5235
|
lines.push(folderToMarkdown(collection.rootFolder, requests, 3, examples));
|
|
5336
5236
|
}
|
|
@@ -5473,7 +5373,7 @@ function registerDocsHandlers(ipc) {
|
|
|
5473
5373
|
}
|
|
5474
5374
|
function registerCoverageHandlers(ipc) {
|
|
5475
5375
|
handle.handleIpc(ipc, handle.IPC.coverage.loadSpec, async (_e, source) => {
|
|
5476
|
-
if (source.text
|
|
5376
|
+
if (source.text?.trim()) {
|
|
5477
5377
|
const t = source.text.trim();
|
|
5478
5378
|
return t.startsWith("{") ? JSON.parse(t) : jsYaml.load(t);
|
|
5479
5379
|
}
|
|
@@ -5485,7 +5385,7 @@ function registerCoverageHandlers(ipc) {
|
|
|
5485
5385
|
return ct.includes("yaml") || /\.ya?ml$/i.test(source.url) ? jsYaml.load(raw) : JSON.parse(raw);
|
|
5486
5386
|
}
|
|
5487
5387
|
if (source.path) {
|
|
5488
|
-
const raw = await promises
|
|
5388
|
+
const raw = await promises.readFile(source.path, "utf8");
|
|
5489
5389
|
return /\.ya?ml$/i.test(source.path) ? jsYaml.load(raw) : JSON.parse(raw);
|
|
5490
5390
|
}
|
|
5491
5391
|
throw new Error("No spec source: provide a file path, URL, or pasted text.");
|
|
@@ -5527,7 +5427,7 @@ async function resolveSnapshotSpec(relPath) {
|
|
|
5527
5427
|
const dir = getWorkspaceDir();
|
|
5528
5428
|
if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
|
|
5529
5429
|
const snap = await brokerClient.loadSnapshot(dir, relPath);
|
|
5530
|
-
const tmp = path.join(os
|
|
5430
|
+
const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
|
|
5531
5431
|
await promises.writeFile(tmp, snap.spec, "utf8");
|
|
5532
5432
|
return { specPath: tmp };
|
|
5533
5433
|
}
|
|
@@ -5585,7 +5485,7 @@ function registerContractHandlers(ipc) {
|
|
|
5585
5485
|
handle.handleIpc(ipc, handle.IPC.contract.exportDesignPact, async (_e, contract) => {
|
|
5586
5486
|
const dir = getWorkspaceDir();
|
|
5587
5487
|
if (!dir) throw new Error("No workspace open — open or save a workspace first.");
|
|
5588
|
-
const safe = (s) => (s || "unnamed").
|
|
5488
|
+
const safe = (s) => (s || "unnamed").replaceAll(/[^a-zA-Z0-9._-]+/g, "-");
|
|
5589
5489
|
const relPath = path.join("pacts", `${safe(contract.consumer)}-${safe(contract.provider)}.pact.json`);
|
|
5590
5490
|
const fullPath = path.join(dir, relPath);
|
|
5591
5491
|
await promises.mkdir(path.join(dir, "pacts"), { recursive: true });
|
|
@@ -5740,8 +5640,8 @@ function registerGitHandlers(ipc) {
|
|
|
5740
5640
|
const looksLocal = !isRemote;
|
|
5741
5641
|
let ahead;
|
|
5742
5642
|
let behind;
|
|
5743
|
-
const aheadM =
|
|
5744
|
-
const behindM =
|
|
5643
|
+
const aheadM = /ahead (\d+)/.exec(track);
|
|
5644
|
+
const behindM = /behind (\d+)/.exec(track);
|
|
5745
5645
|
if (aheadM) ahead = Number(aheadM[1]);
|
|
5746
5646
|
if (behindM) behind = Number(behindM[1]);
|
|
5747
5647
|
branches.push({
|
|
@@ -5816,7 +5716,7 @@ function resolveStatus(result, filePath, staged) {
|
|
|
5816
5716
|
if (staged) {
|
|
5817
5717
|
if (result.created.includes(filePath)) return "added";
|
|
5818
5718
|
if (result.deleted.includes(filePath)) return "deleted";
|
|
5819
|
-
if (result.renamed.
|
|
5719
|
+
if (result.renamed.some((r) => r.to === filePath || r.from === filePath)) return "renamed";
|
|
5820
5720
|
return "modified";
|
|
5821
5721
|
}
|
|
5822
5722
|
if (result.deleted.includes(filePath)) return "deleted";
|
|
@@ -5880,9 +5780,9 @@ async function cloudFetch(path2, method, body) {
|
|
|
5880
5780
|
return json;
|
|
5881
5781
|
}
|
|
5882
5782
|
function interpolateStatic(str, vars) {
|
|
5883
|
-
return str.
|
|
5783
|
+
return str.replaceAll(/\{\{([^}]+)\}\}/g, (m, key) => {
|
|
5884
5784
|
const trimmed = String(key).trim();
|
|
5885
|
-
return Object.
|
|
5785
|
+
return Object.hasOwn(vars, trimmed) ? vars[trimmed] : m;
|
|
5886
5786
|
});
|
|
5887
5787
|
}
|
|
5888
5788
|
function resolveRequest(req, vars) {
|
|
@@ -5904,7 +5804,7 @@ function resolveHeaders(headers, vars) {
|
|
|
5904
5804
|
return (headers ?? []).filter((h) => h.enabled !== false && h.key).map((h) => ({ key: interpolateStatic(h.key, vars), value: interpolateStatic(h.value ?? "", vars), enabled: true }));
|
|
5905
5805
|
}
|
|
5906
5806
|
function resolveBody(body, vars) {
|
|
5907
|
-
if (!body
|
|
5807
|
+
if (!body?.mode || body.mode === "none") return { mode: "none" };
|
|
5908
5808
|
const b = { ...body };
|
|
5909
5809
|
if (body.mode === "json" && body.json != null) b.json = interpolateStatic(body.json, vars);
|
|
5910
5810
|
else if (body.mode === "raw" && body.raw != null) b.raw = interpolateStatic(body.raw, vars);
|
|
@@ -5940,7 +5840,7 @@ function omitKeys(vars, names) {
|
|
|
5940
5840
|
return out;
|
|
5941
5841
|
}
|
|
5942
5842
|
function slugify(name) {
|
|
5943
|
-
return (name || "mock").toLowerCase().
|
|
5843
|
+
return (name || "mock").toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 63) || "mock";
|
|
5944
5844
|
}
|
|
5945
5845
|
function registerCloudHandlers(ipc) {
|
|
5946
5846
|
handle.handleIpc(ipc, handle.IPC.cloud.test, async () => {
|
|
@@ -6034,7 +5934,7 @@ function registerCloudHandlers(ipc) {
|
|
|
6034
5934
|
}
|
|
6035
5935
|
const REGISTRY_URL = "https://registry.npmjs.org/@testsmith/api-spector/latest";
|
|
6036
5936
|
function isNewer(a, b) {
|
|
6037
|
-
const core = (v) => v.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
5937
|
+
const core = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
6038
5938
|
const [a0, a1, a2] = core(a);
|
|
6039
5939
|
const [b0, b1, b2] = core(b);
|
|
6040
5940
|
if (a0 !== b0) return a0 > b0;
|
|
@@ -6042,7 +5942,7 @@ function isNewer(a, b) {
|
|
|
6042
5942
|
return a2 > b2;
|
|
6043
5943
|
}
|
|
6044
5944
|
async function checkForUpdate() {
|
|
6045
|
-
const current = "0.6.
|
|
5945
|
+
const current = "0.6.5";
|
|
6046
5946
|
try {
|
|
6047
5947
|
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
|
|
6048
5948
|
if (!res.ok) return null;
|
|
@@ -6079,7 +5979,7 @@ function parseOpenFromGit(deepLink) {
|
|
|
6079
5979
|
const action = u.hostname || u.pathname.replace(/^\/+/, "").split("/")[0];
|
|
6080
5980
|
if (action !== "open-from-git") return null;
|
|
6081
5981
|
const repo = u.searchParams.get("url");
|
|
6082
|
-
return repo
|
|
5982
|
+
return repo?.trim() ? repo.trim() : null;
|
|
6083
5983
|
}
|
|
6084
5984
|
function handleDeepLink(deepLink) {
|
|
6085
5985
|
const repo = parseOpenFromGit(deepLink);
|
|
@@ -6128,15 +6028,15 @@ function loadAppIcon() {
|
|
|
6128
6028
|
path.join(__dirname, "../../build/icon.png")
|
|
6129
6029
|
];
|
|
6130
6030
|
for (const p of pngCandidates) {
|
|
6131
|
-
if (fs
|
|
6031
|
+
if (fs.existsSync(p)) return electron.nativeImage.createFromPath(p);
|
|
6132
6032
|
}
|
|
6133
6033
|
const svgCandidates = [
|
|
6134
6034
|
path.join(electron.app.getAppPath(), "resources/icon.svg"),
|
|
6135
6035
|
path.join(__dirname, "../../resources/icon.svg")
|
|
6136
6036
|
];
|
|
6137
6037
|
for (const p of svgCandidates) {
|
|
6138
|
-
if (fs
|
|
6139
|
-
const dataUrl = "data:image/svg+xml;base64," + fs
|
|
6038
|
+
if (fs.existsSync(p)) {
|
|
6039
|
+
const dataUrl = "data:image/svg+xml;base64," + fs.readFileSync(p).toString("base64");
|
|
6140
6040
|
return electron.nativeImage.createFromDataURL(dataUrl);
|
|
6141
6041
|
}
|
|
6142
6042
|
}
|