@testsmith/api-spector 0.7.7 → 0.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +4 -0
- package/out/main/chunks/{broker-client-Df4PpJqB.js → broker-client-CJBfpvbd.js} +4 -1
- package/out/main/chunks/{flow-engine-BVzjz4lJ.js → flow-engine-BxwjEy37.js} +66 -1
- package/out/main/chunks/{handle-Bz0C56Eq.js → handle-Crv1i9hb.js} +60 -0
- package/out/main/chunks/{import-BhGBiW7j.js → import-DTcZMRNu.js} +2 -2
- package/out/main/chunks/perf-report-ChaO3K0w.js +124 -0
- package/out/main/chunks/perf-run-Co_5unBR.js +578 -0
- package/out/main/chunks/{request-collection-KGPuvN7o.js → request-collection-gkOYlWml.js} +68 -11
- package/out/main/chunks/{request-exec-B-F0K-pN.js → request-exec-CgyqbNWk.js} +445 -13
- package/out/main/chunks/run-concurrent-468tH0AX.js +26 -0
- package/out/main/chunks/{soap-handler-DqIPcIQL.js → soap-handler-Brr7BPWO.js} +2 -2
- package/out/main/contract.js +6 -3
- package/out/main/flow.js +8 -5
- package/out/main/index.js +1352 -185
- package/out/main/lib.js +6 -3
- package/out/main/perf-worker.js +45 -0
- package/out/main/perf.js +253 -0
- package/out/main/runner.js +181 -7
- package/out/main/socketio.js +172 -0
- package/out/main/wsdl.js +3 -3
- package/out/preload/index.js +127 -2
- package/out/renderer/assets/{GraphQLEditor-BXpdmpn6.js → GraphQLEditor-CXvLKrnv.js} +59 -14
- package/out/renderer/assets/{SoapEditor-De6WlP6E.js → SoapEditor-7hxhISPZ.js} +3 -3
- package/out/renderer/assets/ajv-CJPwFjiP.js +6718 -0
- package/out/renderer/assets/{codemirror-F6m3Hm1M.js → codemirror-CdIFBPs2.js} +1214 -1207
- package/out/renderer/assets/{index-Gcd_t6pX.js → index-BUFSn_qD.js} +15344 -12285
- package/out/renderer/assets/index-DkhdKzQJ.css +627 -0
- package/out/renderer/index.html +4 -4
- package/package.json +12 -6
- package/out/renderer/assets/ajv-2MAg6kGF.js +0 -6153
- package/out/renderer/assets/index-CYArpqm5.css +0 -627
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
const undici = require("undici");
|
|
25
25
|
const promises$1 = require("fs/promises");
|
|
26
26
|
const nodeTls = require("tls");
|
|
27
|
-
const handle = require("./handle-
|
|
27
|
+
const handle = require("./handle-Crv1i9hb.js");
|
|
28
28
|
const fs = require("node:fs");
|
|
29
29
|
const os = require("node:os");
|
|
30
30
|
const path = require("node:path");
|
|
@@ -35,6 +35,9 @@ const vm = require("node:vm");
|
|
|
35
35
|
const tv4 = require("tv4");
|
|
36
36
|
const jsonpathPlus = require("jsonpath-plus");
|
|
37
37
|
const xmldom = require("@xmldom/xmldom");
|
|
38
|
+
const mqtt = require("mqtt");
|
|
39
|
+
const WebSocket = require("ws");
|
|
40
|
+
const socket_ioClient = require("socket.io-client");
|
|
38
41
|
const http = require("node:http");
|
|
39
42
|
const Ajv = require("ajv");
|
|
40
43
|
function _interopNamespaceDefault(e) {
|
|
@@ -838,6 +841,10 @@ function buildUrl(baseUrl, params, vars) {
|
|
|
838
841
|
templateTokens.add(String(name).trim());
|
|
839
842
|
return "";
|
|
840
843
|
});
|
|
844
|
+
baseUrl.replaceAll(/\[([A-Za-z_]\w*)\]/g, (_m, name) => {
|
|
845
|
+
templateTokens.add(String(name));
|
|
846
|
+
return "";
|
|
847
|
+
});
|
|
841
848
|
const enabled = (params ?? []).filter((p) => p.enabled && p.key);
|
|
842
849
|
const pathRows = [];
|
|
843
850
|
const queryRows = [];
|
|
@@ -850,7 +857,8 @@ function buildUrl(baseUrl, params, vars) {
|
|
|
850
857
|
...vars,
|
|
851
858
|
...Object.fromEntries(pathRows.map((p) => [p.key, interpolate(p.value, vars)]))
|
|
852
859
|
} : vars;
|
|
853
|
-
|
|
860
|
+
let url = interpolate(baseUrl, mergedVars);
|
|
861
|
+
url = url.replaceAll(/\[([A-Za-z_]\w*)\]/g, (m, name) => name in mergedVars ? mergedVars[name] : m);
|
|
854
862
|
if (!queryRows.length) return url;
|
|
855
863
|
const sep = url.includes("?") ? "&" : "?";
|
|
856
864
|
const qs = queryRows.map((p) => `${encodeURIComponent(interpolate(p.key, vars))}=${encodeURIComponent(interpolate(p.value, vars))}`).join("&");
|
|
@@ -905,9 +913,9 @@ async function buildEnvVars(environment) {
|
|
|
905
913
|
}
|
|
906
914
|
return vars;
|
|
907
915
|
}
|
|
908
|
-
function mergeVars(envVars, collectionVars, globals2, localVars = {}, dynamicVars = {}) {
|
|
916
|
+
function mergeVars(envVars, collectionVars, globals2, localVars = {}, dynamicVars = {}, folderVars = {}) {
|
|
909
917
|
const out = {};
|
|
910
|
-
for (const src of [dynamicVars, globals2, collectionVars, envVars, localVars]) {
|
|
918
|
+
for (const src of [dynamicVars, globals2, collectionVars, folderVars, envVars, localVars]) {
|
|
911
919
|
for (const [k, v] of Object.entries(src ?? {})) {
|
|
912
920
|
if (v == null) continue;
|
|
913
921
|
out[k] = v;
|
|
@@ -1091,6 +1099,7 @@ class AssertionError extends Error {
|
|
|
1091
1099
|
}
|
|
1092
1100
|
function buildAt(ctx, testResults, consoleOutput) {
|
|
1093
1101
|
const { envVars, collectionVars, globals: globals2, localVars, piiMaskPatterns = [] } = ctx;
|
|
1102
|
+
const folderVars = ctx.folderVars ?? {};
|
|
1094
1103
|
const isSensitiveKey = (key) => piiMaskPatterns.some((p) => key.toLowerCase().includes(p.toLowerCase()));
|
|
1095
1104
|
function makeVarScope(store, scopeName) {
|
|
1096
1105
|
return {
|
|
@@ -1113,9 +1122,10 @@ function buildAt(ctx, testResults, consoleOutput) {
|
|
|
1113
1122
|
variables: makeVarScope(localVars, "variables"),
|
|
1114
1123
|
environment: makeVarScope(envVars, "environment"),
|
|
1115
1124
|
collectionVariables: makeVarScope(collectionVars, "collectionVariables"),
|
|
1125
|
+
folder: makeVarScope(folderVars, "folder"),
|
|
1116
1126
|
globals: makeVarScope(globals2, "globals"),
|
|
1117
1127
|
// Convenience: get/set across all scopes (local wins)
|
|
1118
|
-
variables_get: (key) => localVars[key] ?? envVars[key] ?? collectionVars[key] ?? globals2[key] ?? null,
|
|
1128
|
+
variables_get: (key) => localVars[key] ?? envVars[key] ?? folderVars[key] ?? collectionVars[key] ?? globals2[key] ?? null,
|
|
1119
1129
|
variables_set: (key, value) => {
|
|
1120
1130
|
localVars[key] = String(value);
|
|
1121
1131
|
},
|
|
@@ -1174,6 +1184,14 @@ function buildAt(ctx, testResults, consoleOutput) {
|
|
|
1174
1184
|
return String(binCode % 10 ** digits).padStart(digits, "0");
|
|
1175
1185
|
}
|
|
1176
1186
|
};
|
|
1187
|
+
const collected = ctx.messages ?? ctx.mqttMessages;
|
|
1188
|
+
if (collected) {
|
|
1189
|
+
sp.messages = collected.map((m) => ({
|
|
1190
|
+
...m,
|
|
1191
|
+
json: () => JSON.parse(m.payload),
|
|
1192
|
+
text: () => m.payload
|
|
1193
|
+
}));
|
|
1194
|
+
}
|
|
1177
1195
|
if (ctx.response) {
|
|
1178
1196
|
const resp = ctx.response;
|
|
1179
1197
|
let parsedJson = void 0;
|
|
@@ -1249,12 +1267,17 @@ async function runScript(code, ctx, timeoutMs = 5e3) {
|
|
|
1249
1267
|
const collectionCopy = { ...ctx.collectionVars };
|
|
1250
1268
|
const globalsCopy = { ...ctx.globals };
|
|
1251
1269
|
const localVarsCopy = { ...ctx.localVars };
|
|
1270
|
+
const folderCopy = { ...ctx.folderVars ?? {} };
|
|
1252
1271
|
const scriptCtx = {
|
|
1253
1272
|
envVars: envVarsCopy,
|
|
1254
1273
|
collectionVars: collectionCopy,
|
|
1255
1274
|
globals: globalsCopy,
|
|
1256
1275
|
localVars: localVarsCopy,
|
|
1276
|
+
folderVars: folderCopy,
|
|
1257
1277
|
response: ctx.response,
|
|
1278
|
+
data: ctx.data,
|
|
1279
|
+
mqttMessages: ctx.mqttMessages,
|
|
1280
|
+
messages: ctx.messages,
|
|
1258
1281
|
piiMaskPatterns: ctx.piiMaskPatterns
|
|
1259
1282
|
};
|
|
1260
1283
|
const sandbox = buildSandbox(scriptCtx, testResults, consoleOutput, faker);
|
|
@@ -1269,6 +1292,7 @@ async function runScript(code, ctx, timeoutMs = 5e3) {
|
|
|
1269
1292
|
updatedCollectionVars: collectionCopy,
|
|
1270
1293
|
updatedGlobals: globalsCopy,
|
|
1271
1294
|
updatedLocalVars: localVarsCopy,
|
|
1295
|
+
updatedFolderVars: folderCopy,
|
|
1272
1296
|
error: message
|
|
1273
1297
|
};
|
|
1274
1298
|
}
|
|
@@ -1278,7 +1302,8 @@ async function runScript(code, ctx, timeoutMs = 5e3) {
|
|
|
1278
1302
|
updatedEnvVars: envVarsCopy,
|
|
1279
1303
|
updatedCollectionVars: collectionCopy,
|
|
1280
1304
|
updatedGlobals: globalsCopy,
|
|
1281
|
-
updatedLocalVars: localVarsCopy
|
|
1305
|
+
updatedLocalVars: localVarsCopy,
|
|
1306
|
+
updatedFolderVars: folderCopy
|
|
1282
1307
|
};
|
|
1283
1308
|
}
|
|
1284
1309
|
async function evaluateExpression(code, ctx, timeoutMs = 5e3) {
|
|
@@ -1357,6 +1382,7 @@ class StreamFramer {
|
|
|
1357
1382
|
constructor(mode) {
|
|
1358
1383
|
this.mode = mode;
|
|
1359
1384
|
}
|
|
1385
|
+
mode;
|
|
1360
1386
|
carry = "";
|
|
1361
1387
|
/** Feed a decoded text chunk; returns the frames that completed. */
|
|
1362
1388
|
push(text) {
|
|
@@ -1519,6 +1545,314 @@ async function readStream(body, kind, startMs, opts = {}) {
|
|
|
1519
1545
|
}
|
|
1520
1546
|
return { events, text, close, firstEventMs: Math.max(firstEventMs, 0) };
|
|
1521
1547
|
}
|
|
1548
|
+
async function runMqttTest(cfg) {
|
|
1549
|
+
const messages = [];
|
|
1550
|
+
const collectMs = Math.max(0, Math.min(6e4, Math.floor(cfg.collectMs ?? 2e3)));
|
|
1551
|
+
const clientId = cfg.clientId?.trim() || `api-spector-test-${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 10)}`;
|
|
1552
|
+
return new Promise((resolve2) => {
|
|
1553
|
+
let settled = false;
|
|
1554
|
+
let connected = false;
|
|
1555
|
+
let lastError = "";
|
|
1556
|
+
let client;
|
|
1557
|
+
let timer;
|
|
1558
|
+
const runAssertions = async (connectError) => {
|
|
1559
|
+
if (settled) return;
|
|
1560
|
+
settled = true;
|
|
1561
|
+
if (timer) clearTimeout(timer);
|
|
1562
|
+
try {
|
|
1563
|
+
client?.end(true);
|
|
1564
|
+
} catch {
|
|
1565
|
+
}
|
|
1566
|
+
if (connectError && messages.length === 0) {
|
|
1567
|
+
resolve2({ testResults: [], consoleOutput: [], error: connectError, messages, clientId });
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
const out = await runScript(cfg.script ?? "", {
|
|
1571
|
+
envVars: cfg.envVars ?? {},
|
|
1572
|
+
collectionVars: cfg.collectionVars ?? {},
|
|
1573
|
+
globals: cfg.globals ?? {},
|
|
1574
|
+
localVars: cfg.localVars ?? {},
|
|
1575
|
+
mqttMessages: messages
|
|
1576
|
+
});
|
|
1577
|
+
resolve2({ testResults: out.testResults, consoleOutput: out.consoleOutput, error: out.error, messages, clientId });
|
|
1578
|
+
};
|
|
1579
|
+
try {
|
|
1580
|
+
client = mqtt.connect(cfg.url, {
|
|
1581
|
+
clientId,
|
|
1582
|
+
username: cfg.username?.trim() || void 0,
|
|
1583
|
+
password: cfg.password ? cfg.password : void 0,
|
|
1584
|
+
clean: cfg.cleanSession !== false,
|
|
1585
|
+
reconnectPeriod: 0,
|
|
1586
|
+
connectTimeout: 15e3
|
|
1587
|
+
});
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
resolve2({ testResults: [], consoleOutput: [], error: err instanceof Error ? err.message : String(err), messages, clientId });
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
client.on("connect", () => {
|
|
1593
|
+
connected = true;
|
|
1594
|
+
const publish = () => {
|
|
1595
|
+
const topic = cfg.publishTopic?.trim();
|
|
1596
|
+
if (!topic) return;
|
|
1597
|
+
if (/[+#]/.test(topic)) return;
|
|
1598
|
+
client.publish(topic, cfg.publishPayload ?? "", { qos: cfg.qos ?? 0, retain: Boolean(cfg.retain) });
|
|
1599
|
+
};
|
|
1600
|
+
const sub = cfg.subscribeTopic?.trim();
|
|
1601
|
+
if (sub) {
|
|
1602
|
+
client.subscribe(sub, { qos: cfg.qos ?? 0 }, () => setTimeout(publish, 100));
|
|
1603
|
+
} else {
|
|
1604
|
+
publish();
|
|
1605
|
+
}
|
|
1606
|
+
timer = setTimeout(() => void runAssertions(), collectMs);
|
|
1607
|
+
});
|
|
1608
|
+
client.on("message", (topic, payload, packet) => {
|
|
1609
|
+
messages.push({ topic, payload: payload.toString("utf8"), qos: packet?.qos, retain: Boolean(packet?.retain), timestamp: Date.now() });
|
|
1610
|
+
});
|
|
1611
|
+
client.on("error", (err) => {
|
|
1612
|
+
const code = err?.code;
|
|
1613
|
+
lastError = [err?.message, code].filter(Boolean).join(" ");
|
|
1614
|
+
void runAssertions(err?.message || "Could not connect to the broker");
|
|
1615
|
+
});
|
|
1616
|
+
client.on("close", () => {
|
|
1617
|
+
if (settled) return;
|
|
1618
|
+
void runAssertions(connected ? `The test connection was dropped by the broker before the window finished${lastError ? ` [${lastError}]` : ""}. Common causes: publishing to a topic that contains a wildcard (+ or #), or another connection using the same client id. Check the broker's log.` : `Could not connect to the broker${lastError ? ` [${lastError}]` : ""}`);
|
|
1619
|
+
});
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
function clampWindow(ms) {
|
|
1623
|
+
return Math.max(0, Math.min(6e4, Math.floor(ms ?? 3e3)));
|
|
1624
|
+
}
|
|
1625
|
+
async function assertOver(cfg, messages) {
|
|
1626
|
+
const out = await runScript(cfg.script ?? "", {
|
|
1627
|
+
envVars: cfg.envVars ?? {},
|
|
1628
|
+
collectionVars: cfg.collectionVars ?? {},
|
|
1629
|
+
globals: cfg.globals ?? {},
|
|
1630
|
+
localVars: cfg.localVars ?? {},
|
|
1631
|
+
messages
|
|
1632
|
+
});
|
|
1633
|
+
return { testResults: out.testResults, consoleOutput: out.consoleOutput, error: out.error, messages };
|
|
1634
|
+
}
|
|
1635
|
+
function errorResult(error, messages) {
|
|
1636
|
+
return { testResults: [], consoleOutput: [], error, messages };
|
|
1637
|
+
}
|
|
1638
|
+
function parseJsonObject(text, label) {
|
|
1639
|
+
if (!text || !text.trim()) return void 0;
|
|
1640
|
+
let parsed;
|
|
1641
|
+
try {
|
|
1642
|
+
parsed = JSON.parse(text);
|
|
1643
|
+
} catch {
|
|
1644
|
+
throw new Error(`${label} is not valid JSON`);
|
|
1645
|
+
}
|
|
1646
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
1647
|
+
}
|
|
1648
|
+
async function runWsTest(cfg) {
|
|
1649
|
+
const messages = [];
|
|
1650
|
+
const collectMs = clampWindow(cfg.collectMs);
|
|
1651
|
+
return new Promise((resolve2) => {
|
|
1652
|
+
let settled = false;
|
|
1653
|
+
let connected = false;
|
|
1654
|
+
let lastError = "";
|
|
1655
|
+
let ws;
|
|
1656
|
+
let timer;
|
|
1657
|
+
const finish = async (connectError) => {
|
|
1658
|
+
if (settled) return;
|
|
1659
|
+
settled = true;
|
|
1660
|
+
if (timer) clearTimeout(timer);
|
|
1661
|
+
try {
|
|
1662
|
+
ws?.close();
|
|
1663
|
+
} catch {
|
|
1664
|
+
}
|
|
1665
|
+
if (connectError && messages.length === 0) {
|
|
1666
|
+
resolve2(errorResult(connectError, messages));
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
resolve2(await assertOver(cfg, messages));
|
|
1670
|
+
};
|
|
1671
|
+
try {
|
|
1672
|
+
ws = new WebSocket(cfg.url, { headers: cfg.headers });
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
resolve2(errorResult(err instanceof Error ? err.message : String(err), messages));
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
ws.on("open", () => {
|
|
1678
|
+
connected = true;
|
|
1679
|
+
if (cfg.sendMessage) {
|
|
1680
|
+
try {
|
|
1681
|
+
ws.send(cfg.sendMessage);
|
|
1682
|
+
} catch {
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
timer = setTimeout(() => void finish(), collectMs);
|
|
1686
|
+
});
|
|
1687
|
+
ws.on("message", (data) => {
|
|
1688
|
+
const text = data.toString();
|
|
1689
|
+
messages.push({ payload: text, data: text, timestamp: Date.now() });
|
|
1690
|
+
});
|
|
1691
|
+
ws.on("error", (err) => {
|
|
1692
|
+
lastError = err.message;
|
|
1693
|
+
void finish(err.message || "Could not connect to the server");
|
|
1694
|
+
});
|
|
1695
|
+
ws.on("close", () => {
|
|
1696
|
+
if (settled) return;
|
|
1697
|
+
void finish(connected ? `The WebSocket was closed before the window finished${lastError ? ` [${lastError}]` : ""}.` : `Could not connect to the server${lastError ? ` [${lastError}]` : ""}`);
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
function targetUrl(url, namespace) {
|
|
1702
|
+
const ns = (namespace ?? "").trim();
|
|
1703
|
+
if (!ns || ns === "/") return url;
|
|
1704
|
+
return url.replace(/\/+$/, "") + (ns.startsWith("/") ? ns : `/${ns}`);
|
|
1705
|
+
}
|
|
1706
|
+
async function runSocketIoTest(cfg) {
|
|
1707
|
+
const messages = [];
|
|
1708
|
+
const collectMs = clampWindow(cfg.collectMs);
|
|
1709
|
+
let auth;
|
|
1710
|
+
try {
|
|
1711
|
+
auth = parseJsonObject(cfg.auth, "Auth");
|
|
1712
|
+
} catch (e) {
|
|
1713
|
+
return errorResult(e instanceof Error ? e.message : String(e), messages);
|
|
1714
|
+
}
|
|
1715
|
+
let emitArgs = [];
|
|
1716
|
+
if (cfg.emitArgs && cfg.emitArgs.trim()) {
|
|
1717
|
+
try {
|
|
1718
|
+
const parsed = JSON.parse(cfg.emitArgs);
|
|
1719
|
+
emitArgs = Array.isArray(parsed) ? parsed : [parsed];
|
|
1720
|
+
} catch {
|
|
1721
|
+
return errorResult("Emit arguments are not valid JSON", messages);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
return new Promise((resolve2) => {
|
|
1725
|
+
let settled = false;
|
|
1726
|
+
let socket;
|
|
1727
|
+
let timer;
|
|
1728
|
+
const finish = async (connectError) => {
|
|
1729
|
+
if (settled) return;
|
|
1730
|
+
settled = true;
|
|
1731
|
+
if (timer) clearTimeout(timer);
|
|
1732
|
+
try {
|
|
1733
|
+
socket?.disconnect();
|
|
1734
|
+
} catch {
|
|
1735
|
+
}
|
|
1736
|
+
if (connectError && messages.length === 0) {
|
|
1737
|
+
resolve2(errorResult(connectError, messages));
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
resolve2(await assertOver(cfg, messages));
|
|
1741
|
+
};
|
|
1742
|
+
try {
|
|
1743
|
+
socket = socket_ioClient.io(targetUrl(cfg.url, cfg.namespace), {
|
|
1744
|
+
path: cfg.path?.trim() || "/socket.io",
|
|
1745
|
+
transports: ["websocket", "polling"],
|
|
1746
|
+
extraHeaders: cfg.headers,
|
|
1747
|
+
auth,
|
|
1748
|
+
forceNew: true,
|
|
1749
|
+
reconnection: false
|
|
1750
|
+
});
|
|
1751
|
+
} catch (err) {
|
|
1752
|
+
resolve2(errorResult(err instanceof Error ? err.message : String(err), messages));
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
socket.on("connect", () => {
|
|
1756
|
+
const ev = cfg.emitEvent?.trim();
|
|
1757
|
+
if (ev) setTimeout(() => {
|
|
1758
|
+
try {
|
|
1759
|
+
socket.emit(ev, ...emitArgs);
|
|
1760
|
+
} catch {
|
|
1761
|
+
}
|
|
1762
|
+
}, 100);
|
|
1763
|
+
timer = setTimeout(() => void finish(), collectMs);
|
|
1764
|
+
});
|
|
1765
|
+
socket.on("connect_error", (err) => {
|
|
1766
|
+
void finish(`Could not connect to the server${err.message ? ` [${err.message}]` : ""}`);
|
|
1767
|
+
});
|
|
1768
|
+
socket.onAny((event, ...args) => {
|
|
1769
|
+
const payload = JSON.stringify(args.length <= 1 ? args[0] ?? null : args);
|
|
1770
|
+
messages.push({ payload, event, args, timestamp: Date.now() });
|
|
1771
|
+
});
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
const GQL_SUB_ID = "1";
|
|
1775
|
+
async function runGraphqlWsTest(cfg) {
|
|
1776
|
+
const messages = [];
|
|
1777
|
+
const collectMs = clampWindow(cfg.collectMs);
|
|
1778
|
+
let variables;
|
|
1779
|
+
let connectionParams;
|
|
1780
|
+
try {
|
|
1781
|
+
variables = parseJsonObject(cfg.variables, "Variables");
|
|
1782
|
+
connectionParams = parseJsonObject(cfg.connectionParams, "Connection params");
|
|
1783
|
+
} catch (e) {
|
|
1784
|
+
return errorResult(e instanceof Error ? e.message : String(e), messages);
|
|
1785
|
+
}
|
|
1786
|
+
return new Promise((resolve2) => {
|
|
1787
|
+
let settled = false;
|
|
1788
|
+
let connected = false;
|
|
1789
|
+
let lastError = "";
|
|
1790
|
+
let ws;
|
|
1791
|
+
let timer;
|
|
1792
|
+
const finish = async (connectError) => {
|
|
1793
|
+
if (settled) return;
|
|
1794
|
+
settled = true;
|
|
1795
|
+
if (timer) clearTimeout(timer);
|
|
1796
|
+
try {
|
|
1797
|
+
ws?.close();
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
if (connectError && messages.length === 0) {
|
|
1801
|
+
resolve2(errorResult(connectError, messages));
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
resolve2(await assertOver(cfg, messages));
|
|
1805
|
+
};
|
|
1806
|
+
try {
|
|
1807
|
+
ws = new WebSocket(cfg.url, "graphql-transport-ws", { headers: cfg.headers });
|
|
1808
|
+
} catch (err) {
|
|
1809
|
+
resolve2(errorResult(err instanceof Error ? err.message : String(err), messages));
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
ws.on("open", () => {
|
|
1813
|
+
connected = true;
|
|
1814
|
+
ws.send(JSON.stringify({ type: "connection_init", payload: connectionParams }));
|
|
1815
|
+
timer = setTimeout(() => void finish(), collectMs);
|
|
1816
|
+
});
|
|
1817
|
+
ws.on("message", (raw) => {
|
|
1818
|
+
let msg;
|
|
1819
|
+
try {
|
|
1820
|
+
msg = JSON.parse(raw.toString());
|
|
1821
|
+
} catch {
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
switch (msg.type) {
|
|
1825
|
+
case "connection_ack":
|
|
1826
|
+
ws.send(JSON.stringify({ id: GQL_SUB_ID, type: "subscribe", payload: { query: cfg.query, variables, operationName: cfg.operationName } }));
|
|
1827
|
+
break;
|
|
1828
|
+
case "next": {
|
|
1829
|
+
const payload = msg.payload;
|
|
1830
|
+
const data = payload?.data;
|
|
1831
|
+
messages.push({ payload: JSON.stringify(data ?? payload ?? null), data, errors: payload?.errors, timestamp: Date.now() });
|
|
1832
|
+
break;
|
|
1833
|
+
}
|
|
1834
|
+
case "error":
|
|
1835
|
+
lastError = JSON.stringify(msg.payload);
|
|
1836
|
+
void finish(`The subscription returned an error${lastError ? ` [${lastError}]` : ""}`);
|
|
1837
|
+
break;
|
|
1838
|
+
case "complete":
|
|
1839
|
+
void finish();
|
|
1840
|
+
break;
|
|
1841
|
+
case "ping":
|
|
1842
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
1843
|
+
break;
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
ws.on("error", (err) => {
|
|
1847
|
+
lastError = err.message;
|
|
1848
|
+
void finish(err.message || "Could not connect to the server");
|
|
1849
|
+
});
|
|
1850
|
+
ws.on("close", () => {
|
|
1851
|
+
if (settled) return;
|
|
1852
|
+
void finish(connected ? `The subscription connection was closed before the window finished${lastError ? ` [${lastError}]` : ""}.` : `Could not connect to the server${lastError ? ` [${lastError}]` : ""}`);
|
|
1853
|
+
});
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1522
1856
|
const SIGNATURE = Buffer.from("NTLMSSP\0", "latin1");
|
|
1523
1857
|
const F_UNICODE = 1;
|
|
1524
1858
|
const F_OEM = 2;
|
|
@@ -1695,7 +2029,7 @@ async function buildAuthHeaders(auth, vars) {
|
|
|
1695
2029
|
const username = interpolate(auth.username ?? "", vars);
|
|
1696
2030
|
headers["Authorization"] = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
|
1697
2031
|
}
|
|
1698
|
-
if (auth.type === "apikey" && auth.apiKeyIn === "header") {
|
|
2032
|
+
if (auth.type === "apikey" && (auth.apiKeyIn ?? "header") === "header") {
|
|
1699
2033
|
let value = auth.apiKeyValue ?? "";
|
|
1700
2034
|
if (!value && auth.apiKeySecretRef) value = await getSecret(auth.apiKeySecretRef) ?? "";
|
|
1701
2035
|
value = interpolate(value, vars);
|
|
@@ -2286,6 +2620,7 @@ async function resolveInlineSecrets(req) {
|
|
|
2286
2620
|
async function executeRunnerRequest(opts) {
|
|
2287
2621
|
const { req, collectionVars, envVars, globals: globals2, dispatcher, piiMaskPatterns, proxy, tls, onScriptOutput } = opts;
|
|
2288
2622
|
let { localVars } = opts;
|
|
2623
|
+
const folderVars = opts.folderVars ?? {};
|
|
2289
2624
|
applyRequestDefaults(req);
|
|
2290
2625
|
const base = {
|
|
2291
2626
|
requestId: req.id,
|
|
@@ -2296,13 +2631,99 @@ async function executeRunnerRequest(opts) {
|
|
|
2296
2631
|
};
|
|
2297
2632
|
const dynamicVars = await buildDynamicVars();
|
|
2298
2633
|
if (localVars && Object.keys(localVars).length > 0) {
|
|
2299
|
-
localVars = resolveDataRow(localVars, mergeVars(envVars, collectionVars, globals2, {}, dynamicVars));
|
|
2634
|
+
localVars = resolveDataRow(localVars, mergeVars(envVars, collectionVars, globals2, {}, dynamicVars, folderVars));
|
|
2300
2635
|
}
|
|
2301
|
-
let vars = mergeVars(envVars, collectionVars, globals2, localVars, dynamicVars);
|
|
2636
|
+
let vars = mergeVars(envVars, collectionVars, globals2, localVars, dynamicVars, folderVars);
|
|
2302
2637
|
let updatedEnvVars = { ...envVars };
|
|
2303
2638
|
let updatedCollectionVars = { ...collectionVars };
|
|
2304
2639
|
let updatedGlobals = { ...globals2 };
|
|
2640
|
+
let updatedFolderVars = { ...folderVars };
|
|
2305
2641
|
let preScriptError;
|
|
2642
|
+
if (req.protocol === "mqtt") {
|
|
2643
|
+
const m = req.mqtt ?? {};
|
|
2644
|
+
const istr = (s) => s ? interpolate(s, vars) : void 0;
|
|
2645
|
+
const mqttStart = Date.now();
|
|
2646
|
+
const testRes = await runMqttTest({
|
|
2647
|
+
url: interpolate(req.url, vars),
|
|
2648
|
+
clientId: istr(m.clientId),
|
|
2649
|
+
username: istr(m.username),
|
|
2650
|
+
password: m.password ? interpolate(m.password, vars) : void 0,
|
|
2651
|
+
cleanSession: m.cleanSession !== false,
|
|
2652
|
+
subscribeTopic: istr(m.subscribeTopic),
|
|
2653
|
+
qos: m.qos,
|
|
2654
|
+
publishTopic: istr(m.publishTopic),
|
|
2655
|
+
publishPayload: istr(m.publishPayload),
|
|
2656
|
+
retain: m.retain,
|
|
2657
|
+
collectMs: m.collectMs ?? 2e3,
|
|
2658
|
+
script: m.testScript ?? "",
|
|
2659
|
+
envVars,
|
|
2660
|
+
collectionVars,
|
|
2661
|
+
globals: globals2,
|
|
2662
|
+
localVars
|
|
2663
|
+
});
|
|
2664
|
+
const status = testRes.error ? "error" : testRes.testResults.length === 0 ? "skipped" : testRes.testResults.every((t) => t.passed) ? "passed" : "failed";
|
|
2665
|
+
return {
|
|
2666
|
+
result: {
|
|
2667
|
+
...base,
|
|
2668
|
+
status,
|
|
2669
|
+
durationMs: Date.now() - mqttStart,
|
|
2670
|
+
testResults: testRes.testResults,
|
|
2671
|
+
consoleOutput: testRes.consoleOutput,
|
|
2672
|
+
error: testRes.error
|
|
2673
|
+
},
|
|
2674
|
+
updatedEnvVars,
|
|
2675
|
+
updatedCollectionVars,
|
|
2676
|
+
updatedGlobals,
|
|
2677
|
+
updatedLocalVars: localVars,
|
|
2678
|
+
updatedFolderVars
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
if (req.protocol === "websocket" || req.protocol === "socketio") {
|
|
2682
|
+
const rtHeaders = {};
|
|
2683
|
+
for (const h of req.headers ?? []) if (h.enabled && h.key) rtHeaders[interpolate(h.key, vars)] = interpolate(h.value, vars);
|
|
2684
|
+
const istr = (s) => s ? interpolate(s, vars) : void 0;
|
|
2685
|
+
const rtStart = Date.now();
|
|
2686
|
+
const testRes = req.protocol === "websocket" ? await runWsTest({
|
|
2687
|
+
url: interpolate(req.url, vars),
|
|
2688
|
+
headers: rtHeaders,
|
|
2689
|
+
sendMessage: istr(req.websocket?.sendMessage),
|
|
2690
|
+
collectMs: req.websocket?.collectMs ?? 3e3,
|
|
2691
|
+
script: req.websocket?.testScript ?? "",
|
|
2692
|
+
envVars,
|
|
2693
|
+
collectionVars,
|
|
2694
|
+
globals: globals2,
|
|
2695
|
+
localVars
|
|
2696
|
+
}) : await runSocketIoTest({
|
|
2697
|
+
url: interpolate(req.url, vars),
|
|
2698
|
+
namespace: req.socketio?.namespace,
|
|
2699
|
+
auth: istr(req.socketio?.auth),
|
|
2700
|
+
headers: rtHeaders,
|
|
2701
|
+
emitEvent: istr(req.socketio?.emitEvent),
|
|
2702
|
+
emitArgs: istr(req.socketio?.emitArgs),
|
|
2703
|
+
collectMs: req.socketio?.collectMs ?? 3e3,
|
|
2704
|
+
script: req.socketio?.testScript ?? "",
|
|
2705
|
+
envVars,
|
|
2706
|
+
collectionVars,
|
|
2707
|
+
globals: globals2,
|
|
2708
|
+
localVars
|
|
2709
|
+
});
|
|
2710
|
+
const status = testRes.error ? "error" : testRes.testResults.length === 0 ? "skipped" : testRes.testResults.every((t) => t.passed) ? "passed" : "failed";
|
|
2711
|
+
return {
|
|
2712
|
+
result: {
|
|
2713
|
+
...base,
|
|
2714
|
+
status,
|
|
2715
|
+
durationMs: Date.now() - rtStart,
|
|
2716
|
+
testResults: testRes.testResults,
|
|
2717
|
+
consoleOutput: testRes.consoleOutput,
|
|
2718
|
+
error: testRes.error
|
|
2719
|
+
},
|
|
2720
|
+
updatedEnvVars,
|
|
2721
|
+
updatedCollectionVars,
|
|
2722
|
+
updatedGlobals,
|
|
2723
|
+
updatedLocalVars: localVars,
|
|
2724
|
+
updatedFolderVars
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2306
2727
|
let inlineSecrets = {};
|
|
2307
2728
|
try {
|
|
2308
2729
|
inlineSecrets = await resolveInlineSecrets(req);
|
|
@@ -2317,7 +2738,8 @@ async function executeRunnerRequest(opts) {
|
|
|
2317
2738
|
updatedEnvVars,
|
|
2318
2739
|
updatedCollectionVars,
|
|
2319
2740
|
updatedGlobals,
|
|
2320
|
-
updatedLocalVars: localVars
|
|
2741
|
+
updatedLocalVars: localVars,
|
|
2742
|
+
updatedFolderVars
|
|
2321
2743
|
};
|
|
2322
2744
|
}
|
|
2323
2745
|
if (Object.keys(inlineSecrets).length) vars = { ...vars, ...inlineSecrets };
|
|
@@ -2327,6 +2749,7 @@ async function executeRunnerRequest(opts) {
|
|
|
2327
2749
|
collectionVars: { ...collectionVars },
|
|
2328
2750
|
globals: { ...globals2 },
|
|
2329
2751
|
localVars: { ...localVars },
|
|
2752
|
+
folderVars: { ...updatedFolderVars },
|
|
2330
2753
|
piiMaskPatterns
|
|
2331
2754
|
});
|
|
2332
2755
|
preScriptError = r.error;
|
|
@@ -2334,10 +2757,11 @@ async function executeRunnerRequest(opts) {
|
|
|
2334
2757
|
updatedEnvVars = r.updatedEnvVars;
|
|
2335
2758
|
updatedCollectionVars = r.updatedCollectionVars;
|
|
2336
2759
|
updatedGlobals = r.updatedGlobals;
|
|
2760
|
+
updatedFolderVars = r.updatedFolderVars;
|
|
2337
2761
|
patchGlobals(r.updatedGlobals);
|
|
2338
2762
|
await persistGlobals();
|
|
2339
2763
|
onScriptOutput?.("pre", r.consoleOutput, r.error);
|
|
2340
|
-
vars = mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
|
|
2764
|
+
vars = mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars, updatedFolderVars);
|
|
2341
2765
|
if (Object.keys(inlineSecrets).length) vars = { ...vars, ...inlineSecrets };
|
|
2342
2766
|
}
|
|
2343
2767
|
const resolvedUrl = buildUrl(req.url, req.params, vars);
|
|
@@ -2366,6 +2790,7 @@ async function executeRunnerRequest(opts) {
|
|
|
2366
2790
|
collectionVars: updatedCollectionVars,
|
|
2367
2791
|
globals: updatedGlobals,
|
|
2368
2792
|
localVars,
|
|
2793
|
+
folderVars: updatedFolderVars,
|
|
2369
2794
|
response: scriptResponse,
|
|
2370
2795
|
piiMaskPatterns
|
|
2371
2796
|
});
|
|
@@ -2375,6 +2800,7 @@ async function executeRunnerRequest(opts) {
|
|
|
2375
2800
|
updatedEnvVars = r.updatedEnvVars;
|
|
2376
2801
|
updatedCollectionVars = r.updatedCollectionVars;
|
|
2377
2802
|
updatedGlobals = r.updatedGlobals;
|
|
2803
|
+
updatedFolderVars = r.updatedFolderVars;
|
|
2378
2804
|
localVars = r.updatedLocalVars;
|
|
2379
2805
|
patchGlobals(r.updatedGlobals);
|
|
2380
2806
|
await persistGlobals();
|
|
@@ -2414,7 +2840,8 @@ async function executeRunnerRequest(opts) {
|
|
|
2414
2840
|
updatedEnvVars,
|
|
2415
2841
|
updatedCollectionVars,
|
|
2416
2842
|
updatedGlobals,
|
|
2417
|
-
updatedLocalVars: localVars
|
|
2843
|
+
updatedLocalVars: localVars,
|
|
2844
|
+
updatedFolderVars
|
|
2418
2845
|
};
|
|
2419
2846
|
} catch (err) {
|
|
2420
2847
|
return {
|
|
@@ -2428,7 +2855,8 @@ async function executeRunnerRequest(opts) {
|
|
|
2428
2855
|
updatedEnvVars,
|
|
2429
2856
|
updatedCollectionVars,
|
|
2430
2857
|
updatedGlobals,
|
|
2431
|
-
updatedLocalVars: localVars
|
|
2858
|
+
updatedLocalVars: localVars,
|
|
2859
|
+
updatedFolderVars
|
|
2432
2860
|
};
|
|
2433
2861
|
}
|
|
2434
2862
|
}
|
|
@@ -2510,7 +2938,11 @@ exports.registerSecretProvider = registerSecretProvider;
|
|
|
2510
2938
|
exports.registeredSchemes = registeredSchemes;
|
|
2511
2939
|
exports.resolveDataRow = resolveDataRow;
|
|
2512
2940
|
exports.resolveExternalSecret = resolveExternalSecret;
|
|
2941
|
+
exports.runGraphqlWsTest = runGraphqlWsTest;
|
|
2942
|
+
exports.runMqttTest = runMqttTest;
|
|
2513
2943
|
exports.runScript = runScript;
|
|
2944
|
+
exports.runSocketIoTest = runSocketIoTest;
|
|
2945
|
+
exports.runWsTest = runWsTest;
|
|
2514
2946
|
exports.setGlobals = setGlobals;
|
|
2515
2947
|
exports.setSecretsConfig = setSecretsConfig;
|
|
2516
2948
|
exports.syntheticHttpFailure = syntheticHttpFailure;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
async function runPlanConcurrent(concurrency, items, isBarrier, run) {
|
|
3
|
+
const n = Math.max(1, Math.floor(concurrency));
|
|
4
|
+
const pool = [];
|
|
5
|
+
const settleOne = async () => {
|
|
6
|
+
const done = await Promise.race(pool.map((x) => x.p));
|
|
7
|
+
const pi = pool.findIndex((x) => x.i === done);
|
|
8
|
+
if (pi >= 0) pool.splice(pi, 1);
|
|
9
|
+
};
|
|
10
|
+
const drain = async () => {
|
|
11
|
+
while (pool.length) await settleOne();
|
|
12
|
+
};
|
|
13
|
+
for (let i = 0; i < items.length; i++) {
|
|
14
|
+
const item = items[i];
|
|
15
|
+
if (isBarrier(item)) {
|
|
16
|
+
await drain();
|
|
17
|
+
await run(item, i);
|
|
18
|
+
} else {
|
|
19
|
+
const idx = i;
|
|
20
|
+
pool.push({ p: run(item, idx).then(() => idx), i: idx });
|
|
21
|
+
if (pool.length >= n) await settleOne();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
await drain();
|
|
25
|
+
}
|
|
26
|
+
exports.runPlanConcurrent = runPlanConcurrent;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const handle = require("./handle-
|
|
2
|
+
const handle = require("./handle-Crv1i9hb.js");
|
|
3
3
|
const https = require("node:https");
|
|
4
4
|
const http = require("node:http");
|
|
5
5
|
const xmldom = require("@xmldom/xmldom");
|
|
@@ -313,7 +313,7 @@ function registerSoapHandlers(ipc) {
|
|
|
313
313
|
handle.handleIpc(ipc, handle.IPC.wsdl.import, async (_event, opts) => {
|
|
314
314
|
const { validateWsdlImport } = await Promise.resolve().then(() => require("./ipc-validate-sqUFqRmE.js"));
|
|
315
315
|
validateWsdlImport(opts);
|
|
316
|
-
const { importWsdl } = await Promise.resolve().then(() => require("./import-
|
|
316
|
+
const { importWsdl } = await Promise.resolve().then(() => require("./import-DTcZMRNu.js"));
|
|
317
317
|
const wsdlText = opts.xml ?? (opts.url ? await fetchUrl(opts.url) : "");
|
|
318
318
|
if (!wsdlText) throw new Error("wsdl:import requires either `url` or `xml`");
|
|
319
319
|
return importWsdl(wsdlText, { name: opts.name, existingMockPorts: opts.existingMockPorts });
|