@testsmith/api-spector 0.6.2 → 0.6.4
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-Hqh3I3rs.js → request-collection-KGPuvN7o.js} +5 -4
- 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 +166 -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 +50 -22
- package/out/main/wsdl.js +9 -9
- package/out/preload/index.js +1 -1
- package/out/renderer/assets/{GraphQLEditor-DtppmZJO.js → GraphQLEditor-Bih8eb_r.js} +6 -6
- package/out/renderer/assets/{SoapEditor-BwSm12wH.js → SoapEditor-D1sQKu7l.js} +3 -3
- package/out/renderer/assets/{index-D6-zmXen.js → index-vEDOfWlE.js} +418 -279
- 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",
|
|
@@ -4686,6 +4626,7 @@ function registerRunnerHandler(ipc) {
|
|
|
4686
4626
|
isHook: item.isHook,
|
|
4687
4627
|
hookType: item.hookType,
|
|
4688
4628
|
scopeId: item.scopeId,
|
|
4629
|
+
collection: item.collection,
|
|
4689
4630
|
scopePath: item.scopePath,
|
|
4690
4631
|
iterationLabel: item.iterationLabel
|
|
4691
4632
|
};
|
|
@@ -4699,6 +4640,7 @@ function registerRunnerHandler(ipc) {
|
|
|
4699
4640
|
isHook: item.isHook,
|
|
4700
4641
|
hookType: item.hookType,
|
|
4701
4642
|
scopeId: item.scopeId,
|
|
4643
|
+
collection: item.collection,
|
|
4702
4644
|
scopePath: item.scopePath
|
|
4703
4645
|
};
|
|
4704
4646
|
event.sender.send(handle.IPC.runner.progress, { requestId: item.request.id, ...runningUpdate });
|
|
@@ -4707,7 +4649,7 @@ function registerRunnerHandler(ipc) {
|
|
|
4707
4649
|
collectionVars: { ...item.collectionVars, ...runCollectionVars },
|
|
4708
4650
|
envVars: runEnvVars,
|
|
4709
4651
|
globals: runGlobals,
|
|
4710
|
-
localVars: { ...runLocalVars, ...item.dataRow
|
|
4652
|
+
localVars: { ...runLocalVars, ...item.dataRow },
|
|
4711
4653
|
dispatcher,
|
|
4712
4654
|
piiMaskPatterns,
|
|
4713
4655
|
proxy,
|
|
@@ -4730,6 +4672,7 @@ function registerRunnerHandler(ipc) {
|
|
|
4730
4672
|
isHook: item.isHook,
|
|
4731
4673
|
hookType: item.hookType,
|
|
4732
4674
|
scopeId: item.scopeId,
|
|
4675
|
+
collection: item.collection,
|
|
4733
4676
|
scopePath: item.scopePath
|
|
4734
4677
|
});
|
|
4735
4678
|
if (requestDelay > 0 && item !== items[items.length - 1]) {
|
|
@@ -5021,7 +4964,7 @@ function registerWsHandlers(ipc) {
|
|
|
5021
4964
|
});
|
|
5022
4965
|
handle.handleIpc(ipc, handle.IPC.ws.send, async (_event, requestId, data) => {
|
|
5023
4966
|
const ws = connections.get(requestId);
|
|
5024
|
-
if (
|
|
4967
|
+
if (ws?.readyState !== WebSocket.OPEN) {
|
|
5025
4968
|
throw new Error("WebSocket is not connected");
|
|
5026
4969
|
}
|
|
5027
4970
|
ws.send(data);
|
|
@@ -5056,7 +4999,7 @@ function resolveProtoFile(src) {
|
|
|
5056
4999
|
if (src.protoPath) {
|
|
5057
5000
|
return { file: src.protoPath, includeDirs: [path__namespace.dirname(src.protoPath), ...importPaths] };
|
|
5058
5001
|
}
|
|
5059
|
-
if (src.protoSource
|
|
5002
|
+
if (src.protoSource?.trim()) {
|
|
5060
5003
|
const dir = fs__namespace.mkdtempSync(path__namespace.join(os__namespace.tmpdir(), "apispector-proto-"));
|
|
5061
5004
|
const file = path__namespace.join(dir, "main.proto");
|
|
5062
5005
|
fs__namespace.writeFileSync(file, src.protoSource, "utf8");
|
|
@@ -5202,28 +5145,19 @@ function langForContentType(ct) {
|
|
|
5202
5145
|
return "";
|
|
5203
5146
|
}
|
|
5204
5147
|
function escMd(s) {
|
|
5205
|
-
return s.
|
|
5148
|
+
return s.replaceAll(/[|\\`*_{}[\]()#+\-.!]/g, (c) => `\\${c}`);
|
|
5206
5149
|
}
|
|
5207
5150
|
function requestToMarkdown(req, example) {
|
|
5208
5151
|
const lines = [];
|
|
5209
5152
|
const methodLabel = req.protocol === "websocket" ? "WS" : req.method;
|
|
5210
|
-
lines.push(`#### ${methodLabel} ${escMd(req.name)}
|
|
5211
|
-
lines.push("");
|
|
5153
|
+
lines.push(`#### ${methodLabel} ${escMd(req.name)}`, "");
|
|
5212
5154
|
if (req.description?.trim()) {
|
|
5213
|
-
lines.push(req.description.trim());
|
|
5214
|
-
lines.push("");
|
|
5155
|
+
lines.push(req.description.trim(), "");
|
|
5215
5156
|
}
|
|
5216
|
-
lines.push("**URL**");
|
|
5217
|
-
lines.push("```");
|
|
5218
|
-
lines.push(req.url || "(no url)");
|
|
5219
|
-
lines.push("```");
|
|
5220
|
-
lines.push("");
|
|
5157
|
+
lines.push("**URL**", "```", req.url || "(no url)", "```", "");
|
|
5221
5158
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
5222
5159
|
if (enabledParams.length) {
|
|
5223
|
-
lines.push("**Query Parameters**");
|
|
5224
|
-
lines.push("");
|
|
5225
|
-
lines.push("| Key | Value | Description |");
|
|
5226
|
-
lines.push("|-----|-------|-------------|");
|
|
5160
|
+
lines.push("**Query Parameters**", "", "| Key | Value | Description |", "|-----|-------|-------------|");
|
|
5227
5161
|
for (const p of enabledParams) {
|
|
5228
5162
|
lines.push(`| ${escMd(p.key)} | ${escMd(p.value)} | ${escMd(p.description ?? "")} |`);
|
|
5229
5163
|
}
|
|
@@ -5231,63 +5165,37 @@ function requestToMarkdown(req, example) {
|
|
|
5231
5165
|
}
|
|
5232
5166
|
const enabledHeaders = req.headers.filter((h) => h.enabled && h.key);
|
|
5233
5167
|
if (enabledHeaders.length) {
|
|
5234
|
-
lines.push("**Headers**");
|
|
5235
|
-
lines.push("");
|
|
5236
|
-
lines.push("| Key | Value |");
|
|
5237
|
-
lines.push("|-----|-------|");
|
|
5168
|
+
lines.push("**Headers**", "", "| Key | Value |", "|-----|-------|");
|
|
5238
5169
|
for (const h of enabledHeaders) {
|
|
5239
5170
|
lines.push(`| ${escMd(h.key)} | ${escMd(h.value)} |`);
|
|
5240
5171
|
}
|
|
5241
5172
|
lines.push("");
|
|
5242
5173
|
}
|
|
5243
5174
|
if (req.auth.type !== "none") {
|
|
5244
|
-
lines.push(`**Auth**: ${req.auth.type}
|
|
5245
|
-
lines.push("");
|
|
5175
|
+
lines.push(`**Auth**: ${req.auth.type}`, "");
|
|
5246
5176
|
}
|
|
5247
5177
|
const mode = req.body.mode;
|
|
5248
5178
|
if (mode === "json" && req.body.json?.trim()) {
|
|
5249
|
-
lines.push("**Body** (JSON)");
|
|
5250
|
-
lines.push("```json");
|
|
5251
|
-
lines.push(req.body.json.trim());
|
|
5252
|
-
lines.push("```");
|
|
5253
|
-
lines.push("");
|
|
5179
|
+
lines.push("**Body** (JSON)", "```json", req.body.json.trim(), "```", "");
|
|
5254
5180
|
} else if (mode === "raw" && req.body.raw?.trim()) {
|
|
5255
5181
|
const ct = req.body.rawContentType ?? "text";
|
|
5256
|
-
lines.push(`**Body** (${ct})
|
|
5257
|
-
lines.push("```");
|
|
5258
|
-
lines.push(req.body.raw.trim());
|
|
5259
|
-
lines.push("```");
|
|
5260
|
-
lines.push("");
|
|
5182
|
+
lines.push(`**Body** (${ct})`, "```", req.body.raw.trim(), "```", "");
|
|
5261
5183
|
} else if (mode === "graphql" && req.body.graphql?.query?.trim()) {
|
|
5262
|
-
lines.push("**Body** (GraphQL)");
|
|
5263
|
-
lines.push("```graphql");
|
|
5264
|
-
lines.push(req.body.graphql.query.trim());
|
|
5265
|
-
lines.push("```");
|
|
5266
|
-
lines.push("");
|
|
5184
|
+
lines.push("**Body** (GraphQL)", "```graphql", req.body.graphql.query.trim(), "```", "");
|
|
5267
5185
|
} else if (mode === "soap" && req.body.soap?.envelope?.trim()) {
|
|
5268
|
-
lines.push("**Body** (SOAP)");
|
|
5269
|
-
lines.push("```xml");
|
|
5270
|
-
lines.push(req.body.soap.envelope.trim());
|
|
5271
|
-
lines.push("```");
|
|
5272
|
-
lines.push("");
|
|
5186
|
+
lines.push("**Body** (SOAP)", "```xml", req.body.soap.envelope.trim(), "```", "");
|
|
5273
5187
|
}
|
|
5274
5188
|
if (example?.sent?.body?.trim()) {
|
|
5275
5189
|
const ct = example.sent.headers?.["Content-Type"] ?? example.sent.headers?.["content-type"];
|
|
5276
5190
|
const lang = langForContentType(ct);
|
|
5277
|
-
lines.push("**Example Request Body**");
|
|
5278
|
-
lines.push("```" + lang);
|
|
5279
|
-
lines.push(formatBody(example.sent.body, ct));
|
|
5280
|
-
lines.push("```");
|
|
5281
|
-
lines.push("");
|
|
5191
|
+
lines.push("**Example Request Body**", "```" + lang, formatBody(example.sent.body, ct), "```", "");
|
|
5282
5192
|
}
|
|
5283
5193
|
if (example?.response) {
|
|
5284
5194
|
const ct = example.response.headers["content-type"] ?? example.response.headers["Content-Type"];
|
|
5285
5195
|
const lang = langForContentType(ct);
|
|
5286
5196
|
lines.push(`**Example Response** (${example.response.status})`);
|
|
5287
5197
|
if (example.response.body?.trim()) {
|
|
5288
|
-
lines.push("```" + lang);
|
|
5289
|
-
lines.push(formatBody(example.response.body, ct));
|
|
5290
|
-
lines.push("```");
|
|
5198
|
+
lines.push("```" + lang, formatBody(example.response.body, ct), "```");
|
|
5291
5199
|
} else {
|
|
5292
5200
|
lines.push("_(empty body)_");
|
|
5293
5201
|
}
|
|
@@ -5299,11 +5207,9 @@ function folderToMarkdown(folder, requests, depth, examples) {
|
|
|
5299
5207
|
const lines = [];
|
|
5300
5208
|
const heading = "#".repeat(depth);
|
|
5301
5209
|
if (folder.name !== "root") {
|
|
5302
|
-
lines.push(`${heading} ${escMd(folder.name)}
|
|
5303
|
-
lines.push("");
|
|
5210
|
+
lines.push(`${heading} ${escMd(folder.name)}`, "");
|
|
5304
5211
|
if (folder.description?.trim()) {
|
|
5305
|
-
lines.push(folder.description.trim());
|
|
5306
|
-
lines.push("");
|
|
5212
|
+
lines.push(folder.description.trim(), "");
|
|
5307
5213
|
}
|
|
5308
5214
|
}
|
|
5309
5215
|
for (const reqId of folder.requestIds) {
|
|
@@ -5319,15 +5225,12 @@ function folderToMarkdown(folder, requests, depth, examples) {
|
|
|
5319
5225
|
}
|
|
5320
5226
|
function generateMarkdown(payload) {
|
|
5321
5227
|
const lines = [];
|
|
5322
|
-
lines.push("# API Documentation");
|
|
5323
|
-
lines.push("");
|
|
5228
|
+
lines.push("# API Documentation", "");
|
|
5324
5229
|
const examples = payload.examples ?? {};
|
|
5325
5230
|
for (const { collection, requests } of payload.collections) {
|
|
5326
|
-
lines.push(`## ${escMd(collection.name)}
|
|
5327
|
-
lines.push("");
|
|
5231
|
+
lines.push(`## ${escMd(collection.name)}`, "");
|
|
5328
5232
|
if (collection.description?.trim()) {
|
|
5329
|
-
lines.push(collection.description.trim());
|
|
5330
|
-
lines.push("");
|
|
5233
|
+
lines.push(collection.description.trim(), "");
|
|
5331
5234
|
}
|
|
5332
5235
|
lines.push(folderToMarkdown(collection.rootFolder, requests, 3, examples));
|
|
5333
5236
|
}
|
|
@@ -5470,7 +5373,7 @@ function registerDocsHandlers(ipc) {
|
|
|
5470
5373
|
}
|
|
5471
5374
|
function registerCoverageHandlers(ipc) {
|
|
5472
5375
|
handle.handleIpc(ipc, handle.IPC.coverage.loadSpec, async (_e, source) => {
|
|
5473
|
-
if (source.text
|
|
5376
|
+
if (source.text?.trim()) {
|
|
5474
5377
|
const t = source.text.trim();
|
|
5475
5378
|
return t.startsWith("{") ? JSON.parse(t) : jsYaml.load(t);
|
|
5476
5379
|
}
|
|
@@ -5482,7 +5385,7 @@ function registerCoverageHandlers(ipc) {
|
|
|
5482
5385
|
return ct.includes("yaml") || /\.ya?ml$/i.test(source.url) ? jsYaml.load(raw) : JSON.parse(raw);
|
|
5483
5386
|
}
|
|
5484
5387
|
if (source.path) {
|
|
5485
|
-
const raw = await promises
|
|
5388
|
+
const raw = await promises.readFile(source.path, "utf8");
|
|
5486
5389
|
return /\.ya?ml$/i.test(source.path) ? jsYaml.load(raw) : JSON.parse(raw);
|
|
5487
5390
|
}
|
|
5488
5391
|
throw new Error("No spec source: provide a file path, URL, or pasted text.");
|
|
@@ -5524,7 +5427,7 @@ async function resolveSnapshotSpec(relPath) {
|
|
|
5524
5427
|
const dir = getWorkspaceDir();
|
|
5525
5428
|
if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
|
|
5526
5429
|
const snap = await brokerClient.loadSnapshot(dir, relPath);
|
|
5527
|
-
const tmp = path.join(os
|
|
5430
|
+
const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
|
|
5528
5431
|
await promises.writeFile(tmp, snap.spec, "utf8");
|
|
5529
5432
|
return { specPath: tmp };
|
|
5530
5433
|
}
|
|
@@ -5582,7 +5485,7 @@ function registerContractHandlers(ipc) {
|
|
|
5582
5485
|
handle.handleIpc(ipc, handle.IPC.contract.exportDesignPact, async (_e, contract) => {
|
|
5583
5486
|
const dir = getWorkspaceDir();
|
|
5584
5487
|
if (!dir) throw new Error("No workspace open — open or save a workspace first.");
|
|
5585
|
-
const safe = (s) => (s || "unnamed").
|
|
5488
|
+
const safe = (s) => (s || "unnamed").replaceAll(/[^a-zA-Z0-9._-]+/g, "-");
|
|
5586
5489
|
const relPath = path.join("pacts", `${safe(contract.consumer)}-${safe(contract.provider)}.pact.json`);
|
|
5587
5490
|
const fullPath = path.join(dir, relPath);
|
|
5588
5491
|
await promises.mkdir(path.join(dir, "pacts"), { recursive: true });
|
|
@@ -5737,8 +5640,8 @@ function registerGitHandlers(ipc) {
|
|
|
5737
5640
|
const looksLocal = !isRemote;
|
|
5738
5641
|
let ahead;
|
|
5739
5642
|
let behind;
|
|
5740
|
-
const aheadM =
|
|
5741
|
-
const behindM =
|
|
5643
|
+
const aheadM = /ahead (\d+)/.exec(track);
|
|
5644
|
+
const behindM = /behind (\d+)/.exec(track);
|
|
5742
5645
|
if (aheadM) ahead = Number(aheadM[1]);
|
|
5743
5646
|
if (behindM) behind = Number(behindM[1]);
|
|
5744
5647
|
branches.push({
|
|
@@ -5813,7 +5716,7 @@ function resolveStatus(result, filePath, staged) {
|
|
|
5813
5716
|
if (staged) {
|
|
5814
5717
|
if (result.created.includes(filePath)) return "added";
|
|
5815
5718
|
if (result.deleted.includes(filePath)) return "deleted";
|
|
5816
|
-
if (result.renamed.
|
|
5719
|
+
if (result.renamed.some((r) => r.to === filePath || r.from === filePath)) return "renamed";
|
|
5817
5720
|
return "modified";
|
|
5818
5721
|
}
|
|
5819
5722
|
if (result.deleted.includes(filePath)) return "deleted";
|
|
@@ -5877,9 +5780,9 @@ async function cloudFetch(path2, method, body) {
|
|
|
5877
5780
|
return json;
|
|
5878
5781
|
}
|
|
5879
5782
|
function interpolateStatic(str, vars) {
|
|
5880
|
-
return str.
|
|
5783
|
+
return str.replaceAll(/\{\{([^}]+)\}\}/g, (m, key) => {
|
|
5881
5784
|
const trimmed = String(key).trim();
|
|
5882
|
-
return Object.
|
|
5785
|
+
return Object.hasOwn(vars, trimmed) ? vars[trimmed] : m;
|
|
5883
5786
|
});
|
|
5884
5787
|
}
|
|
5885
5788
|
function resolveRequest(req, vars) {
|
|
@@ -5901,7 +5804,7 @@ function resolveHeaders(headers, vars) {
|
|
|
5901
5804
|
return (headers ?? []).filter((h) => h.enabled !== false && h.key).map((h) => ({ key: interpolateStatic(h.key, vars), value: interpolateStatic(h.value ?? "", vars), enabled: true }));
|
|
5902
5805
|
}
|
|
5903
5806
|
function resolveBody(body, vars) {
|
|
5904
|
-
if (!body
|
|
5807
|
+
if (!body?.mode || body.mode === "none") return { mode: "none" };
|
|
5905
5808
|
const b = { ...body };
|
|
5906
5809
|
if (body.mode === "json" && body.json != null) b.json = interpolateStatic(body.json, vars);
|
|
5907
5810
|
else if (body.mode === "raw" && body.raw != null) b.raw = interpolateStatic(body.raw, vars);
|
|
@@ -5937,7 +5840,7 @@ function omitKeys(vars, names) {
|
|
|
5937
5840
|
return out;
|
|
5938
5841
|
}
|
|
5939
5842
|
function slugify(name) {
|
|
5940
|
-
return (name || "mock").toLowerCase().
|
|
5843
|
+
return (name || "mock").toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 63) || "mock";
|
|
5941
5844
|
}
|
|
5942
5845
|
function registerCloudHandlers(ipc) {
|
|
5943
5846
|
handle.handleIpc(ipc, handle.IPC.cloud.test, async () => {
|
|
@@ -6031,7 +5934,7 @@ function registerCloudHandlers(ipc) {
|
|
|
6031
5934
|
}
|
|
6032
5935
|
const REGISTRY_URL = "https://registry.npmjs.org/@testsmith/api-spector/latest";
|
|
6033
5936
|
function isNewer(a, b) {
|
|
6034
|
-
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);
|
|
6035
5938
|
const [a0, a1, a2] = core(a);
|
|
6036
5939
|
const [b0, b1, b2] = core(b);
|
|
6037
5940
|
if (a0 !== b0) return a0 > b0;
|
|
@@ -6039,7 +5942,7 @@ function isNewer(a, b) {
|
|
|
6039
5942
|
return a2 > b2;
|
|
6040
5943
|
}
|
|
6041
5944
|
async function checkForUpdate() {
|
|
6042
|
-
const current = "0.6.
|
|
5945
|
+
const current = "0.6.4";
|
|
6043
5946
|
try {
|
|
6044
5947
|
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
|
|
6045
5948
|
if (!res.ok) return null;
|
|
@@ -6076,7 +5979,7 @@ function parseOpenFromGit(deepLink) {
|
|
|
6076
5979
|
const action = u.hostname || u.pathname.replace(/^\/+/, "").split("/")[0];
|
|
6077
5980
|
if (action !== "open-from-git") return null;
|
|
6078
5981
|
const repo = u.searchParams.get("url");
|
|
6079
|
-
return repo
|
|
5982
|
+
return repo?.trim() ? repo.trim() : null;
|
|
6080
5983
|
}
|
|
6081
5984
|
function handleDeepLink(deepLink) {
|
|
6082
5985
|
const repo = parseOpenFromGit(deepLink);
|
|
@@ -6125,15 +6028,15 @@ function loadAppIcon() {
|
|
|
6125
6028
|
path.join(__dirname, "../../build/icon.png")
|
|
6126
6029
|
];
|
|
6127
6030
|
for (const p of pngCandidates) {
|
|
6128
|
-
if (fs
|
|
6031
|
+
if (fs.existsSync(p)) return electron.nativeImage.createFromPath(p);
|
|
6129
6032
|
}
|
|
6130
6033
|
const svgCandidates = [
|
|
6131
6034
|
path.join(electron.app.getAppPath(), "resources/icon.svg"),
|
|
6132
6035
|
path.join(__dirname, "../../resources/icon.svg")
|
|
6133
6036
|
];
|
|
6134
6037
|
for (const p of svgCandidates) {
|
|
6135
|
-
if (fs
|
|
6136
|
-
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");
|
|
6137
6040
|
return electron.nativeImage.createFromDataURL(dataUrl);
|
|
6138
6041
|
}
|
|
6139
6042
|
}
|