@testsmith/api-spector 0.3.1 → 0.3.3
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 +21 -4
- package/out/main/agents.js +24 -35
- package/out/main/chunks/{auth-builder-CRQayp8x.js → auth-builder-CUs9yzOF.js} +4 -3
- package/out/main/chunks/cli-common-CDQY1erJ.js +98 -0
- package/out/main/chunks/handle-C0IQL-Vl.js +164 -0
- package/out/main/chunks/{import-C9qdkBCH.js → import-C5Ngq8hk.js} +2 -1
- package/out/main/chunks/{ipc-validate-CscN4HfG.js → ipc-validate-k6KI8adf.js} +4 -2
- package/out/main/chunks/{request-collection-d2vgYls1.js → request-collection-8TOVNXE0.js} +296 -350
- package/out/main/chunks/{snapshots-CQliv7WB.js → snapshots-UFd3XgSS.js} +366 -26
- package/out/main/chunks/{soap-handler-Cpj-JwyA.js → soap-handler-B9x_YCtj.js} +6 -5
- package/out/main/contract.js +515 -58
- package/out/main/index.js +825 -785
- package/out/main/mock.js +30 -84
- package/out/main/record.js +19 -53
- package/out/main/runner.js +59 -321
- package/out/main/wsdl.js +9 -45
- package/out/preload/index.js +243 -90
- package/out/renderer/assets/index-CjvjKHtF.css +2 -0
- package/out/renderer/assets/{index-DYoP9JFL.js → index-vRsURTUY.js} +3652 -3765
- package/out/renderer/index.html +2 -2
- package/package.json +7 -4
- package/out/renderer/assets/index-D4vegaiz.css +0 -2
package/out/main/index.js
CHANGED
|
@@ -22,11 +22,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
24
|
const electron = require("electron");
|
|
25
|
+
const handle = require("./chunks/handle-C0IQL-Vl.js");
|
|
25
26
|
const path = require("path");
|
|
26
27
|
const fs = require("fs");
|
|
27
28
|
const promises = require("fs/promises");
|
|
28
|
-
const requestCollection = require("./chunks/request-collection-
|
|
29
|
-
const authBuilder = require("./chunks/auth-builder-
|
|
29
|
+
const requestCollection = require("./chunks/request-collection-8TOVNXE0.js");
|
|
30
|
+
const authBuilder = require("./chunks/auth-builder-CUs9yzOF.js");
|
|
31
|
+
const ipcValidate = require("./chunks/ipc-validate-k6KI8adf.js");
|
|
30
32
|
const uuid = require("uuid");
|
|
31
33
|
const jsYaml = require("js-yaml");
|
|
32
34
|
const undici = require("undici");
|
|
@@ -34,11 +36,10 @@ const JSZip = require("jszip");
|
|
|
34
36
|
const mockServer = require("./chunks/mock-server-DSLR2ulH.js");
|
|
35
37
|
const http = require("http");
|
|
36
38
|
const WebSocket = require("ws");
|
|
37
|
-
const soapHandler = require("./chunks/soap-handler-
|
|
39
|
+
const soapHandler = require("./chunks/soap-handler-B9x_YCtj.js");
|
|
38
40
|
const os = require("os");
|
|
39
41
|
const crypto = require("crypto");
|
|
40
|
-
const snapshots = require("./chunks/snapshots-
|
|
41
|
-
const ipcValidate = require("./chunks/ipc-validate-CscN4HfG.js");
|
|
42
|
+
const snapshots = require("./chunks/snapshots-UFd3XgSS.js");
|
|
42
43
|
const simpleGit = require("simple-git");
|
|
43
44
|
const recorder = require("./chunks/recorder-DFxJgn9c.js");
|
|
44
45
|
require("vm");
|
|
@@ -211,7 +212,7 @@ function defaultWorkspaceName() {
|
|
|
211
212
|
return "my-workspace.spector";
|
|
212
213
|
}
|
|
213
214
|
function registerFileHandlers(ipc) {
|
|
214
|
-
ipc.
|
|
215
|
+
handle.handleIpc(ipc, handle.IPC.file.openWorkspace, async () => {
|
|
215
216
|
const result = await electron.dialog.showOpenDialog({
|
|
216
217
|
title: "Open Workspace",
|
|
217
218
|
defaultPath: dialogStartDir(),
|
|
@@ -227,7 +228,7 @@ function registerFileHandlers(ipc) {
|
|
|
227
228
|
const raw = await promises.readFile(wsPath, "utf8");
|
|
228
229
|
return { workspace: JSON.parse(raw), workspacePath: wsPath };
|
|
229
230
|
});
|
|
230
|
-
ipc.
|
|
231
|
+
handle.handleIpc(ipc, handle.IPC.file.newWorkspace, async () => {
|
|
231
232
|
const startDir = dialogStartDir();
|
|
232
233
|
const defaultPath = startDir ? path.join(startDir, defaultWorkspaceName()) : defaultWorkspaceName();
|
|
233
234
|
const result = await electron.dialog.showSaveDialog({
|
|
@@ -254,34 +255,34 @@ function registerFileHandlers(ipc) {
|
|
|
254
255
|
await saveLastWorkspacePath(result.filePath);
|
|
255
256
|
return { workspace: ws, workspacePath: result.filePath };
|
|
256
257
|
});
|
|
257
|
-
ipc.
|
|
258
|
+
handle.handleIpc(ipc, handle.IPC.file.saveWorkspace, async (_e, ws) => {
|
|
258
259
|
if (!workspaceFile) return;
|
|
259
260
|
await atomicWrite(workspaceFile, JSON.stringify(ws, null, 2));
|
|
260
261
|
if (workspaceDir) await ensureVscodeFileAssociation(workspaceDir);
|
|
261
262
|
});
|
|
262
|
-
ipc.
|
|
263
|
+
handle.handleIpc(ipc, handle.IPC.file.loadCollection, async (_e, relPath) => {
|
|
263
264
|
if (!workspaceDir) throw new Error("No workspace open");
|
|
264
265
|
const raw = await promises.readFile(path.resolve(workspaceDir, relPath), "utf8");
|
|
265
266
|
return JSON.parse(raw);
|
|
266
267
|
});
|
|
267
|
-
ipc.
|
|
268
|
+
handle.handleIpc(ipc, handle.IPC.file.saveCollection, async (_e, relPath, col) => {
|
|
268
269
|
if (!workspaceDir) throw new Error("No workspace open");
|
|
269
270
|
const fullPath = path.resolve(workspaceDir, relPath);
|
|
270
271
|
await promises.mkdir(path.dirname(fullPath), { recursive: true });
|
|
271
272
|
await atomicWrite(fullPath, JSON.stringify(col, null, 2));
|
|
272
273
|
});
|
|
273
|
-
ipc.
|
|
274
|
+
handle.handleIpc(ipc, handle.IPC.file.loadEnvironment, async (_e, relPath) => {
|
|
274
275
|
if (!workspaceDir) throw new Error("No workspace open");
|
|
275
276
|
const raw = await promises.readFile(path.resolve(workspaceDir, relPath), "utf8");
|
|
276
277
|
return JSON.parse(raw);
|
|
277
278
|
});
|
|
278
|
-
ipc.
|
|
279
|
+
handle.handleIpc(ipc, handle.IPC.file.saveEnvironment, async (_e, relPath, env) => {
|
|
279
280
|
if (!workspaceDir) throw new Error("No workspace open");
|
|
280
281
|
const fullPath = path.resolve(workspaceDir, relPath);
|
|
281
282
|
await promises.mkdir(path.dirname(fullPath), { recursive: true });
|
|
282
283
|
await atomicWrite(fullPath, JSON.stringify(env, null, 2));
|
|
283
284
|
});
|
|
284
|
-
ipc.
|
|
285
|
+
handle.handleIpc(ipc, handle.IPC.file.deleteWorkspaceFile, async (_e, relPath) => {
|
|
285
286
|
if (!workspaceDir) throw new Error("No workspace open");
|
|
286
287
|
const fullPath = path.resolve(workspaceDir, relPath);
|
|
287
288
|
if (!fullPath.startsWith(path.resolve(workspaceDir) + (process.platform === "win32" ? "\\" : "/"))) {
|
|
@@ -293,14 +294,14 @@ function registerFileHandlers(ipc) {
|
|
|
293
294
|
if (err.code !== "ENOENT") throw err;
|
|
294
295
|
}
|
|
295
296
|
});
|
|
296
|
-
ipc.
|
|
297
|
+
handle.handleIpc(ipc, handle.IPC.dialog.pickDir, async () => {
|
|
297
298
|
const result = await electron.dialog.showOpenDialog({
|
|
298
299
|
title: "Select Output Directory",
|
|
299
300
|
properties: ["openDirectory", "createDirectory"]
|
|
300
301
|
});
|
|
301
302
|
return result.canceled ? null : result.filePaths[0];
|
|
302
303
|
});
|
|
303
|
-
ipc.
|
|
304
|
+
handle.handleIpc(ipc, handle.IPC.results.save, async (_e, content, defaultName) => {
|
|
304
305
|
const ext = defaultName.endsWith(".xml") ? "xml" : defaultName.endsWith(".html") ? "html" : "json";
|
|
305
306
|
const allFilters = [
|
|
306
307
|
{ name: "JSON", extensions: ["json"] },
|
|
@@ -316,19 +317,19 @@ function registerFileHandlers(ipc) {
|
|
|
316
317
|
await promises.writeFile(result.filePath, content, "utf8");
|
|
317
318
|
return true;
|
|
318
319
|
});
|
|
319
|
-
ipc.
|
|
320
|
-
ipc.
|
|
320
|
+
handle.handleIpc(ipc, handle.IPC.globals.get, () => requestCollection.getGlobals());
|
|
321
|
+
handle.handleIpc(ipc, handle.IPC.globals.set, async (_e, patch) => {
|
|
321
322
|
requestCollection.setGlobals(patch);
|
|
322
323
|
await requestCollection.persistGlobals();
|
|
323
324
|
});
|
|
324
|
-
ipc.
|
|
325
|
+
handle.handleIpc(ipc, handle.IPC.file.closeWorkspace, async () => {
|
|
325
326
|
workspaceDir = null;
|
|
326
327
|
workspaceFile = null;
|
|
327
328
|
await promises.writeFile(LAST_WS_FILE, JSON.stringify({ path: null }), "utf8").catch((err) => {
|
|
328
329
|
console.warn("file-handler: could not clear last-workspace pointer", err);
|
|
329
330
|
});
|
|
330
331
|
});
|
|
331
|
-
ipc.
|
|
332
|
+
handle.handleIpc(ipc, handle.IPC.file.getLastWorkspace, async () => {
|
|
332
333
|
const cwd = process.env.API_SPECTOR_LAUNCH_CWD;
|
|
333
334
|
if (cwd) {
|
|
334
335
|
const fromCwd = await tryOpenWorkspaceInDir(cwd);
|
|
@@ -372,6 +373,255 @@ async function tryOpenWorkspaceInDir(dir) {
|
|
|
372
373
|
function getWorkspaceDir() {
|
|
373
374
|
return workspaceDir;
|
|
374
375
|
}
|
|
376
|
+
function asObject(value) {
|
|
377
|
+
return value && typeof value === "object" ? value : null;
|
|
378
|
+
}
|
|
379
|
+
function readStringField(obj, key) {
|
|
380
|
+
const value = obj?.[key];
|
|
381
|
+
return typeof value === "string" && value ? value : void 0;
|
|
382
|
+
}
|
|
383
|
+
function safeProxySummary(proxy) {
|
|
384
|
+
if (!proxy?.url?.trim()) return "off";
|
|
385
|
+
try {
|
|
386
|
+
const normalized = requestCollection.buildProxyUri({ url: proxy.url });
|
|
387
|
+
const parsed = new URL(normalized);
|
|
388
|
+
const host = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname;
|
|
389
|
+
const auth = proxy.auth ? "yes" : "no";
|
|
390
|
+
return `${parsed.protocol}//${host} auth=${auth}`;
|
|
391
|
+
} catch {
|
|
392
|
+
return `invalid input "${proxy.url}"`;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function safeTlsSummary(tls) {
|
|
396
|
+
if (!tls) return "off";
|
|
397
|
+
const parts = [];
|
|
398
|
+
if (tls.rejectUnauthorized !== void 0) parts.push(`rejectUnauthorized=${String(tls.rejectUnauthorized)}`);
|
|
399
|
+
if (tls.caCertPath) parts.push(`ca=${tls.caCertPath}`);
|
|
400
|
+
if (tls.clientCertPath) parts.push(`cert=${tls.clientCertPath}`);
|
|
401
|
+
if (tls.clientKeyPath) parts.push(`key=${tls.clientKeyPath}`);
|
|
402
|
+
return parts.length ? parts.join(", ") : "on";
|
|
403
|
+
}
|
|
404
|
+
function formatRequestError(err, context) {
|
|
405
|
+
const obj = asObject(err);
|
|
406
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
407
|
+
const code = readStringField(obj, "code");
|
|
408
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
409
|
+
const causeObj = obj ? asObject(obj["cause"]) : null;
|
|
410
|
+
const causeMessage = readStringField(causeObj, "message");
|
|
411
|
+
const causeCode = readStringField(causeObj, "code");
|
|
412
|
+
const lines = [
|
|
413
|
+
`[request:send] ${context.method} ${context.resolvedUrl}`,
|
|
414
|
+
`[request:send] requestId=${context.requestId}`,
|
|
415
|
+
`[request:send] proxy=${safeProxySummary(context.proxy)}`,
|
|
416
|
+
`[request:send] tls=${safeTlsSummary(context.tls)}`,
|
|
417
|
+
`[request:send] error=${message}${code ? ` (code=${code})` : ""}`
|
|
418
|
+
];
|
|
419
|
+
if (causeMessage) {
|
|
420
|
+
lines.push(`[request:send] cause=${causeMessage}${causeCode ? ` (code=${causeCode})` : ""}`);
|
|
421
|
+
}
|
|
422
|
+
if (stack) {
|
|
423
|
+
const preview = stack.split("\n").slice(0, 6).join("\n");
|
|
424
|
+
lines.push("[request:send] stack:");
|
|
425
|
+
lines.push(preview);
|
|
426
|
+
}
|
|
427
|
+
return lines.join("\n");
|
|
428
|
+
}
|
|
429
|
+
function registerRequestHandler(ipc) {
|
|
430
|
+
handle.handleIpc(ipc, handle.IPC.request.send, async (_e, payload) => {
|
|
431
|
+
ipcValidate.validateSendRequestPayload(payload);
|
|
432
|
+
const {
|
|
433
|
+
request: req,
|
|
434
|
+
environment,
|
|
435
|
+
collectionVars,
|
|
436
|
+
globals: payloadGlobals,
|
|
437
|
+
proxy,
|
|
438
|
+
tls,
|
|
439
|
+
piiMaskPatterns = []
|
|
440
|
+
} = payload;
|
|
441
|
+
requestCollection.applyRequestDefaults(req);
|
|
442
|
+
const start = Date.now();
|
|
443
|
+
const liveGlobals = requestCollection.getGlobals();
|
|
444
|
+
const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
|
|
445
|
+
const envVars = await authBuilder.buildEnvVars(environment);
|
|
446
|
+
let localVars = {};
|
|
447
|
+
const decryptionWarnings = [];
|
|
448
|
+
if (environment) {
|
|
449
|
+
const masterKeySet = Boolean(process.env["API_SPECTOR_MASTER_KEY"]);
|
|
450
|
+
for (const v of environment.variables) {
|
|
451
|
+
if (!v.enabled || !v.secret || !v.secretEncrypted) continue;
|
|
452
|
+
if (!masterKeySet) {
|
|
453
|
+
decryptionWarnings.push(`[warn] Secret "${v.key}" was not decrypted: API_SPECTOR_MASTER_KEY is not set. Use the master password modal or export the variable in your shell.`);
|
|
454
|
+
} else if (envVars[v.key] === void 0) {
|
|
455
|
+
decryptionWarnings.push(`[warn] Secret "${v.key}" could not be decrypted: wrong password or corrupted data.`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const dynamicVars = await authBuilder.buildDynamicVars();
|
|
460
|
+
let vars = authBuilder.mergeVars(envVars, collectionVars, mergedGlobals, localVars, dynamicVars);
|
|
461
|
+
let preScriptMeta = { consoleOutput: [] };
|
|
462
|
+
let updatedCollectionVars = { ...collectionVars };
|
|
463
|
+
let updatedEnvVars = { ...envVars };
|
|
464
|
+
let updatedGlobals = { ...mergedGlobals };
|
|
465
|
+
if (req.preRequestScript?.trim()) {
|
|
466
|
+
const result = await requestCollection.runScript(authBuilder.interpolate(req.preRequestScript, vars), {
|
|
467
|
+
envVars: { ...envVars },
|
|
468
|
+
collectionVars: { ...collectionVars },
|
|
469
|
+
globals: { ...mergedGlobals },
|
|
470
|
+
localVars: {}
|
|
471
|
+
});
|
|
472
|
+
preScriptMeta = { error: result.error, consoleOutput: result.consoleOutput };
|
|
473
|
+
localVars = result.updatedLocalVars;
|
|
474
|
+
updatedEnvVars = result.updatedEnvVars;
|
|
475
|
+
updatedCollectionVars = result.updatedCollectionVars;
|
|
476
|
+
updatedGlobals = result.updatedGlobals;
|
|
477
|
+
requestCollection.patchGlobals(result.updatedGlobals);
|
|
478
|
+
await requestCollection.persistGlobals();
|
|
479
|
+
vars = authBuilder.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
|
|
480
|
+
}
|
|
481
|
+
let response;
|
|
482
|
+
let scriptResponse;
|
|
483
|
+
let sentRequest = { method: req.method, url: "", headers: {} };
|
|
484
|
+
const resolvedUrl = authBuilder.buildUrl(req.url, req.params, vars);
|
|
485
|
+
const secretValues = /* @__PURE__ */ new Set();
|
|
486
|
+
if (environment) {
|
|
487
|
+
for (const v of environment.variables) {
|
|
488
|
+
if (!v.enabled) continue;
|
|
489
|
+
if ((v.secret || v.envRef) && envVars[v.key]) {
|
|
490
|
+
secretValues.add(envVars[v.key]);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function redactSecrets(s) {
|
|
495
|
+
if (!secretValues.size) return s;
|
|
496
|
+
let result = s;
|
|
497
|
+
for (const secret of secretValues) {
|
|
498
|
+
if (secret) result = result.split(secret).join("[*****]");
|
|
499
|
+
}
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
502
|
+
function redactSentRequest(sr) {
|
|
503
|
+
const headers = {};
|
|
504
|
+
for (const [k, v] of Object.entries(sr.headers)) {
|
|
505
|
+
headers[k] = redactSecrets(v);
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
method: sr.method,
|
|
509
|
+
url: redactSecrets(sr.url),
|
|
510
|
+
headers,
|
|
511
|
+
body: sr.body !== void 0 ? redactSecrets(sr.body) : void 0
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
const dispatcher = await requestCollection.buildDispatcher(proxy, tls);
|
|
516
|
+
const exchange = await requestCollection.performHttpExchange({
|
|
517
|
+
req,
|
|
518
|
+
vars,
|
|
519
|
+
resolvedUrl,
|
|
520
|
+
dispatcher,
|
|
521
|
+
proxy,
|
|
522
|
+
tls,
|
|
523
|
+
onSent: (sent) => {
|
|
524
|
+
sentRequest = sent;
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
const maskedBody = requestCollection.maskPii(exchange.responseBody, piiMaskPatterns);
|
|
528
|
+
const maskedHeaders = requestCollection.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
|
|
529
|
+
const bodySize = Buffer.byteLength(exchange.responseBody, "utf8");
|
|
530
|
+
response = {
|
|
531
|
+
status: exchange.status,
|
|
532
|
+
statusText: exchange.statusText,
|
|
533
|
+
headers: maskedHeaders,
|
|
534
|
+
body: maskedBody,
|
|
535
|
+
bodySize,
|
|
536
|
+
durationMs: exchange.durationMs
|
|
537
|
+
};
|
|
538
|
+
scriptResponse = {
|
|
539
|
+
status: exchange.status,
|
|
540
|
+
statusText: exchange.statusText,
|
|
541
|
+
headers: exchange.rawHeaders,
|
|
542
|
+
body: exchange.responseBody,
|
|
543
|
+
bodySize,
|
|
544
|
+
durationMs: exchange.durationMs
|
|
545
|
+
};
|
|
546
|
+
} catch (err) {
|
|
547
|
+
const diagnostic = formatRequestError(err, {
|
|
548
|
+
requestId: req.id,
|
|
549
|
+
method: req.method,
|
|
550
|
+
resolvedUrl,
|
|
551
|
+
proxy,
|
|
552
|
+
tls
|
|
553
|
+
});
|
|
554
|
+
console.error(diagnostic);
|
|
555
|
+
response = {
|
|
556
|
+
status: 0,
|
|
557
|
+
statusText: "Error",
|
|
558
|
+
headers: {},
|
|
559
|
+
body: "",
|
|
560
|
+
bodySize: 0,
|
|
561
|
+
durationMs: Date.now() - start,
|
|
562
|
+
error: diagnostic
|
|
563
|
+
};
|
|
564
|
+
scriptResponse = response;
|
|
565
|
+
}
|
|
566
|
+
const schemaTestResults = !response.error ? requestCollection.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
|
|
567
|
+
let postTestResults = [];
|
|
568
|
+
let postConsole = [];
|
|
569
|
+
let postError;
|
|
570
|
+
if (req.postRequestScript?.trim() && !response.error) {
|
|
571
|
+
const result = await requestCollection.runScript(authBuilder.interpolate(req.postRequestScript, vars), {
|
|
572
|
+
envVars: { ...updatedEnvVars },
|
|
573
|
+
collectionVars: { ...updatedCollectionVars },
|
|
574
|
+
globals: { ...updatedGlobals },
|
|
575
|
+
localVars: { ...localVars },
|
|
576
|
+
// Pass the *unmasked* response so the script can extract real values
|
|
577
|
+
// (tokens, ids, …). The displayed `response` keeps the redacted copy.
|
|
578
|
+
response: scriptResponse
|
|
579
|
+
});
|
|
580
|
+
postTestResults = result.testResults;
|
|
581
|
+
postConsole = result.consoleOutput;
|
|
582
|
+
postError = result.error;
|
|
583
|
+
updatedEnvVars = result.updatedEnvVars;
|
|
584
|
+
updatedCollectionVars = result.updatedCollectionVars;
|
|
585
|
+
updatedGlobals = result.updatedGlobals;
|
|
586
|
+
localVars = result.updatedLocalVars;
|
|
587
|
+
requestCollection.patchGlobals(result.updatedGlobals);
|
|
588
|
+
await requestCollection.persistGlobals();
|
|
589
|
+
}
|
|
590
|
+
const combinedTestResults = [...schemaTestResults, ...postTestResults];
|
|
591
|
+
if (!response.error && response.status >= 400 && combinedTestResults.length === 0) {
|
|
592
|
+
combinedTestResults.push({
|
|
593
|
+
name: `HTTP status ${response.status} ${response.statusText}`.trim(),
|
|
594
|
+
passed: false,
|
|
595
|
+
error: `Request returned ${response.status} — no assertion was defined to verify the status code.`
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
const scriptResult = {
|
|
599
|
+
testResults: combinedTestResults,
|
|
600
|
+
consoleOutput: [...decryptionWarnings, ...preScriptMeta.consoleOutput, ...postConsole],
|
|
601
|
+
updatedEnvVars,
|
|
602
|
+
updatedCollectionVars,
|
|
603
|
+
updatedGlobals,
|
|
604
|
+
updatedLocalVars: localVars,
|
|
605
|
+
resolvedUrl,
|
|
606
|
+
preScriptError: preScriptMeta.error,
|
|
607
|
+
postScriptError: postError
|
|
608
|
+
};
|
|
609
|
+
return { response, scriptResult, sentRequest: redactSentRequest(sentRequest) };
|
|
610
|
+
});
|
|
611
|
+
handle.handleIpc(ipc, handle.IPC.script.runHook, async (_e, payload) => {
|
|
612
|
+
const { script, envVars, collectionVars, globals } = payload;
|
|
613
|
+
const result = await requestCollection.runScript(script, { envVars, collectionVars, globals, localVars: {} });
|
|
614
|
+
requestCollection.patchGlobals(result.updatedGlobals);
|
|
615
|
+
await requestCollection.persistGlobals();
|
|
616
|
+
return {
|
|
617
|
+
updatedEnvVars: result.updatedEnvVars,
|
|
618
|
+
updatedCollectionVars: result.updatedCollectionVars,
|
|
619
|
+
updatedGlobals: result.updatedGlobals,
|
|
620
|
+
consoleOutput: result.consoleOutput,
|
|
621
|
+
error: result.error
|
|
622
|
+
};
|
|
623
|
+
});
|
|
624
|
+
}
|
|
375
625
|
const POSTMAN_RULES = [
|
|
376
626
|
// Variables — environment
|
|
377
627
|
[/\bpm\.environment\.get\(/g, "sp.environment.get("],
|
|
@@ -1125,8 +1375,160 @@ async function importBruno(filePath) {
|
|
|
1125
1375
|
requests
|
|
1126
1376
|
};
|
|
1127
1377
|
}
|
|
1378
|
+
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
1379
|
+
const HTTP_TO_SPECTOR = {
|
|
1380
|
+
$guid: "$uuid",
|
|
1381
|
+
$randomInt: "$randomInt",
|
|
1382
|
+
$timestamp: "$timestamp",
|
|
1383
|
+
$datetime: "$isoTimestamp",
|
|
1384
|
+
$localDatetime: "$isoTimestamp"
|
|
1385
|
+
};
|
|
1386
|
+
const SPECTOR_TO_HTTP = {
|
|
1387
|
+
$uuid: "$guid",
|
|
1388
|
+
$randomInt: "$randomInt",
|
|
1389
|
+
$timestamp: "$timestamp",
|
|
1390
|
+
$isoTimestamp: "$datetime iso8601"
|
|
1391
|
+
};
|
|
1392
|
+
function mapDynamicVars(s) {
|
|
1393
|
+
return s.replace(/\{\{\s*(\$[A-Za-z]+)[^}]*\}\}/g, (_m, name) => `{{${HTTP_TO_SPECTOR[name] ?? name}}}`);
|
|
1394
|
+
}
|
|
1395
|
+
function contentTypeOf(headers) {
|
|
1396
|
+
return headers.find((h) => h.key.toLowerCase() === "content-type")?.value.toLowerCase() ?? "";
|
|
1397
|
+
}
|
|
1398
|
+
function toRequestBody(bodyText, headers) {
|
|
1399
|
+
if (!bodyText.trim()) return { mode: "none" };
|
|
1400
|
+
const ct = contentTypeOf(headers);
|
|
1401
|
+
if (ct.includes("json") || !ct && /^\s*[[{]/.test(bodyText)) {
|
|
1402
|
+
return { mode: "json", json: bodyText };
|
|
1403
|
+
}
|
|
1404
|
+
if (ct.includes("graphql")) {
|
|
1405
|
+
return { mode: "graphql", graphql: { query: bodyText, variables: "{}" } };
|
|
1406
|
+
}
|
|
1407
|
+
if (ct.includes("x-www-form-urlencoded")) {
|
|
1408
|
+
const form = bodyText.split("&").filter(Boolean).map((pair) => {
|
|
1409
|
+
const [k, v = ""] = pair.split("=");
|
|
1410
|
+
return { key: decodeURIComponent(k.trim()), value: decodeURIComponent(v.trim()), enabled: true };
|
|
1411
|
+
});
|
|
1412
|
+
return { mode: "form", form };
|
|
1413
|
+
}
|
|
1414
|
+
return { mode: "raw", raw: bodyText, rawContentType: ct || "text/plain" };
|
|
1415
|
+
}
|
|
1416
|
+
function extractAuth(headers) {
|
|
1417
|
+
const authHeader = headers.find((h) => h.key.toLowerCase() === "authorization");
|
|
1418
|
+
if (authHeader) {
|
|
1419
|
+
const bearer = /^Bearer\s+(.+)$/i.exec(authHeader.value.trim());
|
|
1420
|
+
if (bearer) {
|
|
1421
|
+
return {
|
|
1422
|
+
headers: headers.filter((h) => h !== authHeader),
|
|
1423
|
+
auth: { type: "bearer", token: bearer[1].trim() }
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
return { headers, auth: { type: "none" } };
|
|
1428
|
+
}
|
|
1429
|
+
function stripScriptBlocks(bodyLines2) {
|
|
1430
|
+
const joined = bodyLines2.join("\n");
|
|
1431
|
+
return joined.replace(/[<>]\s*\{%[\s\S]*?%\}/g, "").replace(/^\s*>\s+\S.*$/gm, "").replace(/^\s*<\s+\S.*$/gm, "").trim();
|
|
1432
|
+
}
|
|
1433
|
+
function splitBlocks(text) {
|
|
1434
|
+
const blocks = [];
|
|
1435
|
+
let current = null;
|
|
1436
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
1437
|
+
const sep = /^###\s*(.*)$/.exec(raw);
|
|
1438
|
+
if (sep) {
|
|
1439
|
+
current = { label: sep[1].trim() || void 0, lines: [] };
|
|
1440
|
+
blocks.push(current);
|
|
1441
|
+
} else if (current) {
|
|
1442
|
+
current.lines.push(raw);
|
|
1443
|
+
} else {
|
|
1444
|
+
current = { lines: [raw] };
|
|
1445
|
+
blocks.push(current);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return blocks;
|
|
1449
|
+
}
|
|
1450
|
+
function parseBlock(block) {
|
|
1451
|
+
const { lines } = block;
|
|
1452
|
+
let name = block.label;
|
|
1453
|
+
let i = 0;
|
|
1454
|
+
for (; i < lines.length; i++) {
|
|
1455
|
+
const t = lines[i].trim();
|
|
1456
|
+
if (!t) continue;
|
|
1457
|
+
const named = /^(?:#|\/\/)\s*@name\s*=?\s*(.+)$/.exec(t);
|
|
1458
|
+
if (named) {
|
|
1459
|
+
name = named[1].trim();
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1462
|
+
if (t.startsWith("#") || t.startsWith("//")) continue;
|
|
1463
|
+
if (/^@[A-Za-z0-9_]+\s*=/.test(t)) continue;
|
|
1464
|
+
break;
|
|
1465
|
+
}
|
|
1466
|
+
if (i >= lines.length) return null;
|
|
1467
|
+
const requestLine = lines[i++].trim();
|
|
1468
|
+
const rm = new RegExp(`^(?:(${METHODS.join("|")})\\s+)?(\\S.*?)(?:\\s+HTTP/[\\d.]+)?$`, "i").exec(requestLine);
|
|
1469
|
+
if (!rm) return null;
|
|
1470
|
+
const method = (rm[1] ?? "GET").toUpperCase();
|
|
1471
|
+
let url = rm[2].trim();
|
|
1472
|
+
const rawHeaders = [];
|
|
1473
|
+
for (; i < lines.length; i++) {
|
|
1474
|
+
const line = lines[i];
|
|
1475
|
+
if (!line.trim()) {
|
|
1476
|
+
i++;
|
|
1477
|
+
break;
|
|
1478
|
+
}
|
|
1479
|
+
if (/^\s*[?&]/.test(line)) {
|
|
1480
|
+
url += line.trim();
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
const h = /^([^:\s][^:]*):\s*(.*)$/.exec(line);
|
|
1484
|
+
if (h) rawHeaders.push({ key: h[1].trim(), value: mapDynamicVars(h[2].trim()), enabled: true });
|
|
1485
|
+
}
|
|
1486
|
+
const bodyText = mapDynamicVars(stripScriptBlocks(lines.slice(i)));
|
|
1487
|
+
const { headers, auth } = extractAuth(rawHeaders);
|
|
1488
|
+
const body = toRequestBody(bodyText, headers);
|
|
1489
|
+
return {
|
|
1490
|
+
id: uuid.v4(),
|
|
1491
|
+
name: name ?? `${method} ${url}`,
|
|
1492
|
+
method,
|
|
1493
|
+
url: mapDynamicVars(url),
|
|
1494
|
+
headers,
|
|
1495
|
+
params: [],
|
|
1496
|
+
auth,
|
|
1497
|
+
body,
|
|
1498
|
+
meta: { tags: ["http-file"] }
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
function parseHttpFile(text, name) {
|
|
1502
|
+
const collectionVariables = {};
|
|
1503
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1504
|
+
const m = /^@([A-Za-z0-9_]+)\s*=\s*(.*)$/.exec(line);
|
|
1505
|
+
if (m) collectionVariables[m[1]] = mapDynamicVars(m[2].trim());
|
|
1506
|
+
}
|
|
1507
|
+
const rootFolder = { id: uuid.v4(), name: "root", description: "", folders: [], requestIds: [] };
|
|
1508
|
+
const requests = {};
|
|
1509
|
+
for (const block of splitBlocks(text)) {
|
|
1510
|
+
const req = parseBlock(block);
|
|
1511
|
+
if (!req) continue;
|
|
1512
|
+
requests[req.id] = req;
|
|
1513
|
+
rootFolder.requestIds.push(req.id);
|
|
1514
|
+
}
|
|
1515
|
+
return {
|
|
1516
|
+
version: "1.0",
|
|
1517
|
+
id: uuid.v4(),
|
|
1518
|
+
name,
|
|
1519
|
+
description: "",
|
|
1520
|
+
rootFolder,
|
|
1521
|
+
requests,
|
|
1522
|
+
...Object.keys(collectionVariables).length ? { collectionVariables } : {}
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
async function importHttpFile(filePath) {
|
|
1526
|
+
const raw = await promises.readFile(filePath, "utf8");
|
|
1527
|
+
const name = path.basename(filePath).replace(/\.(http|rest)$/i, "") || "Imported HTTP";
|
|
1528
|
+
return parseHttpFile(raw, name);
|
|
1529
|
+
}
|
|
1128
1530
|
function registerImportHandlers(ipc) {
|
|
1129
|
-
ipc.
|
|
1531
|
+
handle.handleIpc(ipc, handle.IPC.import.postman, async () => {
|
|
1130
1532
|
const result = await electron.dialog.showOpenDialog({
|
|
1131
1533
|
title: "Import Postman Collection",
|
|
1132
1534
|
filters: [{ name: "JSON", extensions: ["json"] }],
|
|
@@ -1135,7 +1537,7 @@ function registerImportHandlers(ipc) {
|
|
|
1135
1537
|
if (result.canceled || !result.filePaths[0]) return null;
|
|
1136
1538
|
return importPostman(result.filePaths[0]);
|
|
1137
1539
|
});
|
|
1138
|
-
ipc.
|
|
1540
|
+
handle.handleIpc(ipc, handle.IPC.import.openapi, async () => {
|
|
1139
1541
|
const result = await electron.dialog.showOpenDialog({
|
|
1140
1542
|
title: "Import OpenAPI Definition",
|
|
1141
1543
|
filters: [{ name: "OpenAPI", extensions: ["json", "yaml", "yml"] }],
|
|
@@ -1144,10 +1546,10 @@ function registerImportHandlers(ipc) {
|
|
|
1144
1546
|
if (result.canceled || !result.filePaths[0]) return null;
|
|
1145
1547
|
return importOpenApi(result.filePaths[0]);
|
|
1146
1548
|
});
|
|
1147
|
-
ipc.
|
|
1549
|
+
handle.handleIpc(ipc, handle.IPC.import.openapiUrl, async (_event, url) => {
|
|
1148
1550
|
return importOpenApiFromUrl(url);
|
|
1149
1551
|
});
|
|
1150
|
-
ipc.
|
|
1552
|
+
handle.handleIpc(ipc, handle.IPC.import.insomnia, async () => {
|
|
1151
1553
|
const result = await electron.dialog.showOpenDialog({
|
|
1152
1554
|
title: "Import Insomnia Collection",
|
|
1153
1555
|
filters: [{ name: "JSON", extensions: ["json"] }],
|
|
@@ -1156,7 +1558,7 @@ function registerImportHandlers(ipc) {
|
|
|
1156
1558
|
if (result.canceled || !result.filePaths[0]) return null;
|
|
1157
1559
|
return importInsomnia(result.filePaths[0]);
|
|
1158
1560
|
});
|
|
1159
|
-
ipc.
|
|
1561
|
+
handle.handleIpc(ipc, handle.IPC.import.bruno, async () => {
|
|
1160
1562
|
const result = await electron.dialog.showOpenDialog({
|
|
1161
1563
|
title: "Import Bruno Collection",
|
|
1162
1564
|
filters: [{ name: "Bruno Collection", extensions: ["json"] }],
|
|
@@ -1165,7 +1567,16 @@ function registerImportHandlers(ipc) {
|
|
|
1165
1567
|
if (result.canceled || !result.filePaths[0]) return null;
|
|
1166
1568
|
return importBruno(result.filePaths[0]);
|
|
1167
1569
|
});
|
|
1168
|
-
ipc.
|
|
1570
|
+
handle.handleIpc(ipc, handle.IPC.import.http, async () => {
|
|
1571
|
+
const result = await electron.dialog.showOpenDialog({
|
|
1572
|
+
title: "Import .http / .rest file",
|
|
1573
|
+
filters: [{ name: "HTTP file", extensions: ["http", "rest"] }],
|
|
1574
|
+
properties: ["openFile"]
|
|
1575
|
+
});
|
|
1576
|
+
if (result.canceled || !result.filePaths[0]) return null;
|
|
1577
|
+
return importHttpFile(result.filePaths[0]);
|
|
1578
|
+
});
|
|
1579
|
+
handle.handleIpc(ipc, handle.IPC.import.openapiSchemas, async () => {
|
|
1169
1580
|
const result = await electron.dialog.showOpenDialog({
|
|
1170
1581
|
title: "Load OpenAPI spec for schema sync",
|
|
1171
1582
|
filters: [{ name: "OpenAPI", extensions: ["json", "yaml", "yml"] }],
|
|
@@ -1174,7 +1585,7 @@ function registerImportHandlers(ipc) {
|
|
|
1174
1585
|
if (result.canceled || !result.filePaths[0]) return null;
|
|
1175
1586
|
return extractSchemasFromFile(result.filePaths[0]);
|
|
1176
1587
|
});
|
|
1177
|
-
ipc.
|
|
1588
|
+
handle.handleIpc(ipc, handle.IPC.import.openapiSchemasUrl, async (_event, url) => {
|
|
1178
1589
|
return extractSchemasFromUrl(url);
|
|
1179
1590
|
});
|
|
1180
1591
|
}
|
|
@@ -1232,6 +1643,97 @@ function parsePostScript(script) {
|
|
|
1232
1643
|
function accessorToJsonPath(accessor) {
|
|
1233
1644
|
return accessor.replace(/^json\.?/, "").replace(/\["([^"]+)"\]/g, "['$1']");
|
|
1234
1645
|
}
|
|
1646
|
+
function slug(name) {
|
|
1647
|
+
return name.replace(/\W+/g, "-").toLowerCase().replace(/^-|-$/g, "");
|
|
1648
|
+
}
|
|
1649
|
+
function toEnvVar(key) {
|
|
1650
|
+
return key.replace(/\W+/g, "_").toUpperCase();
|
|
1651
|
+
}
|
|
1652
|
+
function interpolateEnvVars(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
1653
|
+
return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
1654
|
+
const envKey = toEnvVar(key.trim());
|
|
1655
|
+
return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
function renderJsValue(value, indent, sharedVars = /* @__PURE__ */ new Set()) {
|
|
1659
|
+
const next = indent + " ";
|
|
1660
|
+
if (value === null) return "null";
|
|
1661
|
+
if (typeof value === "boolean" || typeof value === "number") return String(value);
|
|
1662
|
+
if (typeof value === "string") {
|
|
1663
|
+
if (value.includes("{{")) {
|
|
1664
|
+
return "`" + interpolateEnvVars(value, sharedVars) + "`";
|
|
1665
|
+
}
|
|
1666
|
+
return JSON.stringify(value);
|
|
1667
|
+
}
|
|
1668
|
+
if (Array.isArray(value)) {
|
|
1669
|
+
if (!value.length) return "[]";
|
|
1670
|
+
return `[
|
|
1671
|
+
${value.map((v) => next + renderJsValue(v, next, sharedVars)).join(",\n")},
|
|
1672
|
+
${indent}]`;
|
|
1673
|
+
}
|
|
1674
|
+
if (typeof value === "object") {
|
|
1675
|
+
const entries = Object.entries(value);
|
|
1676
|
+
if (!entries.length) return "{}";
|
|
1677
|
+
return `{
|
|
1678
|
+
${entries.map(([k, v]) => `${next}${k}: ${renderJsValue(v, next, sharedVars)}`).join(",\n")},
|
|
1679
|
+
${indent}}`;
|
|
1680
|
+
}
|
|
1681
|
+
return JSON.stringify(value);
|
|
1682
|
+
}
|
|
1683
|
+
function buildNameMap$1(folder, requests) {
|
|
1684
|
+
const map = /* @__PURE__ */ new Map();
|
|
1685
|
+
const used = /* @__PURE__ */ new Set();
|
|
1686
|
+
for (const id of folder.requestIds) {
|
|
1687
|
+
const req = requests[id];
|
|
1688
|
+
if (!req) continue;
|
|
1689
|
+
const base = req.name;
|
|
1690
|
+
let name = base;
|
|
1691
|
+
if (used.has(name)) {
|
|
1692
|
+
let i = 2;
|
|
1693
|
+
while (used.has(`${base} ${i}`)) i++;
|
|
1694
|
+
name = `${base} ${i}`;
|
|
1695
|
+
}
|
|
1696
|
+
used.add(name);
|
|
1697
|
+
map.set(id, name);
|
|
1698
|
+
}
|
|
1699
|
+
return map;
|
|
1700
|
+
}
|
|
1701
|
+
function javaClass(name) {
|
|
1702
|
+
return name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
1703
|
+
}
|
|
1704
|
+
function resolveEffectiveAuth(req, inherited) {
|
|
1705
|
+
return req.auth.type !== "none" ? req.auth : inherited.auth ?? req.auth;
|
|
1706
|
+
}
|
|
1707
|
+
function mergeHeaders(req, inherited) {
|
|
1708
|
+
return [...inherited.headers.filter((h) => h.enabled && h.key), ...req.headers.filter((h) => h.enabled && h.key)];
|
|
1709
|
+
}
|
|
1710
|
+
function hasBody(req) {
|
|
1711
|
+
return req.body.mode !== "none" && !["get", "head"].includes(req.method.toLowerCase());
|
|
1712
|
+
}
|
|
1713
|
+
function getEnvBaseUrl(environment, fallback) {
|
|
1714
|
+
return environment?.variables.find(
|
|
1715
|
+
(v) => ["base_url", "baseurl", "base-url"].includes(v.key.toLowerCase()) && !v.secret
|
|
1716
|
+
)?.value ?? fallback;
|
|
1717
|
+
}
|
|
1718
|
+
function renderTree(paths) {
|
|
1719
|
+
const root = {};
|
|
1720
|
+
for (const p of [...paths].sort()) {
|
|
1721
|
+
let cur = root;
|
|
1722
|
+
for (const part of p.split("/")) {
|
|
1723
|
+
cur = cur[part] ??= {};
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
function render(node, prefix = "") {
|
|
1727
|
+
const entries = Object.entries(node);
|
|
1728
|
+
return entries.flatMap(([name, children], i) => {
|
|
1729
|
+
const last = i === entries.length - 1;
|
|
1730
|
+
const lines = [`${prefix}${last ? "└── " : "├── "}${name}`];
|
|
1731
|
+
if (Object.keys(children).length) lines.push(...render(children, prefix + (last ? " " : "│ ")));
|
|
1732
|
+
return lines;
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
return [".", ...render(root)].join("\n");
|
|
1736
|
+
}
|
|
1235
1737
|
function safeName(name) {
|
|
1236
1738
|
return name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1237
1739
|
}
|
|
@@ -1247,7 +1749,7 @@ function interpolate(value, vars) {
|
|
|
1247
1749
|
return v?.secret ? envVar(key.trim()) : robotVar(key.trim());
|
|
1248
1750
|
});
|
|
1249
1751
|
}
|
|
1250
|
-
function buildNameMap
|
|
1752
|
+
function buildNameMap(root, requests) {
|
|
1251
1753
|
const map = /* @__PURE__ */ new Map();
|
|
1252
1754
|
const used = /* @__PURE__ */ new Set();
|
|
1253
1755
|
function visit(folder) {
|
|
@@ -1317,8 +1819,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1317
1819
|
lines.push(kwName);
|
|
1318
1820
|
lines.push(` [Documentation] Hook: ${req.hookType} — ${req.name}`);
|
|
1319
1821
|
const { body } = req;
|
|
1320
|
-
|
|
1321
|
-
if (hasBody && body.mode === "json" && body.json) {
|
|
1822
|
+
if (hasBody(req) && body.mode === "json" && body.json) {
|
|
1322
1823
|
const bodyPairs = jsonToRfDictPairs(body.json, varMap);
|
|
1323
1824
|
if (bodyPairs !== null) {
|
|
1324
1825
|
lines.push(` VAR &{body} ${bodyPairs}`);
|
|
@@ -1327,7 +1828,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1327
1828
|
}
|
|
1328
1829
|
}
|
|
1329
1830
|
const callArgs = [];
|
|
1330
|
-
if (hasBody && body.mode === "json") callArgs.push("json=${body}");
|
|
1831
|
+
if (hasBody(req) && body.mode === "json") callArgs.push("json=${body}");
|
|
1331
1832
|
lines.push(` \${response}= ${method} ${url}`);
|
|
1332
1833
|
if (callArgs.length) lines.push(` ... ${callArgs.join(" ")}`);
|
|
1333
1834
|
const parsed = parsePostScript(req.postRequestScript);
|
|
@@ -1353,8 +1854,8 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1353
1854
|
lines.push(kwName);
|
|
1354
1855
|
lines.push(` [Documentation] ${req.description || req.name}`);
|
|
1355
1856
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
|
|
1356
|
-
const effectiveAuth = req
|
|
1357
|
-
const allHeaders =
|
|
1857
|
+
const effectiveAuth = resolveEffectiveAuth(req, inherited);
|
|
1858
|
+
const allHeaders = mergeHeaders(req, inherited);
|
|
1358
1859
|
const headerPairs = [];
|
|
1359
1860
|
if (effectiveAuth.type === "bearer") {
|
|
1360
1861
|
const token = effectiveAuth.token ?? "";
|
|
@@ -1392,8 +1893,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1392
1893
|
lines.push(` VAR &{params} ${pairs}`);
|
|
1393
1894
|
}
|
|
1394
1895
|
const { body } = req;
|
|
1395
|
-
|
|
1396
|
-
if (hasBody && body.mode === "json" && body.json) {
|
|
1896
|
+
if (hasBody(req) && body.mode === "json" && body.json) {
|
|
1397
1897
|
const bodyPairs = jsonToRfDictPairs(body.json, varMap);
|
|
1398
1898
|
if (bodyPairs !== null) {
|
|
1399
1899
|
lines.push(` VAR &{body} ${bodyPairs}`);
|
|
@@ -1405,7 +1905,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
|
|
|
1405
1905
|
const callArgs = [];
|
|
1406
1906
|
if (headerPairs.length) callArgs.push("headers=${headers}");
|
|
1407
1907
|
if (enabledParams.length) callArgs.push("params=${params}");
|
|
1408
|
-
if (hasBody && body.mode === "json") callArgs.push("json=${body}");
|
|
1908
|
+
if (hasBody(req) && body.mode === "json") callArgs.push("json=${body}");
|
|
1409
1909
|
lines.push(` \${response}= ${method} ${url}`);
|
|
1410
1910
|
if (callArgs.length) {
|
|
1411
1911
|
lines.push(` ... ${callArgs.join(" ")}`);
|
|
@@ -1494,27 +1994,8 @@ function buildTestSuite(collection, environment, nameMap) {
|
|
|
1494
1994
|
processFolder(collection.rootFolder);
|
|
1495
1995
|
return lines.join("\n");
|
|
1496
1996
|
}
|
|
1497
|
-
function renderTree$6(paths) {
|
|
1498
|
-
const root = {};
|
|
1499
|
-
for (const p of [...paths].sort()) {
|
|
1500
|
-
let cur = root;
|
|
1501
|
-
for (const part of p.split("/")) {
|
|
1502
|
-
cur = cur[part] ??= {};
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
function render(node, prefix = "") {
|
|
1506
|
-
const entries = Object.entries(node);
|
|
1507
|
-
return entries.flatMap(([name, children], i) => {
|
|
1508
|
-
const last = i === entries.length - 1;
|
|
1509
|
-
const lines = [`${prefix}${last ? "└── " : "├── "}${name}`];
|
|
1510
|
-
if (Object.keys(children).length) lines.push(...render(children, prefix + (last ? " " : "│ ")));
|
|
1511
|
-
return lines;
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
return [".", ...render(root)].join("\n");
|
|
1515
|
-
}
|
|
1516
1997
|
function buildReadme$6(collectionName, filePaths) {
|
|
1517
|
-
const tree = renderTree
|
|
1998
|
+
const tree = renderTree(filePaths);
|
|
1518
1999
|
return `# ${collectionName} — API Tests (Robot Framework)
|
|
1519
2000
|
|
|
1520
2001
|
## Project structure
|
|
@@ -1543,7 +2024,7 @@ function generateRobotFramework(collection, environment) {
|
|
|
1543
2024
|
const varMap = new Map(
|
|
1544
2025
|
(environment?.variables ?? []).map((v) => [v.key, v])
|
|
1545
2026
|
);
|
|
1546
|
-
const nameMap = buildNameMap
|
|
2027
|
+
const nameMap = buildNameMap(collection.rootFolder, collection.requests);
|
|
1547
2028
|
const slug2 = collection.name.replace(/\W+/g, "_").toLowerCase();
|
|
1548
2029
|
const hookExtractedVars = /* @__PURE__ */ new Set();
|
|
1549
2030
|
const contentFiles = [
|
|
@@ -1558,65 +2039,8 @@ function generateRobotFramework(collection, environment) {
|
|
|
1558
2039
|
...contentFiles
|
|
1559
2040
|
];
|
|
1560
2041
|
}
|
|
1561
|
-
function slug$3(name) {
|
|
1562
|
-
return name.replace(/\W+/g, "-").toLowerCase().replace(/^-|-$/g, "");
|
|
1563
|
-
}
|
|
1564
|
-
function toEnvVar$3(key) {
|
|
1565
|
-
return key.replace(/\W+/g, "_").toUpperCase();
|
|
1566
|
-
}
|
|
1567
|
-
function interpolatePath$1(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
1568
|
-
return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
1569
|
-
const envKey = toEnvVar$3(key.trim());
|
|
1570
|
-
return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
|
|
1571
|
-
});
|
|
1572
|
-
}
|
|
1573
|
-
function buildNameMap$1(folder, requests) {
|
|
1574
|
-
const map = /* @__PURE__ */ new Map();
|
|
1575
|
-
const used = /* @__PURE__ */ new Set();
|
|
1576
|
-
for (const id of folder.requestIds) {
|
|
1577
|
-
const req = requests[id];
|
|
1578
|
-
if (!req) continue;
|
|
1579
|
-
const base = req.name;
|
|
1580
|
-
let name = base;
|
|
1581
|
-
if (used.has(name)) {
|
|
1582
|
-
let i = 2;
|
|
1583
|
-
while (used.has(`${base} ${i}`)) i++;
|
|
1584
|
-
name = `${base} ${i}`;
|
|
1585
|
-
}
|
|
1586
|
-
used.add(name);
|
|
1587
|
-
map.set(id, name);
|
|
1588
|
-
}
|
|
1589
|
-
return map;
|
|
1590
|
-
}
|
|
1591
|
-
function renderJsValue$1(value, indent, sharedVars = /* @__PURE__ */ new Set()) {
|
|
1592
|
-
const next = indent + " ";
|
|
1593
|
-
if (value === null) return "null";
|
|
1594
|
-
if (typeof value === "boolean" || typeof value === "number") return String(value);
|
|
1595
|
-
if (typeof value === "string") {
|
|
1596
|
-
if (value.includes("{{")) {
|
|
1597
|
-
return "`" + interpolatePath$1(value, sharedVars) + "`";
|
|
1598
|
-
}
|
|
1599
|
-
return JSON.stringify(value);
|
|
1600
|
-
}
|
|
1601
|
-
if (Array.isArray(value)) {
|
|
1602
|
-
if (!value.length) return "[]";
|
|
1603
|
-
return `[
|
|
1604
|
-
${value.map((v) => next + renderJsValue$1(v, next, sharedVars)).join(",\n")},
|
|
1605
|
-
${indent}]`;
|
|
1606
|
-
}
|
|
1607
|
-
if (typeof value === "object") {
|
|
1608
|
-
const entries = Object.entries(value);
|
|
1609
|
-
if (!entries.length) return "{}";
|
|
1610
|
-
return `{
|
|
1611
|
-
${entries.map(([k, v]) => `${next}${k}: ${renderJsValue$1(v, next, sharedVars)}`).join(",\n")},
|
|
1612
|
-
${indent}}`;
|
|
1613
|
-
}
|
|
1614
|
-
return JSON.stringify(value);
|
|
1615
|
-
}
|
|
1616
2042
|
function buildPlaywrightConfig$1(environment) {
|
|
1617
|
-
const baseUrl = environment
|
|
1618
|
-
(v) => ["base_url", "baseurl", "base-url"].includes(v.key.toLowerCase()) && !v.secret
|
|
1619
|
-
)?.value ?? "http://localhost:3000";
|
|
2043
|
+
const baseUrl = getEnvBaseUrl(environment, "http://localhost:3000");
|
|
1620
2044
|
return `import { defineConfig } from '@playwright/test'
|
|
1621
2045
|
import * as dotenv from 'dotenv'
|
|
1622
2046
|
|
|
@@ -1636,19 +2060,19 @@ export default defineConfig({
|
|
|
1636
2060
|
function buildHookLines$1(req, sharedVars) {
|
|
1637
2061
|
const method = req.method.toLowerCase();
|
|
1638
2062
|
const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
|
|
1639
|
-
const pathExpr = path2.includes("{{") ? "`" +
|
|
2063
|
+
const pathExpr = path2.includes("{{") ? "`" + interpolateEnvVars(path2) + "`" : `'${path2}'`;
|
|
1640
2064
|
const headerEntries = [];
|
|
1641
2065
|
if (req.auth.type === "bearer") {
|
|
1642
2066
|
const token = req.auth.token ?? "";
|
|
1643
2067
|
if (token.includes("{{")) {
|
|
1644
|
-
headerEntries.push(`Authorization: \`Bearer ${
|
|
2068
|
+
headerEntries.push(`Authorization: \`Bearer ${interpolateEnvVars(token)}\``);
|
|
1645
2069
|
} else {
|
|
1646
2070
|
const ref = req.auth.tokenSecretRef ?? "API_TOKEN";
|
|
1647
|
-
headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar
|
|
2071
|
+
headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar(ref)} ?? ''}\``);
|
|
1648
2072
|
}
|
|
1649
2073
|
}
|
|
1650
2074
|
for (const h of req.headers.filter((h2) => h2.enabled && h2.key)) {
|
|
1651
|
-
headerEntries.push(`'${h.key}': \`${
|
|
2075
|
+
headerEntries.push(`'${h.key}': \`${interpolateEnvVars(h.value)}\``);
|
|
1652
2076
|
}
|
|
1653
2077
|
const optionParts = [];
|
|
1654
2078
|
if (headerEntries.length) {
|
|
@@ -1656,7 +2080,7 @@ function buildHookLines$1(req, sharedVars) {
|
|
|
1656
2080
|
}
|
|
1657
2081
|
if (req.body.mode === "json" && req.body.json && !["get", "head"].includes(method)) {
|
|
1658
2082
|
try {
|
|
1659
|
-
optionParts.push(`data: ${renderJsValue
|
|
2083
|
+
optionParts.push(`data: ${renderJsValue(JSON.parse(req.body.json), " ")}`);
|
|
1660
2084
|
} catch {
|
|
1661
2085
|
}
|
|
1662
2086
|
}
|
|
@@ -1670,7 +2094,7 @@ function buildHookLines$1(req, sharedVars) {
|
|
|
1670
2094
|
for (const e of parsed.extractions) {
|
|
1671
2095
|
const jsonPath = e.accessor.replace(/^json\.?/, "");
|
|
1672
2096
|
const expr = jsonPath ? `hookJson.${jsonPath}` : "hookJson";
|
|
1673
|
-
const varName = toEnvVar
|
|
2097
|
+
const varName = toEnvVar(e.varName);
|
|
1674
2098
|
sharedVars.add(varName);
|
|
1675
2099
|
lines.push(` ${varName} = String(${expr});`);
|
|
1676
2100
|
}
|
|
@@ -1728,32 +2152,32 @@ ${lines.join("\n")}
|
|
|
1728
2152
|
const testName = nameMap.get(reqId) ?? req.name;
|
|
1729
2153
|
const method = req.method.toLowerCase();
|
|
1730
2154
|
const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
|
|
1731
|
-
const pathExpr = path2.includes("{{") ? "`" +
|
|
2155
|
+
const pathExpr = path2.includes("{{") ? "`" + interpolateEnvVars(path2, sharedVars) + "`" : `'${path2}'`;
|
|
1732
2156
|
const optionParts = [];
|
|
1733
2157
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
|
|
1734
|
-
const effectiveAuth = req
|
|
1735
|
-
const allHeaders =
|
|
2158
|
+
const effectiveAuth = resolveEffectiveAuth(req, inherited);
|
|
2159
|
+
const allHeaders = mergeHeaders(req, inherited);
|
|
1736
2160
|
const headerEntries = [];
|
|
1737
2161
|
if (effectiveAuth.type === "bearer") {
|
|
1738
2162
|
const token = effectiveAuth.token ?? "";
|
|
1739
2163
|
if (token.includes("{{")) {
|
|
1740
|
-
headerEntries.push(`Authorization: \`Bearer ${
|
|
2164
|
+
headerEntries.push(`Authorization: \`Bearer ${interpolateEnvVars(token, sharedVars)}\``);
|
|
1741
2165
|
} else {
|
|
1742
2166
|
const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
|
|
1743
|
-
headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar
|
|
2167
|
+
headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar(ref)} ?? ''}\``);
|
|
1744
2168
|
}
|
|
1745
2169
|
} else if (effectiveAuth.type === "apikey" && effectiveAuth.apiKeyIn === "header") {
|
|
1746
2170
|
const val = effectiveAuth.apiKeyValue ?? "";
|
|
1747
2171
|
const name = effectiveAuth.apiKeyName ?? "X-API-Key";
|
|
1748
2172
|
if (val.includes("{{")) {
|
|
1749
|
-
headerEntries.push(`'${name}': \`${
|
|
2173
|
+
headerEntries.push(`'${name}': \`${interpolateEnvVars(val, sharedVars)}\``);
|
|
1750
2174
|
} else {
|
|
1751
2175
|
const ref = effectiveAuth.apiKeySecretRef ?? "API_KEY";
|
|
1752
|
-
headerEntries.push(`'${name}': \`\${process.env.${toEnvVar
|
|
2176
|
+
headerEntries.push(`'${name}': \`\${process.env.${toEnvVar(ref)} ?? ''}\``);
|
|
1753
2177
|
}
|
|
1754
2178
|
}
|
|
1755
2179
|
for (const h of allHeaders) {
|
|
1756
|
-
headerEntries.push(`'${h.key}': \`${
|
|
2180
|
+
headerEntries.push(`'${h.key}': \`${interpolateEnvVars(h.value, sharedVars)}\``);
|
|
1757
2181
|
}
|
|
1758
2182
|
if (headerEntries.length) {
|
|
1759
2183
|
optionParts.push(` headers: {
|
|
@@ -1763,17 +2187,16 @@ ${lines.join("\n")}
|
|
|
1763
2187
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
1764
2188
|
if (enabledParams.length) {
|
|
1765
2189
|
const pairs = enabledParams.map(
|
|
1766
|
-
(p) => p.value.includes("{{") ? `'${p.key}': \`${
|
|
2190
|
+
(p) => p.value.includes("{{") ? `'${p.key}': \`${interpolateEnvVars(p.value, sharedVars)}\`` : `'${p.key}': '${p.value}'`
|
|
1767
2191
|
).join(", ");
|
|
1768
2192
|
optionParts.push(` params: { ${pairs} }`);
|
|
1769
2193
|
}
|
|
1770
|
-
|
|
1771
|
-
if (hasBody && req.body.mode === "json" && req.body.json) {
|
|
2194
|
+
if (hasBody(req) && req.body.mode === "json" && req.body.json) {
|
|
1772
2195
|
try {
|
|
1773
|
-
const rendered = renderJsValue
|
|
2196
|
+
const rendered = renderJsValue(JSON.parse(req.body.json), " ", sharedVars);
|
|
1774
2197
|
optionParts.push(` data: ${rendered}`);
|
|
1775
2198
|
} catch {
|
|
1776
|
-
optionParts.push(` data: \`${
|
|
2199
|
+
optionParts.push(` data: \`${interpolateEnvVars(req.body.json, sharedVars)}\``);
|
|
1777
2200
|
}
|
|
1778
2201
|
}
|
|
1779
2202
|
const optionsStr = optionParts.length ? `, {
|
|
@@ -1828,7 +2251,7 @@ ${optionParts.join(",\n")},
|
|
|
1828
2251
|
const path22 = e.accessor.replace(/^json\.?/, "");
|
|
1829
2252
|
const expr = path22 ? `json.${path22}` : "json";
|
|
1830
2253
|
lines.push(` // Extract: ${e.varName} = ${expr}`);
|
|
1831
|
-
lines.push(` process.env.${toEnvVar
|
|
2254
|
+
lines.push(` process.env.${toEnvVar(e.varName)} = String(${expr});`);
|
|
1832
2255
|
}
|
|
1833
2256
|
lines.push(` });`);
|
|
1834
2257
|
tests.push(lines.join("\n"));
|
|
@@ -1859,27 +2282,8 @@ function buildPackageJson$3(collectionName) {
|
|
|
1859
2282
|
}
|
|
1860
2283
|
}, null, 2) + "\n";
|
|
1861
2284
|
}
|
|
1862
|
-
function renderTree$5(paths) {
|
|
1863
|
-
const root = {};
|
|
1864
|
-
for (const p of [...paths].sort()) {
|
|
1865
|
-
let cur = root;
|
|
1866
|
-
for (const part of p.split("/")) {
|
|
1867
|
-
cur = cur[part] ??= {};
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
function render(node, prefix = "") {
|
|
1871
|
-
const entries = Object.entries(node);
|
|
1872
|
-
return entries.flatMap(([name, children], i) => {
|
|
1873
|
-
const last = i === entries.length - 1;
|
|
1874
|
-
const lines = [`${prefix}${last ? "└── " : "├── "}${name}`];
|
|
1875
|
-
if (Object.keys(children).length) lines.push(...render(children, prefix + (last ? " " : "│ ")));
|
|
1876
|
-
return lines;
|
|
1877
|
-
});
|
|
1878
|
-
}
|
|
1879
|
-
return [".", ...render(root)].join("\n");
|
|
1880
|
-
}
|
|
1881
2285
|
function buildReadme$5(collectionName, filePaths) {
|
|
1882
|
-
const tree = renderTree
|
|
2286
|
+
const tree = renderTree([...filePaths, ".env.local"]);
|
|
1883
2287
|
return `# ${collectionName} — API Tests (Playwright TypeScript)
|
|
1884
2288
|
|
|
1885
2289
|
## Project structure
|
|
@@ -1900,83 +2304,26 @@ npm test
|
|
|
1900
2304
|
`;
|
|
1901
2305
|
}
|
|
1902
2306
|
function generatePlaywright(collection, environment) {
|
|
1903
|
-
const files = [];
|
|
1904
|
-
function processFolder(folder, name) {
|
|
1905
|
-
if (folder.requestIds.length > 0) {
|
|
1906
|
-
const nameMap = buildNameMap$1(folder, collection.requests);
|
|
1907
|
-
files.push({ path: `tests/${slug
|
|
1908
|
-
}
|
|
1909
|
-
for (const sub of folder.folders) processFolder(sub, sub.name);
|
|
1910
|
-
}
|
|
1911
|
-
if (collection.rootFolder.requestIds.length > 0) processFolder(collection.rootFolder, collection.name);
|
|
1912
|
-
for (const sub of collection.rootFolder.folders) processFolder(sub, sub.name);
|
|
1913
|
-
const scaffoldPaths = ["package.json", "playwright.config.ts", ...files.map((f) => f.path)];
|
|
1914
|
-
files.unshift(
|
|
1915
|
-
{ path: "package.json", content: buildPackageJson$3(collection.name) },
|
|
1916
|
-
{ path: "playwright.config.ts", content: buildPlaywrightConfig$1(environment) },
|
|
1917
|
-
{ path: "README.md", content: buildReadme$5(collection.name, scaffoldPaths) }
|
|
1918
|
-
);
|
|
1919
|
-
return files;
|
|
1920
|
-
}
|
|
1921
|
-
function slug$2(name) {
|
|
1922
|
-
return name.replace(/\W+/g, "-").toLowerCase().replace(/^-|-$/g, "");
|
|
1923
|
-
}
|
|
1924
|
-
function toEnvVar$2(key) {
|
|
1925
|
-
return key.replace(/\W+/g, "_").toUpperCase();
|
|
1926
|
-
}
|
|
1927
|
-
function interpolatePath(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
1928
|
-
return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
1929
|
-
const envKey = toEnvVar$2(key.trim());
|
|
1930
|
-
return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
|
|
1931
|
-
});
|
|
1932
|
-
}
|
|
1933
|
-
function buildNameMap(folder, requests) {
|
|
1934
|
-
const map = /* @__PURE__ */ new Map();
|
|
1935
|
-
const used = /* @__PURE__ */ new Set();
|
|
1936
|
-
for (const id of folder.requestIds) {
|
|
1937
|
-
const req = requests[id];
|
|
1938
|
-
if (!req) continue;
|
|
1939
|
-
const base = req.name;
|
|
1940
|
-
let name = base;
|
|
1941
|
-
if (used.has(name)) {
|
|
1942
|
-
let i = 2;
|
|
1943
|
-
while (used.has(`${base} ${i}`)) i++;
|
|
1944
|
-
name = `${base} ${i}`;
|
|
1945
|
-
}
|
|
1946
|
-
used.add(name);
|
|
1947
|
-
map.set(id, name);
|
|
1948
|
-
}
|
|
1949
|
-
return map;
|
|
1950
|
-
}
|
|
1951
|
-
function renderJsValue(value, indent, sharedVars = /* @__PURE__ */ new Set()) {
|
|
1952
|
-
const next = indent + " ";
|
|
1953
|
-
if (value === null) return "null";
|
|
1954
|
-
if (typeof value === "boolean" || typeof value === "number") return String(value);
|
|
1955
|
-
if (typeof value === "string") {
|
|
1956
|
-
if (value.includes("{{")) {
|
|
1957
|
-
return "`" + interpolatePath(value, sharedVars) + "`";
|
|
2307
|
+
const files = [];
|
|
2308
|
+
function processFolder(folder, name) {
|
|
2309
|
+
if (folder.requestIds.length > 0) {
|
|
2310
|
+
const nameMap = buildNameMap$1(folder, collection.requests);
|
|
2311
|
+
files.push({ path: `tests/${slug(name)}.spec.ts`, content: buildSpec$1(name, folder, collection, nameMap) });
|
|
1958
2312
|
}
|
|
1959
|
-
|
|
1960
|
-
}
|
|
1961
|
-
if (Array.isArray(value)) {
|
|
1962
|
-
if (!value.length) return "[]";
|
|
1963
|
-
return `[
|
|
1964
|
-
${value.map((v) => next + renderJsValue(v, next, sharedVars)).join(",\n")},
|
|
1965
|
-
${indent}]`;
|
|
1966
|
-
}
|
|
1967
|
-
if (typeof value === "object") {
|
|
1968
|
-
const entries = Object.entries(value);
|
|
1969
|
-
if (!entries.length) return "{}";
|
|
1970
|
-
return `{
|
|
1971
|
-
${entries.map(([k, v]) => `${next}${k}: ${renderJsValue(v, next, sharedVars)}`).join(",\n")},
|
|
1972
|
-
${indent}}`;
|
|
2313
|
+
for (const sub of folder.folders) processFolder(sub, sub.name);
|
|
1973
2314
|
}
|
|
1974
|
-
|
|
2315
|
+
if (collection.rootFolder.requestIds.length > 0) processFolder(collection.rootFolder, collection.name);
|
|
2316
|
+
for (const sub of collection.rootFolder.folders) processFolder(sub, sub.name);
|
|
2317
|
+
const scaffoldPaths = ["package.json", "playwright.config.ts", ...files.map((f) => f.path)];
|
|
2318
|
+
files.unshift(
|
|
2319
|
+
{ path: "package.json", content: buildPackageJson$3(collection.name) },
|
|
2320
|
+
{ path: "playwright.config.ts", content: buildPlaywrightConfig$1(environment) },
|
|
2321
|
+
{ path: "README.md", content: buildReadme$5(collection.name, scaffoldPaths) }
|
|
2322
|
+
);
|
|
2323
|
+
return files;
|
|
1975
2324
|
}
|
|
1976
2325
|
function buildPlaywrightConfig(environment) {
|
|
1977
|
-
const baseUrl = environment
|
|
1978
|
-
(v) => ["base_url", "baseurl", "base-url"].includes(v.key.toLowerCase()) && !v.secret
|
|
1979
|
-
)?.value ?? "http://localhost:3000";
|
|
2326
|
+
const baseUrl = getEnvBaseUrl(environment, "http://localhost:3000");
|
|
1980
2327
|
return `// @ts-check
|
|
1981
2328
|
const { defineConfig } = require('@playwright/test');
|
|
1982
2329
|
require('dotenv').config({ path: '.env.local' });
|
|
@@ -1995,19 +2342,19 @@ module.exports = defineConfig({
|
|
|
1995
2342
|
function buildHookLines(req, sharedVars) {
|
|
1996
2343
|
const method = req.method.toLowerCase();
|
|
1997
2344
|
const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
|
|
1998
|
-
const pathExpr = path2.includes("{{") ? "`" +
|
|
2345
|
+
const pathExpr = path2.includes("{{") ? "`" + interpolateEnvVars(path2) + "`" : `'${path2}'`;
|
|
1999
2346
|
const headerEntries = [];
|
|
2000
2347
|
if (req.auth.type === "bearer") {
|
|
2001
2348
|
const token = req.auth.token ?? "";
|
|
2002
2349
|
if (token.includes("{{")) {
|
|
2003
|
-
headerEntries.push(`Authorization: \`Bearer ${
|
|
2350
|
+
headerEntries.push(`Authorization: \`Bearer ${interpolateEnvVars(token)}\``);
|
|
2004
2351
|
} else {
|
|
2005
2352
|
const ref = req.auth.tokenSecretRef ?? "API_TOKEN";
|
|
2006
|
-
headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar
|
|
2353
|
+
headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar(ref)} ?? ''}\``);
|
|
2007
2354
|
}
|
|
2008
2355
|
}
|
|
2009
2356
|
for (const h of req.headers.filter((h2) => h2.enabled && h2.key)) {
|
|
2010
|
-
headerEntries.push(`'${h.key}': \`${
|
|
2357
|
+
headerEntries.push(`'${h.key}': \`${interpolateEnvVars(h.value)}\``);
|
|
2011
2358
|
}
|
|
2012
2359
|
const optParts = [];
|
|
2013
2360
|
if (headerEntries.length) optParts.push(`headers: { ${headerEntries.join(", ")} }`);
|
|
@@ -2026,7 +2373,7 @@ function buildHookLines(req, sharedVars) {
|
|
|
2026
2373
|
for (const e of parsed.extractions) {
|
|
2027
2374
|
const jp = e.accessor.replace(/^json\.?/, "");
|
|
2028
2375
|
const expr = jp ? `hookJson.${jp}` : "hookJson";
|
|
2029
|
-
const varName = toEnvVar
|
|
2376
|
+
const varName = toEnvVar(e.varName);
|
|
2030
2377
|
sharedVars.add(varName);
|
|
2031
2378
|
lines.push(` ${varName} = String(${expr});`);
|
|
2032
2379
|
}
|
|
@@ -2082,32 +2429,32 @@ ${lines.join("\n")}
|
|
|
2082
2429
|
const testName = nameMap.get(reqId) ?? req.name;
|
|
2083
2430
|
const method = req.method.toLowerCase();
|
|
2084
2431
|
const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
|
|
2085
|
-
const pathExpr = path2.includes("{{") ? "`" +
|
|
2432
|
+
const pathExpr = path2.includes("{{") ? "`" + interpolateEnvVars(path2, sharedVars) + "`" : `'${path2}'`;
|
|
2086
2433
|
const optionParts = [];
|
|
2087
2434
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
|
|
2088
|
-
const effectiveAuth = req
|
|
2089
|
-
const allHeaders =
|
|
2435
|
+
const effectiveAuth = resolveEffectiveAuth(req, inherited);
|
|
2436
|
+
const allHeaders = mergeHeaders(req, inherited);
|
|
2090
2437
|
const headerEntries = [];
|
|
2091
2438
|
if (effectiveAuth.type === "bearer") {
|
|
2092
2439
|
const token = effectiveAuth.token ?? "";
|
|
2093
2440
|
if (token.includes("{{")) {
|
|
2094
|
-
headerEntries.push(`Authorization: \`Bearer ${
|
|
2441
|
+
headerEntries.push(`Authorization: \`Bearer ${interpolateEnvVars(token, sharedVars)}\``);
|
|
2095
2442
|
} else {
|
|
2096
2443
|
const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
|
|
2097
|
-
headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar
|
|
2444
|
+
headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar(ref)} ?? ''}\``);
|
|
2098
2445
|
}
|
|
2099
2446
|
} else if (effectiveAuth.type === "apikey" && effectiveAuth.apiKeyIn === "header") {
|
|
2100
2447
|
const val = effectiveAuth.apiKeyValue ?? "";
|
|
2101
2448
|
const name = effectiveAuth.apiKeyName ?? "X-API-Key";
|
|
2102
2449
|
if (val.includes("{{")) {
|
|
2103
|
-
headerEntries.push(`'${name}': \`${
|
|
2450
|
+
headerEntries.push(`'${name}': \`${interpolateEnvVars(val, sharedVars)}\``);
|
|
2104
2451
|
} else {
|
|
2105
2452
|
const ref = effectiveAuth.apiKeySecretRef ?? "API_KEY";
|
|
2106
|
-
headerEntries.push(`'${name}': \`\${process.env.${toEnvVar
|
|
2453
|
+
headerEntries.push(`'${name}': \`\${process.env.${toEnvVar(ref)} ?? ''}\``);
|
|
2107
2454
|
}
|
|
2108
2455
|
}
|
|
2109
2456
|
for (const h of allHeaders) {
|
|
2110
|
-
headerEntries.push(`'${h.key}': \`${
|
|
2457
|
+
headerEntries.push(`'${h.key}': \`${interpolateEnvVars(h.value, sharedVars)}\``);
|
|
2111
2458
|
}
|
|
2112
2459
|
if (headerEntries.length) {
|
|
2113
2460
|
optionParts.push(` headers: {
|
|
@@ -2117,17 +2464,16 @@ ${lines.join("\n")}
|
|
|
2117
2464
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
2118
2465
|
if (enabledParams.length) {
|
|
2119
2466
|
const pairs = enabledParams.map(
|
|
2120
|
-
(p) => p.value.includes("{{") ? `'${p.key}': \`${
|
|
2467
|
+
(p) => p.value.includes("{{") ? `'${p.key}': \`${interpolateEnvVars(p.value, sharedVars)}\`` : `'${p.key}': '${p.value}'`
|
|
2121
2468
|
).join(", ");
|
|
2122
2469
|
optionParts.push(` params: { ${pairs} }`);
|
|
2123
2470
|
}
|
|
2124
|
-
|
|
2125
|
-
if (hasBody && req.body.mode === "json" && req.body.json) {
|
|
2471
|
+
if (hasBody(req) && req.body.mode === "json" && req.body.json) {
|
|
2126
2472
|
try {
|
|
2127
2473
|
const rendered = renderJsValue(JSON.parse(req.body.json), " ", sharedVars);
|
|
2128
2474
|
optionParts.push(` data: ${rendered}`);
|
|
2129
2475
|
} catch {
|
|
2130
|
-
optionParts.push(` data: \`${
|
|
2476
|
+
optionParts.push(` data: \`${interpolateEnvVars(req.body.json, sharedVars)}\``);
|
|
2131
2477
|
}
|
|
2132
2478
|
}
|
|
2133
2479
|
const optionsStr = optionParts.length ? `, {
|
|
@@ -2173,7 +2519,7 @@ ${optionParts.join(",\n")},
|
|
|
2173
2519
|
for (const e of parsed.extractions) {
|
|
2174
2520
|
const path22 = e.accessor.replace(/^json\.?/, "");
|
|
2175
2521
|
const expr = path22 ? `json.${path22}` : "json";
|
|
2176
|
-
lines.push(` process.env.${toEnvVar
|
|
2522
|
+
lines.push(` process.env.${toEnvVar(e.varName)} = String(${expr});`);
|
|
2177
2523
|
}
|
|
2178
2524
|
lines.push(` });`);
|
|
2179
2525
|
tests.push(lines.join("\n"));
|
|
@@ -2203,27 +2549,8 @@ function buildPackageJson$2(collectionName) {
|
|
|
2203
2549
|
}
|
|
2204
2550
|
}, null, 2) + "\n";
|
|
2205
2551
|
}
|
|
2206
|
-
function renderTree$4(paths) {
|
|
2207
|
-
const root = {};
|
|
2208
|
-
for (const p of [...paths].sort()) {
|
|
2209
|
-
let cur = root;
|
|
2210
|
-
for (const part of p.split("/")) {
|
|
2211
|
-
cur = cur[part] ??= {};
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
function render(node, prefix = "") {
|
|
2215
|
-
const entries = Object.entries(node);
|
|
2216
|
-
return entries.flatMap(([name, children], i) => {
|
|
2217
|
-
const last = i === entries.length - 1;
|
|
2218
|
-
const lines = [`${prefix}${last ? "└── " : "├── "}${name}`];
|
|
2219
|
-
if (Object.keys(children).length) lines.push(...render(children, prefix + (last ? " " : "│ ")));
|
|
2220
|
-
return lines;
|
|
2221
|
-
});
|
|
2222
|
-
}
|
|
2223
|
-
return [".", ...render(root)].join("\n");
|
|
2224
|
-
}
|
|
2225
2552
|
function buildReadme$4(collectionName, filePaths) {
|
|
2226
|
-
const tree = renderTree
|
|
2553
|
+
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2227
2554
|
return `# ${collectionName} — API Tests (Playwright JavaScript)
|
|
2228
2555
|
|
|
2229
2556
|
## Project structure
|
|
@@ -2247,8 +2574,8 @@ function generatePlaywrightJs(collection, environment) {
|
|
|
2247
2574
|
const files = [];
|
|
2248
2575
|
function processFolder(folder, name) {
|
|
2249
2576
|
if (folder.requestIds.length > 0) {
|
|
2250
|
-
const nameMap = buildNameMap(folder, collection.requests);
|
|
2251
|
-
files.push({ path: `tests/${slug
|
|
2577
|
+
const nameMap = buildNameMap$1(folder, collection.requests);
|
|
2578
|
+
files.push({ path: `tests/${slug(name)}.spec.js`, content: buildSpec(name, folder, collection, nameMap) });
|
|
2252
2579
|
}
|
|
2253
2580
|
for (const sub of folder.folders) processFolder(sub, sub.name);
|
|
2254
2581
|
}
|
|
@@ -2262,18 +2589,6 @@ function generatePlaywrightJs(collection, environment) {
|
|
|
2262
2589
|
);
|
|
2263
2590
|
return files;
|
|
2264
2591
|
}
|
|
2265
|
-
function slug$1(name) {
|
|
2266
|
-
return name.replace(/\W+/g, "-").toLowerCase().replace(/^-|-$/g, "");
|
|
2267
|
-
}
|
|
2268
|
-
function toEnvVar$1(key) {
|
|
2269
|
-
return key.replace(/\W+/g, "_").toUpperCase();
|
|
2270
|
-
}
|
|
2271
|
-
function interpolateValue$1(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
2272
|
-
return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
2273
|
-
const envKey = toEnvVar$1(key.trim());
|
|
2274
|
-
return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
|
|
2275
|
-
});
|
|
2276
|
-
}
|
|
2277
2592
|
function buildJestConfig$1() {
|
|
2278
2593
|
return `import type { Config } from 'jest'
|
|
2279
2594
|
|
|
@@ -2288,11 +2603,9 @@ export default config
|
|
|
2288
2603
|
`;
|
|
2289
2604
|
}
|
|
2290
2605
|
function buildClient$1(environment) {
|
|
2291
|
-
const baseUrl = environment
|
|
2292
|
-
(v) => ["base_url", "baseurl", "base-url"].includes(v.key.toLowerCase()) && !v.secret
|
|
2293
|
-
)?.value ?? "http://localhost:3000";
|
|
2606
|
+
const baseUrl = getEnvBaseUrl(environment, "http://localhost:3000");
|
|
2294
2607
|
const secretVars = environment?.variables.filter((v) => v.secret && v.secretRef) ?? [];
|
|
2295
|
-
const envComments = secretVars.map((v) => `# ${toEnvVar
|
|
2608
|
+
const envComments = secretVars.map((v) => `# ${toEnvVar(v.key)}=<from keychain>`).join("\n");
|
|
2296
2609
|
return `import supertest from 'supertest'
|
|
2297
2610
|
import * as dotenv from 'dotenv'
|
|
2298
2611
|
|
|
@@ -2337,7 +2650,7 @@ function buildTestFile$1(folderName, folder, collection) {
|
|
|
2337
2650
|
for (const e of parsed.extractions) {
|
|
2338
2651
|
const jp = e.accessor.replace(/^json\.?/, "");
|
|
2339
2652
|
const expr = jp ? `hookRes.body.${jp}` : "hookRes.body";
|
|
2340
|
-
const varName = toEnvVar
|
|
2653
|
+
const varName = toEnvVar(e.varName);
|
|
2341
2654
|
sharedVars.add(varName);
|
|
2342
2655
|
lines.push(` ${varName} = String(${expr});`);
|
|
2343
2656
|
}
|
|
@@ -2368,12 +2681,11 @@ ${lines.join("\n")}
|
|
|
2368
2681
|
const req = requests[reqId];
|
|
2369
2682
|
if (!req || req.disabled || req.hookType) continue;
|
|
2370
2683
|
const method = req.method.toLowerCase();
|
|
2371
|
-
const path2 =
|
|
2684
|
+
const path2 = interpolateEnvVars(req.url.replace(/^https?:\/\/[^/]+/, "") || "/", sharedVars);
|
|
2372
2685
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
|
|
2373
|
-
const effectiveAuth = req
|
|
2374
|
-
const allHeaders =
|
|
2686
|
+
const effectiveAuth = resolveEffectiveAuth(req, inherited);
|
|
2687
|
+
const allHeaders = mergeHeaders(req, inherited);
|
|
2375
2688
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
2376
|
-
const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
|
|
2377
2689
|
const lines = [];
|
|
2378
2690
|
lines.push(` it('${nameMap.get(reqId)}', async () => {`);
|
|
2379
2691
|
lines.push(` const res = await api`);
|
|
@@ -2381,29 +2693,29 @@ ${lines.join("\n")}
|
|
|
2381
2693
|
if (effectiveAuth.type === "bearer") {
|
|
2382
2694
|
const token = effectiveAuth.token ?? "";
|
|
2383
2695
|
if (token.includes("{{")) {
|
|
2384
|
-
lines.push(` .set('Authorization', \`Bearer ${
|
|
2696
|
+
lines.push(` .set('Authorization', \`Bearer ${interpolateEnvVars(token, sharedVars)}\`)`);
|
|
2385
2697
|
} else {
|
|
2386
2698
|
const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
|
|
2387
|
-
lines.push(` .set('Authorization', \`Bearer \${process.env.${toEnvVar
|
|
2699
|
+
lines.push(` .set('Authorization', \`Bearer \${process.env.${toEnvVar(ref)} ?? ''}\`)`);
|
|
2388
2700
|
}
|
|
2389
2701
|
}
|
|
2390
2702
|
for (const h of allHeaders) {
|
|
2391
|
-
lines.push(` .set('${h.key}', \`${
|
|
2703
|
+
lines.push(` .set('${h.key}', \`${interpolateEnvVars(h.value, sharedVars)}\`)`);
|
|
2392
2704
|
}
|
|
2393
2705
|
if (enabledParams.length) {
|
|
2394
|
-
const pairs = enabledParams.map((p) => `${p.key}: \`${
|
|
2706
|
+
const pairs = enabledParams.map((p) => `${p.key}: \`${interpolateEnvVars(p.value, sharedVars)}\``).join(", ");
|
|
2395
2707
|
lines.push(` .query({ ${pairs} })`);
|
|
2396
2708
|
}
|
|
2397
|
-
if (hasBody) {
|
|
2709
|
+
if (hasBody(req)) {
|
|
2398
2710
|
if (req.body.mode === "json") {
|
|
2399
2711
|
const jsonBody = req.body.json ?? "{}";
|
|
2400
2712
|
if (jsonBody.includes("{{")) {
|
|
2401
|
-
lines.push(` .send(JSON.parse(\`${
|
|
2713
|
+
lines.push(` .send(JSON.parse(\`${interpolateEnvVars(jsonBody, sharedVars)}\`))`);
|
|
2402
2714
|
} else {
|
|
2403
2715
|
lines.push(` .send(${jsonBody})`);
|
|
2404
2716
|
}
|
|
2405
2717
|
} else if (req.body.mode === "form" && req.body.form) {
|
|
2406
|
-
const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${
|
|
2718
|
+
const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${interpolateEnvVars(p.value, sharedVars)}\``).join(", ");
|
|
2407
2719
|
lines.push(` .type('form')`);
|
|
2408
2720
|
lines.push(` .send({ ${pairs} })`);
|
|
2409
2721
|
}
|
|
@@ -2442,7 +2754,7 @@ ${lines.join("\n")}
|
|
|
2442
2754
|
for (const e of parsed.extractions) {
|
|
2443
2755
|
const path22 = e.accessor.replace(/^json\.?/, "");
|
|
2444
2756
|
const expr = path22 ? `res.body.${path22}` : "res.body";
|
|
2445
|
-
lines.push(` process.env.${toEnvVar
|
|
2757
|
+
lines.push(` process.env.${toEnvVar(e.varName)} = String(${expr});`);
|
|
2446
2758
|
}
|
|
2447
2759
|
lines.push(` })`);
|
|
2448
2760
|
tests.push(lines.join("\n"));
|
|
@@ -2486,27 +2798,8 @@ function buildTsConfig() {
|
|
|
2486
2798
|
exclude: ["node_modules", "dist"]
|
|
2487
2799
|
}, null, 2) + "\n";
|
|
2488
2800
|
}
|
|
2489
|
-
function renderTree$3(paths) {
|
|
2490
|
-
const root = {};
|
|
2491
|
-
for (const p of [...paths].sort()) {
|
|
2492
|
-
let cur = root;
|
|
2493
|
-
for (const part of p.split("/")) {
|
|
2494
|
-
cur = cur[part] ??= {};
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
function render(node, prefix = "") {
|
|
2498
|
-
const entries = Object.entries(node);
|
|
2499
|
-
return entries.flatMap(([name, children], i) => {
|
|
2500
|
-
const last = i === entries.length - 1;
|
|
2501
|
-
const lines = [`${prefix}${last ? "└── " : "├── "}${name}`];
|
|
2502
|
-
if (Object.keys(children).length) lines.push(...render(children, prefix + (last ? " " : "│ ")));
|
|
2503
|
-
return lines;
|
|
2504
|
-
});
|
|
2505
|
-
}
|
|
2506
|
-
return [".", ...render(root)].join("\n");
|
|
2507
|
-
}
|
|
2508
2801
|
function buildReadme$3(collectionName, filePaths) {
|
|
2509
|
-
const tree = renderTree
|
|
2802
|
+
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2510
2803
|
return `# ${collectionName} — API Tests (Supertest + Jest TypeScript)
|
|
2511
2804
|
|
|
2512
2805
|
## Project structure
|
|
@@ -2533,7 +2826,7 @@ function generateSupertestTs(collection, environment) {
|
|
|
2533
2826
|
function processFolder(folder, name) {
|
|
2534
2827
|
if (folder.requestIds.length > 0) {
|
|
2535
2828
|
files.push({
|
|
2536
|
-
path: `tests/${slug
|
|
2829
|
+
path: `tests/${slug(name)}.test.ts`,
|
|
2537
2830
|
content: buildTestFile$1(name, folder, collection)
|
|
2538
2831
|
});
|
|
2539
2832
|
}
|
|
@@ -2555,18 +2848,6 @@ function generateSupertestTs(collection, environment) {
|
|
|
2555
2848
|
);
|
|
2556
2849
|
return files;
|
|
2557
2850
|
}
|
|
2558
|
-
function slug(name) {
|
|
2559
|
-
return name.replace(/\W+/g, "-").toLowerCase().replace(/^-|-$/g, "");
|
|
2560
|
-
}
|
|
2561
|
-
function toEnvVar(key) {
|
|
2562
|
-
return key.replace(/\W+/g, "_").toUpperCase();
|
|
2563
|
-
}
|
|
2564
|
-
function interpolateValue(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
2565
|
-
return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
2566
|
-
const envKey = toEnvVar(key.trim());
|
|
2567
|
-
return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
|
|
2568
|
-
});
|
|
2569
|
-
}
|
|
2570
2851
|
function buildJestConfig() {
|
|
2571
2852
|
return `/** @type {import('jest').Config} */
|
|
2572
2853
|
module.exports = {
|
|
@@ -2577,9 +2858,7 @@ module.exports = {
|
|
|
2577
2858
|
`;
|
|
2578
2859
|
}
|
|
2579
2860
|
function buildClient(environment) {
|
|
2580
|
-
const baseUrl = environment
|
|
2581
|
-
(v) => ["base_url", "baseurl", "base-url"].includes(v.key.toLowerCase()) && !v.secret
|
|
2582
|
-
)?.value ?? "http://localhost:3000";
|
|
2861
|
+
const baseUrl = getEnvBaseUrl(environment, "http://localhost:3000");
|
|
2583
2862
|
const secretVars = environment?.variables.filter((v) => v.secret && v.secretRef) ?? [];
|
|
2584
2863
|
const envComments = secretVars.map((v) => `# ${toEnvVar(v.key)}=<from keychain>`).join("\n");
|
|
2585
2864
|
return `const supertest = require('supertest');
|
|
@@ -2655,12 +2934,11 @@ ${lines.join("\n")}
|
|
|
2655
2934
|
const req = requests[reqId];
|
|
2656
2935
|
if (!req || req.disabled || req.hookType) continue;
|
|
2657
2936
|
const method = req.method.toLowerCase();
|
|
2658
|
-
const path2 =
|
|
2937
|
+
const path2 = interpolateEnvVars(req.url.replace(/^https?:\/\/[^/]+/, "") || "/", sharedVars);
|
|
2659
2938
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
|
|
2660
|
-
const effectiveAuth = req
|
|
2661
|
-
const allHeaders =
|
|
2939
|
+
const effectiveAuth = resolveEffectiveAuth(req, inherited);
|
|
2940
|
+
const allHeaders = mergeHeaders(req, inherited);
|
|
2662
2941
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
2663
|
-
const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
|
|
2664
2942
|
const lines = [];
|
|
2665
2943
|
lines.push(` it('${nameMap.get(reqId)}', async () => {`);
|
|
2666
2944
|
lines.push(` const res = await api`);
|
|
@@ -2668,29 +2946,29 @@ ${lines.join("\n")}
|
|
|
2668
2946
|
if (effectiveAuth.type === "bearer") {
|
|
2669
2947
|
const token = effectiveAuth.token ?? "";
|
|
2670
2948
|
if (token.includes("{{")) {
|
|
2671
|
-
lines.push(` .set('Authorization', \`Bearer ${
|
|
2949
|
+
lines.push(` .set('Authorization', \`Bearer ${interpolateEnvVars(token, sharedVars)}\`)`);
|
|
2672
2950
|
} else {
|
|
2673
2951
|
const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
|
|
2674
2952
|
lines.push(` .set('Authorization', \`Bearer \${process.env.${toEnvVar(ref)} ?? ''}\`)`);
|
|
2675
2953
|
}
|
|
2676
2954
|
}
|
|
2677
2955
|
for (const h of allHeaders) {
|
|
2678
|
-
lines.push(` .set('${h.key}', \`${
|
|
2956
|
+
lines.push(` .set('${h.key}', \`${interpolateEnvVars(h.value, sharedVars)}\`)`);
|
|
2679
2957
|
}
|
|
2680
2958
|
if (enabledParams.length) {
|
|
2681
|
-
const pairs = enabledParams.map((p) => `${p.key}: \`${
|
|
2959
|
+
const pairs = enabledParams.map((p) => `${p.key}: \`${interpolateEnvVars(p.value, sharedVars)}\``).join(", ");
|
|
2682
2960
|
lines.push(` .query({ ${pairs} })`);
|
|
2683
2961
|
}
|
|
2684
|
-
if (hasBody) {
|
|
2962
|
+
if (hasBody(req)) {
|
|
2685
2963
|
if (req.body.mode === "json") {
|
|
2686
2964
|
const jsonBody = req.body.json ?? "{}";
|
|
2687
2965
|
if (jsonBody.includes("{{")) {
|
|
2688
|
-
lines.push(` .send(JSON.parse(\`${
|
|
2966
|
+
lines.push(` .send(JSON.parse(\`${interpolateEnvVars(jsonBody, sharedVars)}\`))`);
|
|
2689
2967
|
} else {
|
|
2690
2968
|
lines.push(` .send(${jsonBody})`);
|
|
2691
2969
|
}
|
|
2692
2970
|
} else if (req.body.mode === "form" && req.body.form) {
|
|
2693
|
-
const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${
|
|
2971
|
+
const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${interpolateEnvVars(p.value, sharedVars)}\``).join(", ");
|
|
2694
2972
|
lines.push(` .type('form')`);
|
|
2695
2973
|
lines.push(` .send({ ${pairs} })`);
|
|
2696
2974
|
}
|
|
@@ -2755,27 +3033,8 @@ function buildPackageJson(collectionName) {
|
|
|
2755
3033
|
}
|
|
2756
3034
|
}, null, 2) + "\n";
|
|
2757
3035
|
}
|
|
2758
|
-
function renderTree$2(paths) {
|
|
2759
|
-
const root = {};
|
|
2760
|
-
for (const p of [...paths].sort()) {
|
|
2761
|
-
let cur = root;
|
|
2762
|
-
for (const part of p.split("/")) {
|
|
2763
|
-
cur = cur[part] ??= {};
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
function render(node, prefix = "") {
|
|
2767
|
-
const entries = Object.entries(node);
|
|
2768
|
-
return entries.flatMap(([name, children], i) => {
|
|
2769
|
-
const last = i === entries.length - 1;
|
|
2770
|
-
const lines = [`${prefix}${last ? "└── " : "├── "}${name}`];
|
|
2771
|
-
if (Object.keys(children).length) lines.push(...render(children, prefix + (last ? " " : "│ ")));
|
|
2772
|
-
return lines;
|
|
2773
|
-
});
|
|
2774
|
-
}
|
|
2775
|
-
return [".", ...render(root)].join("\n");
|
|
2776
|
-
}
|
|
2777
3036
|
function buildReadme$2(collectionName, filePaths) {
|
|
2778
|
-
const tree = renderTree
|
|
3037
|
+
const tree = renderTree([...filePaths, ".env.local"]);
|
|
2779
3038
|
return `# ${collectionName} — API Tests (Supertest + Jest JavaScript)
|
|
2780
3039
|
|
|
2781
3040
|
## Project structure
|
|
@@ -2823,9 +3082,6 @@ function generateSupertestJs(collection, environment) {
|
|
|
2823
3082
|
);
|
|
2824
3083
|
return files;
|
|
2825
3084
|
}
|
|
2826
|
-
function javaClass$1(name) {
|
|
2827
|
-
return name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
2828
|
-
}
|
|
2829
3085
|
function javaMethod(name) {
|
|
2830
3086
|
const parts = name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean);
|
|
2831
3087
|
return parts[0].toLowerCase() + parts.slice(1).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
@@ -2842,12 +3098,9 @@ function javaTypeFor(expected) {
|
|
|
2842
3098
|
return "Object.class";
|
|
2843
3099
|
}
|
|
2844
3100
|
}
|
|
2845
|
-
function toEnvConst(key) {
|
|
2846
|
-
return key.replace(/\W+/g, "_").toUpperCase();
|
|
2847
|
-
}
|
|
2848
3101
|
function interpolateJava(value, sharedVars = /* @__PURE__ */ new Set()) {
|
|
2849
3102
|
return '"' + value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
2850
|
-
const envKey =
|
|
3103
|
+
const envKey = toEnvVar(key.trim());
|
|
2851
3104
|
if (sharedVars.has(envKey)) {
|
|
2852
3105
|
return `" + ${envKey} + "`;
|
|
2853
3106
|
}
|
|
@@ -2922,12 +3175,10 @@ function buildPom$1(collectionName) {
|
|
|
2922
3175
|
`;
|
|
2923
3176
|
}
|
|
2924
3177
|
function buildBaseTest(environment) {
|
|
2925
|
-
const baseUrl = environment
|
|
2926
|
-
(v) => ["base_url", "baseurl", "base-url"].includes(v.key.toLowerCase()) && !v.secret
|
|
2927
|
-
)?.value ?? "http://localhost:8080";
|
|
3178
|
+
const baseUrl = getEnvBaseUrl(environment, "http://localhost:8080");
|
|
2928
3179
|
const secretVars = environment?.variables.filter((v) => v.secret && v.secretRef) ?? [];
|
|
2929
3180
|
const secretComments = secretVars.map(
|
|
2930
|
-
(v) => ` * - ${
|
|
3181
|
+
(v) => ` * - ${toEnvVar(v.key)}=<value from keychain>`
|
|
2931
3182
|
).join("\n");
|
|
2932
3183
|
return `package com.example.api;
|
|
2933
3184
|
|
|
@@ -2966,7 +3217,7 @@ public class BaseTest {
|
|
|
2966
3217
|
}
|
|
2967
3218
|
function buildTestClass(folderName, folder, collection) {
|
|
2968
3219
|
const requests = collection.requests;
|
|
2969
|
-
const className = javaClass
|
|
3220
|
+
const className = javaClass(folderName) + "Test";
|
|
2970
3221
|
const methods = [];
|
|
2971
3222
|
const hooks = requestCollection.getAllApplicableHooks(folder.id, collection);
|
|
2972
3223
|
const beforeAllH = hooks.beforeAll;
|
|
@@ -2986,7 +3237,7 @@ function buildTestClass(folderName, folder, collection) {
|
|
|
2986
3237
|
lines.push(` .when().${method}("${path2}");`);
|
|
2987
3238
|
for (const e of parsed.extractions) {
|
|
2988
3239
|
const jp = accessorToJsonPath(e.accessor);
|
|
2989
|
-
const varName =
|
|
3240
|
+
const varName = toEnvVar(e.varName);
|
|
2990
3241
|
sharedVars.add(varName);
|
|
2991
3242
|
lines.push(` ${varName} = hookResponse.jsonPath().getString("${jp}");`);
|
|
2992
3243
|
}
|
|
@@ -3032,10 +3283,9 @@ ${lines.join("\n")}
|
|
|
3032
3283
|
const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
|
|
3033
3284
|
const javaPath = interpolateJava(path2, sharedVars);
|
|
3034
3285
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
|
|
3035
|
-
const effectiveAuth = req
|
|
3036
|
-
const allHeaders =
|
|
3286
|
+
const effectiveAuth = resolveEffectiveAuth(req, inherited);
|
|
3287
|
+
const allHeaders = mergeHeaders(req, inherited);
|
|
3037
3288
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
3038
|
-
const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
|
|
3039
3289
|
const lines = [];
|
|
3040
3290
|
lines.push(` @Test`);
|
|
3041
3291
|
lines.push(` public void ${methodName}() {`);
|
|
@@ -3045,14 +3295,14 @@ ${lines.join("\n")}
|
|
|
3045
3295
|
const token = effectiveAuth.token ?? "";
|
|
3046
3296
|
if (token.includes("{{")) {
|
|
3047
3297
|
const varRef = token.match(/\{\{([^}]+)\}\}/)?.[1]?.trim();
|
|
3048
|
-
const envKey = varRef ?
|
|
3298
|
+
const envKey = varRef ? toEnvVar(varRef) : "";
|
|
3049
3299
|
if (envKey && sharedVars.has(envKey)) {
|
|
3050
3300
|
lines.push(` .header("Authorization", "Bearer " + ${envKey})`);
|
|
3051
3301
|
} else {
|
|
3052
3302
|
lines.push(` .header("Authorization", "Bearer " + ${interpolateJava(token, sharedVars)})`);
|
|
3053
3303
|
}
|
|
3054
3304
|
} else {
|
|
3055
|
-
lines.push(` .header("Authorization", "Bearer " + System.getenv("${
|
|
3305
|
+
lines.push(` .header("Authorization", "Bearer " + System.getenv("${toEnvVar(effectiveAuth.tokenSecretRef ?? "API_TOKEN")}"))`);
|
|
3056
3306
|
}
|
|
3057
3307
|
}
|
|
3058
3308
|
for (const h of allHeaders) {
|
|
@@ -3061,7 +3311,7 @@ ${lines.join("\n")}
|
|
|
3061
3311
|
for (const p of enabledParams) {
|
|
3062
3312
|
lines.push(` .queryParam("${p.key}", ${interpolateJava(p.value, sharedVars)})`);
|
|
3063
3313
|
}
|
|
3064
|
-
if (hasBody) {
|
|
3314
|
+
if (hasBody(req)) {
|
|
3065
3315
|
if (req.body.mode === "json" && req.body.json) {
|
|
3066
3316
|
const escaped = req.body.json.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
3067
3317
|
lines.push(` .body(${interpolateJava(escaped, sharedVars)})`);
|
|
@@ -3122,27 +3372,8 @@ ${methods.join("\n\n")}
|
|
|
3122
3372
|
}
|
|
3123
3373
|
`;
|
|
3124
3374
|
}
|
|
3125
|
-
function renderTree$1(paths) {
|
|
3126
|
-
const root = {};
|
|
3127
|
-
for (const p of [...paths].sort()) {
|
|
3128
|
-
let cur = root;
|
|
3129
|
-
for (const part of p.split("/")) {
|
|
3130
|
-
cur = cur[part] ??= {};
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
function render(node, prefix = "") {
|
|
3134
|
-
const entries = Object.entries(node);
|
|
3135
|
-
return entries.flatMap(([name, children], i) => {
|
|
3136
|
-
const last = i === entries.length - 1;
|
|
3137
|
-
const lines = [`${prefix}${last ? "└── " : "├── "}${name}`];
|
|
3138
|
-
if (Object.keys(children).length) lines.push(...render(children, prefix + (last ? " " : "│ ")));
|
|
3139
|
-
return lines;
|
|
3140
|
-
});
|
|
3141
|
-
}
|
|
3142
|
-
return [".", ...render(root)].join("\n");
|
|
3143
|
-
}
|
|
3144
3375
|
function buildReadme$1(collectionName, filePaths) {
|
|
3145
|
-
const tree = renderTree
|
|
3376
|
+
const tree = renderTree(filePaths);
|
|
3146
3377
|
return `# ${collectionName} — API Tests (REST Assured + JUnit 5)
|
|
3147
3378
|
|
|
3148
3379
|
## Project structure
|
|
@@ -3171,7 +3402,7 @@ function generateRestAssured(collection, environment) {
|
|
|
3171
3402
|
];
|
|
3172
3403
|
function processFolder(folder, name) {
|
|
3173
3404
|
if (folder.requestIds.length > 0) {
|
|
3174
|
-
const className = javaClass
|
|
3405
|
+
const className = javaClass(name) + "Test";
|
|
3175
3406
|
files.push({
|
|
3176
3407
|
path: `src/test/java/com/example/api/${className}.java`,
|
|
3177
3408
|
content: buildTestClass(name, folder, collection)
|
|
@@ -3190,9 +3421,6 @@ function generateRestAssured(collection, environment) {
|
|
|
3190
3421
|
files.unshift({ path: "README.md", content: buildReadme$1(collection.name, files.map((f) => f.path)) });
|
|
3191
3422
|
return files;
|
|
3192
3423
|
}
|
|
3193
|
-
function javaClass(name) {
|
|
3194
|
-
return name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
3195
|
-
}
|
|
3196
3424
|
function jsVar(name) {
|
|
3197
3425
|
const parts = name.replace(/[^a-zA-Z0-9]+/g, " ").split(/\s+/).filter(Boolean).map((p) => p.toLowerCase());
|
|
3198
3426
|
if (parts.length === 0) return "_";
|
|
@@ -3290,9 +3518,7 @@ function buildPom(collectionName) {
|
|
|
3290
3518
|
`;
|
|
3291
3519
|
}
|
|
3292
3520
|
function buildKarateConfig(environment) {
|
|
3293
|
-
const baseUrl = environment
|
|
3294
|
-
(v) => ["base_url", "baseurl", "base-url"].includes(v.key.toLowerCase()) && !v.secret
|
|
3295
|
-
)?.value ?? "http://localhost:8080";
|
|
3521
|
+
const baseUrl = getEnvBaseUrl(environment, "http://localhost:8080");
|
|
3296
3522
|
const lines = [];
|
|
3297
3523
|
lines.push(`function fn() {`);
|
|
3298
3524
|
lines.push(` var env = karate.env || 'dev';`);
|
|
@@ -3364,7 +3590,7 @@ function buildBackground(folderId, collection) {
|
|
|
3364
3590
|
for (const x of (h.headers ?? []).filter((x2) => x2.enabled && x2.key)) {
|
|
3365
3591
|
lines.push(` * header ${x.key} = ${interpolateKarate(x.value)}`);
|
|
3366
3592
|
}
|
|
3367
|
-
if (h
|
|
3593
|
+
if (hasBody(h)) {
|
|
3368
3594
|
for (const block of bodySteps(h.body, has)) {
|
|
3369
3595
|
lines.push(` * ${block[0]}`);
|
|
3370
3596
|
for (let i = 1; i < block.length; i++) lines.push(block[i]);
|
|
@@ -3453,11 +3679,10 @@ function buildFeature(folderName, folder, collection) {
|
|
|
3453
3679
|
}
|
|
3454
3680
|
usedTags.add(tag);
|
|
3455
3681
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
|
|
3456
|
-
const effectiveAuth = req
|
|
3457
|
-
const allHeaders =
|
|
3682
|
+
const effectiveAuth = resolveEffectiveAuth(req, inherited);
|
|
3683
|
+
const allHeaders = mergeHeaders(req, inherited);
|
|
3458
3684
|
const enabledParams = req.params.filter((p) => p.enabled && p.key);
|
|
3459
3685
|
const method = req.method.toLowerCase();
|
|
3460
|
-
const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
|
|
3461
3686
|
const lines = [];
|
|
3462
3687
|
lines.push(`@${tag}`);
|
|
3463
3688
|
lines.push(`Scenario: ${req.name}`);
|
|
@@ -3487,7 +3712,7 @@ function buildFeature(folderName, folder, collection) {
|
|
|
3487
3712
|
for (const p of enabledParams) {
|
|
3488
3713
|
setup.push([`param ${p.key} = ${interpolateKarate(p.value)}`]);
|
|
3489
3714
|
}
|
|
3490
|
-
if (hasBody) {
|
|
3715
|
+
if (hasBody(req)) {
|
|
3491
3716
|
const declared = new Set(allHeaders.map((h) => h.key.toLowerCase()));
|
|
3492
3717
|
const has = (n) => declared.has(n.toLowerCase());
|
|
3493
3718
|
for (const block of bodySteps(req.body, has)) setup.push(block);
|
|
@@ -3547,25 +3772,6 @@ ${bgBlock}
|
|
|
3547
3772
|
${scenarios.join("\n\n")}
|
|
3548
3773
|
`;
|
|
3549
3774
|
}
|
|
3550
|
-
function renderTree(paths) {
|
|
3551
|
-
const root = {};
|
|
3552
|
-
for (const p of [...paths].sort()) {
|
|
3553
|
-
let cur = root;
|
|
3554
|
-
for (const part of p.split("/")) {
|
|
3555
|
-
cur = cur[part] ??= {};
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
function render(node, prefix = "") {
|
|
3559
|
-
const entries = Object.entries(node);
|
|
3560
|
-
return entries.flatMap(([name, children], i) => {
|
|
3561
|
-
const last = i === entries.length - 1;
|
|
3562
|
-
const lines = [`${prefix}${last ? "└── " : "├── "}${name}`];
|
|
3563
|
-
if (Object.keys(children).length) lines.push(...render(children, prefix + (last ? " " : "│ ")));
|
|
3564
|
-
return lines;
|
|
3565
|
-
});
|
|
3566
|
-
}
|
|
3567
|
-
return [".", ...render(root)].join("\n");
|
|
3568
|
-
}
|
|
3569
3775
|
function buildReadme(collectionName, filePaths) {
|
|
3570
3776
|
const tree = renderTree(filePaths);
|
|
3571
3777
|
return `# ${collectionName} — API Tests (Karate + JUnit 5)
|
|
@@ -3626,8 +3832,106 @@ function generateKarate(collection, environment) {
|
|
|
3626
3832
|
files.unshift({ path: "README.md", content: buildReadme(collection.name, files.map((f) => f.path)) });
|
|
3627
3833
|
return files;
|
|
3628
3834
|
}
|
|
3835
|
+
function mapOut(s) {
|
|
3836
|
+
return s.replace(/\{\{\s*(\$[A-Za-z]+)\s*\}\}/g, (_m, name) => `{{${SPECTOR_TO_HTTP[name] ?? name}}}`);
|
|
3837
|
+
}
|
|
3838
|
+
function orderedRequests(collection) {
|
|
3839
|
+
const out = [];
|
|
3840
|
+
const walk = (folder) => {
|
|
3841
|
+
for (const id of folder.requestIds) {
|
|
3842
|
+
const req = collection.requests[id];
|
|
3843
|
+
if (req) out.push(req);
|
|
3844
|
+
}
|
|
3845
|
+
for (const sub of folder.folders) walk(sub);
|
|
3846
|
+
};
|
|
3847
|
+
walk(collection.rootFolder);
|
|
3848
|
+
return out;
|
|
3849
|
+
}
|
|
3850
|
+
function authToHeader(auth) {
|
|
3851
|
+
switch (auth.type) {
|
|
3852
|
+
case "bearer":
|
|
3853
|
+
return auth.token ? { key: "Authorization", value: `Bearer ${auth.token}`, enabled: true } : null;
|
|
3854
|
+
case "basic": {
|
|
3855
|
+
const user = auth.username ?? "";
|
|
3856
|
+
const pass = auth.password ?? "";
|
|
3857
|
+
const templated = /\{\{/.test(user) || /\{\{/.test(pass);
|
|
3858
|
+
const value = templated ? `Basic {{base64(${user}:${pass})}}` : `Basic ${Buffer.from(`${user}:${pass}`).toString("base64")}`;
|
|
3859
|
+
return { key: "Authorization", value, enabled: true };
|
|
3860
|
+
}
|
|
3861
|
+
case "apikey":
|
|
3862
|
+
if (auth.apiKeyIn === "query") return null;
|
|
3863
|
+
return auth.apiKeyName ? { key: auth.apiKeyName, value: auth.apiKeyValue ?? "", enabled: true } : null;
|
|
3864
|
+
default:
|
|
3865
|
+
return null;
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
function buildUrl(req) {
|
|
3869
|
+
const query = (req.params ?? []).filter((p) => p.enabled && p.key && p.paramType !== "path");
|
|
3870
|
+
if (req.auth.type === "apikey" && req.auth.apiKeyIn === "query" && req.auth.apiKeyName) {
|
|
3871
|
+
query.push({ key: req.auth.apiKeyName, value: req.auth.apiKeyValue ?? "", enabled: true });
|
|
3872
|
+
}
|
|
3873
|
+
if (!query.length) return req.url;
|
|
3874
|
+
const qs = query.map((p) => `${p.key}=${p.value}`).join("&");
|
|
3875
|
+
return req.url + (req.url.includes("?") ? "&" : "?") + qs;
|
|
3876
|
+
}
|
|
3877
|
+
function bodyLines(req) {
|
|
3878
|
+
const b = req.body;
|
|
3879
|
+
switch (b.mode) {
|
|
3880
|
+
case "json":
|
|
3881
|
+
return { contentType: "application/json", text: b.json ?? "" };
|
|
3882
|
+
case "graphql":
|
|
3883
|
+
return { contentType: "application/json", text: b.graphql?.query ?? "" };
|
|
3884
|
+
case "soap":
|
|
3885
|
+
return { contentType: "text/xml", text: b.soap?.envelope ?? "" };
|
|
3886
|
+
case "raw":
|
|
3887
|
+
return { contentType: b.rawContentType, text: b.raw ?? "" };
|
|
3888
|
+
case "form":
|
|
3889
|
+
return {
|
|
3890
|
+
contentType: "application/x-www-form-urlencoded",
|
|
3891
|
+
text: (b.form ?? []).filter((f) => f.enabled && f.key).map((f) => `${encodeURIComponent(f.key)}=${encodeURIComponent(f.value)}`).join("&")
|
|
3892
|
+
};
|
|
3893
|
+
default:
|
|
3894
|
+
return {};
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
function generateHttpFile(collection, environment) {
|
|
3898
|
+
const lines = [];
|
|
3899
|
+
const vars = {};
|
|
3900
|
+
for (const v of environment?.variables ?? []) {
|
|
3901
|
+
if (v.enabled && !v.secret) vars[v.key] = v.value;
|
|
3902
|
+
}
|
|
3903
|
+
Object.assign(vars, collection.collectionVariables ?? {});
|
|
3904
|
+
const varKeys = Object.keys(vars);
|
|
3905
|
+
if (varKeys.length) {
|
|
3906
|
+
for (const k of varKeys) lines.push(`@${k} = ${mapOut(vars[k])}`);
|
|
3907
|
+
lines.push("");
|
|
3908
|
+
}
|
|
3909
|
+
orderedRequests(collection).forEach((req, idx) => {
|
|
3910
|
+
if (idx > 0) lines.push("");
|
|
3911
|
+
lines.push(`### ${req.name}`);
|
|
3912
|
+
if (req.description?.trim()) {
|
|
3913
|
+
for (const l of req.description.trim().split(/\r?\n/)) lines.push(`# ${l}`);
|
|
3914
|
+
}
|
|
3915
|
+
lines.push(`${req.method} ${mapOut(buildUrl(req))}`);
|
|
3916
|
+
const headers = [...(req.headers ?? []).filter((h) => h.enabled && h.key)];
|
|
3917
|
+
const authHeader = authToHeader(req.auth);
|
|
3918
|
+
if (authHeader) headers.unshift(authHeader);
|
|
3919
|
+
const body = bodyLines(req);
|
|
3920
|
+
const hasCT = headers.some((h) => h.key.toLowerCase() === "content-type");
|
|
3921
|
+
if (body.text && body.contentType && !hasCT) {
|
|
3922
|
+
headers.push({ key: "Content-Type", value: body.contentType, enabled: true });
|
|
3923
|
+
}
|
|
3924
|
+
for (const h of headers) lines.push(`${h.key}: ${mapOut(h.value)}`);
|
|
3925
|
+
if (body.text) {
|
|
3926
|
+
lines.push("");
|
|
3927
|
+
lines.push(mapOut(body.text));
|
|
3928
|
+
}
|
|
3929
|
+
});
|
|
3930
|
+
const slug2 = collection.name.replace(/\W+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "collection";
|
|
3931
|
+
return [{ path: `${slug2}.http`, content: lines.join("\n") + "\n" }];
|
|
3932
|
+
}
|
|
3629
3933
|
function registerGenerateHandlers(ipc) {
|
|
3630
|
-
ipc.
|
|
3934
|
+
handle.handleIpc(ipc, handle.IPC.generate.code, (_e, opts) => {
|
|
3631
3935
|
const { collection, environment, target } = opts;
|
|
3632
3936
|
switch (target) {
|
|
3633
3937
|
case "robot_framework":
|
|
@@ -3644,18 +3948,20 @@ function registerGenerateHandlers(ipc) {
|
|
|
3644
3948
|
return generateRestAssured(collection, environment);
|
|
3645
3949
|
case "karate":
|
|
3646
3950
|
return generateKarate(collection, environment);
|
|
3951
|
+
case "http_file":
|
|
3952
|
+
return generateHttpFile(collection, environment);
|
|
3647
3953
|
default:
|
|
3648
3954
|
throw new Error(`Unknown target: ${target}`);
|
|
3649
3955
|
}
|
|
3650
3956
|
});
|
|
3651
|
-
ipc.
|
|
3957
|
+
handle.handleIpc(ipc, handle.IPC.generate.save, async (_e, files, outputDir) => {
|
|
3652
3958
|
for (const file of files) {
|
|
3653
3959
|
const fullPath = path.join(outputDir, file.path);
|
|
3654
3960
|
await promises.mkdir(path.dirname(fullPath), { recursive: true });
|
|
3655
3961
|
await promises.writeFile(fullPath, file.content, "utf8");
|
|
3656
3962
|
}
|
|
3657
3963
|
});
|
|
3658
|
-
ipc.
|
|
3964
|
+
handle.handleIpc(ipc, handle.IPC.generate.saveZip, async (_e, files, collectionName, target) => {
|
|
3659
3965
|
const colSlug = collectionName.replace(/\W+/g, "-").toLowerCase();
|
|
3660
3966
|
const targetSlug = target.replace(/_/g, "-");
|
|
3661
3967
|
const defaultName = `${colSlug}-${targetSlug}.zip`;
|
|
@@ -3672,294 +3978,23 @@ function registerGenerateHandlers(ipc) {
|
|
|
3672
3978
|
return true;
|
|
3673
3979
|
});
|
|
3674
3980
|
}
|
|
3675
|
-
async function buildDispatcher(proxy, tls) {
|
|
3676
|
-
const connectOpts = {};
|
|
3677
|
-
let hasTls = false;
|
|
3678
|
-
if (tls) {
|
|
3679
|
-
hasTls = true;
|
|
3680
|
-
if (tls.rejectUnauthorized !== void 0) connectOpts["rejectUnauthorized"] = tls.rejectUnauthorized;
|
|
3681
|
-
if (tls.caCertPath) {
|
|
3682
|
-
try {
|
|
3683
|
-
connectOpts["ca"] = await promises.readFile(tls.caCertPath);
|
|
3684
|
-
} catch {
|
|
3685
|
-
}
|
|
3686
|
-
}
|
|
3687
|
-
if (tls.clientCertPath) {
|
|
3688
|
-
try {
|
|
3689
|
-
connectOpts["cert"] = await promises.readFile(tls.clientCertPath);
|
|
3690
|
-
} catch {
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
if (tls.clientKeyPath) {
|
|
3694
|
-
try {
|
|
3695
|
-
connectOpts["key"] = await promises.readFile(tls.clientKeyPath);
|
|
3696
|
-
} catch {
|
|
3697
|
-
}
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
if (proxy?.url) {
|
|
3701
|
-
return new undici.ProxyAgent({
|
|
3702
|
-
uri: proxyUri,
|
|
3703
|
-
requestTls: proxyConnect,
|
|
3704
|
-
proxyTls: proxyConnect
|
|
3705
|
-
});
|
|
3706
|
-
}
|
|
3707
|
-
if (hasTls) return new undici.Agent({ connect: connectOpts });
|
|
3708
|
-
return void 0;
|
|
3709
|
-
}
|
|
3710
|
-
async function executeOne(req, collectionVars, envVars, globals, localVars, dispatcher, piiMaskPatterns, proxy, tls) {
|
|
3711
|
-
if (!req.headers) req.headers = [];
|
|
3712
|
-
if (!req.params) req.params = [];
|
|
3713
|
-
if (!req.body) req.body = { mode: "none" };
|
|
3714
|
-
if (!req.auth) req.auth = { type: "none" };
|
|
3715
|
-
const base = {
|
|
3716
|
-
requestId: req.id,
|
|
3717
|
-
name: req.name,
|
|
3718
|
-
method: req.method,
|
|
3719
|
-
resolvedUrl: "",
|
|
3720
|
-
status: "running"
|
|
3721
|
-
};
|
|
3722
|
-
const dynamicVars = await authBuilder.buildDynamicVars();
|
|
3723
|
-
let vars = authBuilder.mergeVars(envVars, collectionVars, globals, localVars, dynamicVars);
|
|
3724
|
-
let updatedEnvVars = { ...envVars };
|
|
3725
|
-
let updatedCollectionVars = { ...collectionVars };
|
|
3726
|
-
let updatedGlobals = { ...globals };
|
|
3727
|
-
let preScriptError;
|
|
3728
|
-
if (req.preRequestScript?.trim()) {
|
|
3729
|
-
const r = await requestCollection.runScript(authBuilder.interpolate(req.preRequestScript, vars), {
|
|
3730
|
-
envVars: { ...envVars },
|
|
3731
|
-
collectionVars: { ...collectionVars },
|
|
3732
|
-
globals: { ...globals },
|
|
3733
|
-
localVars: {},
|
|
3734
|
-
piiMaskPatterns
|
|
3735
|
-
});
|
|
3736
|
-
preScriptError = r.error;
|
|
3737
|
-
localVars = r.updatedLocalVars;
|
|
3738
|
-
updatedEnvVars = r.updatedEnvVars;
|
|
3739
|
-
updatedCollectionVars = r.updatedCollectionVars;
|
|
3740
|
-
updatedGlobals = r.updatedGlobals;
|
|
3741
|
-
requestCollection.patchGlobals(r.updatedGlobals);
|
|
3742
|
-
await requestCollection.persistGlobals();
|
|
3743
|
-
vars = authBuilder.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
|
|
3744
|
-
}
|
|
3745
|
-
const resolvedUrl = authBuilder.buildUrl(req.url, req.params, vars);
|
|
3746
|
-
base.resolvedUrl = resolvedUrl;
|
|
3747
|
-
const start = Date.now();
|
|
3748
|
-
try {
|
|
3749
|
-
if (req.auth.type === "oauth2") {
|
|
3750
|
-
const now = Date.now();
|
|
3751
|
-
const tokenMissing = !req.auth.oauth2CachedToken;
|
|
3752
|
-
const tokenExpired = req.auth.oauth2TokenExpiry ? req.auth.oauth2TokenExpiry <= now + 5e3 : true;
|
|
3753
|
-
if (tokenMissing || tokenExpired) {
|
|
3754
|
-
const result = await authBuilder.fetchOAuth2Token(req.auth, vars);
|
|
3755
|
-
req.auth.oauth2CachedToken = result.accessToken;
|
|
3756
|
-
req.auth.oauth2TokenExpiry = result.expiresAt;
|
|
3757
|
-
}
|
|
3758
|
-
}
|
|
3759
|
-
const authHeaders = await authBuilder.buildAuthHeaders(req.auth, vars);
|
|
3760
|
-
const headers = new undici.Headers();
|
|
3761
|
-
for (const h of req.headers) {
|
|
3762
|
-
if (h.enabled && h.key) headers.set(authBuilder.interpolate(h.key, vars), authBuilder.interpolate(h.value, vars));
|
|
3763
|
-
}
|
|
3764
|
-
for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
|
|
3765
|
-
let body;
|
|
3766
|
-
if (req.body.mode === "json" && req.body.json) {
|
|
3767
|
-
body = authBuilder.interpolate(req.body.json, vars);
|
|
3768
|
-
if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
|
|
3769
|
-
} else if (req.body.mode === "raw" && req.body.raw) {
|
|
3770
|
-
body = authBuilder.interpolate(req.body.raw, vars);
|
|
3771
|
-
if (!headers.has("content-type")) headers.set("Content-Type", req.body.rawContentType ?? "text/plain");
|
|
3772
|
-
} else if (req.body.mode === "graphql" && req.body.graphql) {
|
|
3773
|
-
const gql = req.body.graphql;
|
|
3774
|
-
const gqlBody = { query: authBuilder.interpolate(gql.query, vars) };
|
|
3775
|
-
const rawVars = gql.variables?.trim();
|
|
3776
|
-
if (rawVars) {
|
|
3777
|
-
try {
|
|
3778
|
-
gqlBody.variables = JSON.parse(authBuilder.interpolate(rawVars, vars));
|
|
3779
|
-
} catch {
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
3782
|
-
if (gql.operationName?.trim()) gqlBody.operationName = gql.operationName.trim();
|
|
3783
|
-
body = JSON.stringify(gqlBody);
|
|
3784
|
-
if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
|
|
3785
|
-
} else if (req.body.mode === "soap" && req.body.soap) {
|
|
3786
|
-
body = authBuilder.interpolate(req.body.soap.envelope, vars);
|
|
3787
|
-
if (!headers.has("content-type")) headers.set("Content-Type", "text/xml; charset=utf-8");
|
|
3788
|
-
if (req.body.soap.soapAction && !headers.has("soapaction")) {
|
|
3789
|
-
headers.set("SOAPAction", req.body.soap.soapAction);
|
|
3790
|
-
}
|
|
3791
|
-
}
|
|
3792
|
-
const methodHasBody = !["GET", "HEAD"].includes(req.method);
|
|
3793
|
-
const doFetch = (h) => undici.fetch(resolvedUrl, {
|
|
3794
|
-
method: req.method,
|
|
3795
|
-
headers: h,
|
|
3796
|
-
body: methodHasBody ? body : void 0,
|
|
3797
|
-
dispatcher
|
|
3798
|
-
});
|
|
3799
|
-
let fetchResp;
|
|
3800
|
-
if (req.auth.type === "ntlm") {
|
|
3801
|
-
const capturedHeaders = {};
|
|
3802
|
-
headers.forEach((v, k) => {
|
|
3803
|
-
capturedHeaders[k] = v;
|
|
3804
|
-
});
|
|
3805
|
-
fetchResp = await authBuilder.performNtlmRequest({
|
|
3806
|
-
url: resolvedUrl,
|
|
3807
|
-
method: req.method,
|
|
3808
|
-
auth: req.auth,
|
|
3809
|
-
vars,
|
|
3810
|
-
baseHeaders: capturedHeaders,
|
|
3811
|
-
body: methodHasBody ? body : void 0,
|
|
3812
|
-
tls,
|
|
3813
|
-
proxy
|
|
3814
|
-
});
|
|
3815
|
-
} else if (req.auth.type === "digest") {
|
|
3816
|
-
const probeFetch = (url, init) => undici.fetch(url, {
|
|
3817
|
-
...init,
|
|
3818
|
-
dispatcher
|
|
3819
|
-
});
|
|
3820
|
-
const digestHeader = await authBuilder.performDigestAuth(resolvedUrl, req.method, req.auth, vars, probeFetch);
|
|
3821
|
-
if (digestHeader) headers.set("Authorization", digestHeader);
|
|
3822
|
-
fetchResp = await doFetch(headers);
|
|
3823
|
-
} else {
|
|
3824
|
-
fetchResp = await doFetch(headers);
|
|
3825
|
-
}
|
|
3826
|
-
const responseBody = await fetchResp.text();
|
|
3827
|
-
const durationMs = Date.now() - start;
|
|
3828
|
-
const rawRespHeaders = {};
|
|
3829
|
-
fetchResp.headers.forEach((v, k) => {
|
|
3830
|
-
rawRespHeaders[k] = v;
|
|
3831
|
-
});
|
|
3832
|
-
const maskedBody = requestCollection.maskPii(responseBody, piiMaskPatterns);
|
|
3833
|
-
const maskedHeaders = requestCollection.maskHeaders(rawRespHeaders, piiMaskPatterns);
|
|
3834
|
-
const scriptResponse = {
|
|
3835
|
-
status: fetchResp.status,
|
|
3836
|
-
statusText: fetchResp.statusText,
|
|
3837
|
-
headers: rawRespHeaders,
|
|
3838
|
-
body: responseBody,
|
|
3839
|
-
bodySize: Buffer.byteLength(responseBody, "utf8"),
|
|
3840
|
-
durationMs
|
|
3841
|
-
};
|
|
3842
|
-
const schemaTestResults = requestCollection.buildSchemaTestResults(req.schema, responseBody);
|
|
3843
|
-
const protocolFaultTests = requestCollection.buildProtocolFaultTests(req.body.mode, responseBody);
|
|
3844
|
-
let testResults = [...schemaTestResults, ...protocolFaultTests];
|
|
3845
|
-
let consoleOutput = [];
|
|
3846
|
-
let postScriptError;
|
|
3847
|
-
if (req.postRequestScript?.trim()) {
|
|
3848
|
-
const r = await requestCollection.runScript(authBuilder.interpolate(req.postRequestScript, vars), {
|
|
3849
|
-
envVars: updatedEnvVars,
|
|
3850
|
-
collectionVars: updatedCollectionVars,
|
|
3851
|
-
globals: updatedGlobals,
|
|
3852
|
-
localVars,
|
|
3853
|
-
response: scriptResponse,
|
|
3854
|
-
piiMaskPatterns
|
|
3855
|
-
});
|
|
3856
|
-
testResults = [...schemaTestResults, ...protocolFaultTests, ...r.testResults];
|
|
3857
|
-
consoleOutput = r.consoleOutput;
|
|
3858
|
-
postScriptError = r.error;
|
|
3859
|
-
updatedEnvVars = r.updatedEnvVars;
|
|
3860
|
-
updatedCollectionVars = r.updatedCollectionVars;
|
|
3861
|
-
updatedGlobals = r.updatedGlobals;
|
|
3862
|
-
localVars = r.updatedLocalVars;
|
|
3863
|
-
requestCollection.patchGlobals(r.updatedGlobals);
|
|
3864
|
-
await requestCollection.persistGlobals();
|
|
3865
|
-
}
|
|
3866
|
-
const allPassed = testResults.every((t) => t.passed);
|
|
3867
|
-
const httpFailed = fetchResp.status >= 400;
|
|
3868
|
-
const hasTests = testResults.length > 0;
|
|
3869
|
-
const status = postScriptError ? "error" : hasTests ? allPassed ? "passed" : "failed" : httpFailed ? "failed" : "passed";
|
|
3870
|
-
if (httpFailed && testResults.length === 0) {
|
|
3871
|
-
testResults = [
|
|
3872
|
-
...testResults,
|
|
3873
|
-
{
|
|
3874
|
-
name: `HTTP status ${fetchResp.status} ${fetchResp.statusText}`.trim(),
|
|
3875
|
-
passed: false,
|
|
3876
|
-
error: `Request returned ${fetchResp.status} — no assertion was defined to verify the status code.`
|
|
3877
|
-
}
|
|
3878
|
-
];
|
|
3879
|
-
}
|
|
3880
|
-
const sentHeaders = {};
|
|
3881
|
-
headers.forEach((v, k) => {
|
|
3882
|
-
sentHeaders[k] = v;
|
|
3883
|
-
});
|
|
3884
|
-
return {
|
|
3885
|
-
result: {
|
|
3886
|
-
...base,
|
|
3887
|
-
status,
|
|
3888
|
-
httpStatus: fetchResp.status,
|
|
3889
|
-
durationMs,
|
|
3890
|
-
testResults,
|
|
3891
|
-
consoleOutput,
|
|
3892
|
-
preScriptError,
|
|
3893
|
-
postScriptError,
|
|
3894
|
-
sentRequest: { headers: sentHeaders, body: body ?? void 0 },
|
|
3895
|
-
receivedResponse: {
|
|
3896
|
-
status: fetchResp.status,
|
|
3897
|
-
statusText: fetchResp.statusText,
|
|
3898
|
-
headers: maskedHeaders,
|
|
3899
|
-
body: maskedBody
|
|
3900
|
-
}
|
|
3901
|
-
},
|
|
3902
|
-
updatedEnvVars,
|
|
3903
|
-
updatedCollectionVars,
|
|
3904
|
-
updatedGlobals,
|
|
3905
|
-
updatedLocalVars: localVars
|
|
3906
|
-
};
|
|
3907
|
-
} catch (err) {
|
|
3908
|
-
return {
|
|
3909
|
-
result: {
|
|
3910
|
-
...base,
|
|
3911
|
-
status: "error",
|
|
3912
|
-
durationMs: Date.now() - start,
|
|
3913
|
-
error: err instanceof Error ? err.cause instanceof Error ? `${err.message}: ${err.cause.message}` : err.message : String(err),
|
|
3914
|
-
preScriptError
|
|
3915
|
-
},
|
|
3916
|
-
updatedEnvVars,
|
|
3917
|
-
updatedCollectionVars,
|
|
3918
|
-
updatedGlobals,
|
|
3919
|
-
updatedLocalVars: localVars
|
|
3920
|
-
};
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
3981
|
const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3924
3982
|
function registerRunnerHandler(ipc) {
|
|
3925
|
-
ipc.
|
|
3983
|
+
handle.handleIpc(ipc, handle.IPC.runner.start, async (event, payload) => {
|
|
3926
3984
|
const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
|
|
3927
3985
|
const envVars = await authBuilder.buildEnvVars(environment);
|
|
3928
3986
|
const liveGlobals = requestCollection.getGlobals();
|
|
3929
3987
|
const globals = { ...payloadGlobals, ...liveGlobals };
|
|
3930
|
-
const dispatcher = await buildDispatcher(proxy, tls);
|
|
3988
|
+
const dispatcher = await requestCollection.buildDispatcher(proxy, tls);
|
|
3931
3989
|
const summary = { total: items.length, passed: 0, failed: 0, errors: 0, skipped: 0, durationMs: 0 };
|
|
3932
3990
|
const totalStart = Date.now();
|
|
3933
3991
|
let runEnvVars = { ...envVars };
|
|
3934
3992
|
let runCollectionVars = {};
|
|
3935
3993
|
let runGlobals = { ...globals };
|
|
3936
3994
|
let runLocalVars = {};
|
|
3937
|
-
const
|
|
3938
|
-
const skipRequests = /* @__PURE__ */ new Set();
|
|
3995
|
+
const skipTracker = new requestCollection.HookSkipTracker();
|
|
3939
3996
|
for (const item of items) {
|
|
3940
|
-
const
|
|
3941
|
-
let skipReason;
|
|
3942
|
-
if (isHook) {
|
|
3943
|
-
if (hookType === "beforeAll") {
|
|
3944
|
-
if ((scopeAncestors ?? []).some((id) => failedScopes.has(id))) {
|
|
3945
|
-
skipReason = "Skipped — outer scope hook failed";
|
|
3946
|
-
}
|
|
3947
|
-
} else if (hookType === "before") {
|
|
3948
|
-
const allScopes = [...scopeAncestors ?? [], scopeId].filter(Boolean);
|
|
3949
|
-
if (allScopes.some((id) => failedScopes.has(id))) {
|
|
3950
|
-
skipReason = "Skipped — scope hook failed";
|
|
3951
|
-
} else if (mainRequestId && skipRequests.has(mainRequestId)) {
|
|
3952
|
-
skipReason = "Skipped — before hook failed";
|
|
3953
|
-
}
|
|
3954
|
-
}
|
|
3955
|
-
} else {
|
|
3956
|
-
const allScopes = [...item.scopeAncestors ?? [], item.scopeId].filter(Boolean);
|
|
3957
|
-
if (allScopes.some((id) => failedScopes.has(id))) {
|
|
3958
|
-
skipReason = "Skipped — beforeAll hook failed";
|
|
3959
|
-
} else if (skipRequests.has(item.request.id)) {
|
|
3960
|
-
skipReason = "Skipped — before hook failed";
|
|
3961
|
-
}
|
|
3962
|
-
}
|
|
3997
|
+
const skipReason = skipTracker.shouldSkip(item);
|
|
3963
3998
|
if (skipReason) {
|
|
3964
3999
|
const skipped = {
|
|
3965
4000
|
requestId: item.request.id,
|
|
@@ -3975,7 +4010,7 @@ function registerRunnerHandler(ipc) {
|
|
|
3975
4010
|
iterationLabel: item.iterationLabel
|
|
3976
4011
|
};
|
|
3977
4012
|
summary.failed++;
|
|
3978
|
-
event.sender.send(
|
|
4013
|
+
event.sender.send(handle.IPC.runner.progress, skipped);
|
|
3979
4014
|
continue;
|
|
3980
4015
|
}
|
|
3981
4016
|
const runningUpdate = {
|
|
@@ -3986,35 +4021,28 @@ function registerRunnerHandler(ipc) {
|
|
|
3986
4021
|
scopeId: item.scopeId,
|
|
3987
4022
|
scopePath: item.scopePath
|
|
3988
4023
|
};
|
|
3989
|
-
event.sender.send(
|
|
3990
|
-
const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await
|
|
3991
|
-
item.request,
|
|
3992
|
-
{ ...item.collectionVars, ...runCollectionVars },
|
|
3993
|
-
runEnvVars,
|
|
3994
|
-
runGlobals,
|
|
3995
|
-
{ ...runLocalVars, ...item.dataRow ?? {} },
|
|
4024
|
+
event.sender.send(handle.IPC.runner.progress, { requestId: item.request.id, ...runningUpdate });
|
|
4025
|
+
const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestCollection.executeRunnerRequest({
|
|
4026
|
+
req: item.request,
|
|
4027
|
+
collectionVars: { ...item.collectionVars, ...runCollectionVars },
|
|
4028
|
+
envVars: runEnvVars,
|
|
4029
|
+
globals: runGlobals,
|
|
4030
|
+
localVars: { ...runLocalVars, ...item.dataRow ?? {} },
|
|
3996
4031
|
dispatcher,
|
|
3997
4032
|
piiMaskPatterns,
|
|
3998
4033
|
proxy,
|
|
3999
4034
|
tls
|
|
4000
|
-
);
|
|
4035
|
+
});
|
|
4001
4036
|
runEnvVars = updatedEnvVars;
|
|
4002
4037
|
runCollectionVars = updatedCollectionVars;
|
|
4003
4038
|
runGlobals = updatedGlobals;
|
|
4004
4039
|
runLocalVars = updatedLocalVars;
|
|
4005
|
-
|
|
4006
|
-
if (isHook && hookFailed) {
|
|
4007
|
-
if (hookType === "beforeAll" && scopeId) {
|
|
4008
|
-
failedScopes.add(scopeId);
|
|
4009
|
-
} else if (hookType === "before" && mainRequestId) {
|
|
4010
|
-
skipRequests.add(mainRequestId);
|
|
4011
|
-
}
|
|
4012
|
-
}
|
|
4040
|
+
skipTracker.recordResult(item, result.status);
|
|
4013
4041
|
if (result.status === "passed") summary.passed++;
|
|
4014
4042
|
else if (result.status === "failed") summary.failed++;
|
|
4015
4043
|
else if (result.status === "skipped") summary.skipped++;
|
|
4016
4044
|
else summary.errors++;
|
|
4017
|
-
event.sender.send(
|
|
4045
|
+
event.sender.send(handle.IPC.runner.progress, {
|
|
4018
4046
|
...result,
|
|
4019
4047
|
iterationLabel: item.iterationLabel,
|
|
4020
4048
|
isHook: item.isHook,
|
|
@@ -4031,26 +4059,26 @@ function registerRunnerHandler(ipc) {
|
|
|
4031
4059
|
});
|
|
4032
4060
|
}
|
|
4033
4061
|
function registerMockHandlers(ipc) {
|
|
4034
|
-
ipc.
|
|
4035
|
-
mockServer.setHitCallback((hit) => e.sender.send(
|
|
4062
|
+
handle.handleIpc(ipc, handle.IPC.mock.start, async (e, server) => {
|
|
4063
|
+
mockServer.setHitCallback((hit) => e.sender.send(handle.IPC.mock.hit, hit));
|
|
4036
4064
|
await mockServer.startMock(server);
|
|
4037
4065
|
});
|
|
4038
|
-
ipc.
|
|
4066
|
+
handle.handleIpc(ipc, handle.IPC.mock.stop, async (_e, id) => {
|
|
4039
4067
|
await mockServer.stopMock(id);
|
|
4040
4068
|
});
|
|
4041
|
-
ipc.
|
|
4042
|
-
ipc.
|
|
4069
|
+
handle.handleIpc(ipc, handle.IPC.mock.isRunning, (_e, id) => mockServer.isRunning(id));
|
|
4070
|
+
handle.handleIpc(ipc, handle.IPC.mock.updateRoutes, (_e, id, routes) => {
|
|
4043
4071
|
mockServer.updateMockRoutes(id, routes);
|
|
4044
4072
|
});
|
|
4045
|
-
ipc.
|
|
4046
|
-
ipc.
|
|
4073
|
+
handle.handleIpc(ipc, handle.IPC.mock.runningIds, () => mockServer.getRunningIds());
|
|
4074
|
+
handle.handleIpc(ipc, handle.IPC.file.saveMock, async (_e, relPath, server) => {
|
|
4047
4075
|
const wsDir = getWorkspaceDir();
|
|
4048
4076
|
if (!wsDir) throw new Error("No workspace open");
|
|
4049
4077
|
const fullPath = path.join(wsDir, relPath);
|
|
4050
4078
|
await promises.mkdir(path.dirname(fullPath), { recursive: true });
|
|
4051
4079
|
await promises.writeFile(fullPath, JSON.stringify(server, null, 2), "utf8");
|
|
4052
4080
|
});
|
|
4053
|
-
ipc.
|
|
4081
|
+
handle.handleIpc(ipc, handle.IPC.file.loadMock, async (_e, relPath) => {
|
|
4054
4082
|
const wsDir = getWorkspaceDir();
|
|
4055
4083
|
if (!wsDir) throw new Error("No workspace open");
|
|
4056
4084
|
const raw = await promises.readFile(path.join(wsDir, relPath), "utf8");
|
|
@@ -4058,7 +4086,7 @@ function registerMockHandlers(ipc) {
|
|
|
4058
4086
|
});
|
|
4059
4087
|
}
|
|
4060
4088
|
function registerOAuth2Handlers(ipc) {
|
|
4061
|
-
ipc.
|
|
4089
|
+
handle.handleIpc(ipc, handle.IPC.oauth2.startFlow, async (_e, auth, vars) => {
|
|
4062
4090
|
const port = auth.oauth2RedirectPort ?? 9876;
|
|
4063
4091
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
4064
4092
|
const authUrl = authBuilder.interpolate(auth.oauth2AuthUrl ?? "", vars);
|
|
@@ -4142,7 +4170,7 @@ function registerOAuth2Handlers(ipc) {
|
|
|
4142
4170
|
refreshToken: json["refresh_token"] ? String(json["refresh_token"]) : void 0
|
|
4143
4171
|
};
|
|
4144
4172
|
});
|
|
4145
|
-
ipc.
|
|
4173
|
+
handle.handleIpc(ipc, handle.IPC.oauth2.refreshToken, async (_e, auth, vars, refreshToken) => {
|
|
4146
4174
|
const tokenUrl = authBuilder.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
4147
4175
|
const clientId = authBuilder.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
4148
4176
|
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
@@ -4188,17 +4216,17 @@ function closeAllWsConnections() {
|
|
|
4188
4216
|
connections.clear();
|
|
4189
4217
|
}
|
|
4190
4218
|
function registerWsHandlers(ipc) {
|
|
4191
|
-
ipc.
|
|
4219
|
+
handle.handleIpc(ipc, handle.IPC.ws.connect, async (event, requestId, url, headers) => {
|
|
4192
4220
|
const existing = connections.get(requestId);
|
|
4193
4221
|
if (existing) {
|
|
4194
4222
|
existing.close();
|
|
4195
4223
|
connections.delete(requestId);
|
|
4196
4224
|
}
|
|
4197
|
-
event.sender.send(
|
|
4225
|
+
event.sender.send(handle.IPC.ws.status, { requestId, status: "connecting" });
|
|
4198
4226
|
const ws = new WebSocket(url, { headers });
|
|
4199
4227
|
connections.set(requestId, ws);
|
|
4200
4228
|
ws.on("open", () => {
|
|
4201
|
-
event.sender.send(
|
|
4229
|
+
event.sender.send(handle.IPC.ws.status, { requestId, status: "connected" });
|
|
4202
4230
|
});
|
|
4203
4231
|
ws.on("message", (data) => {
|
|
4204
4232
|
const message = {
|
|
@@ -4207,25 +4235,25 @@ function registerWsHandlers(ipc) {
|
|
|
4207
4235
|
data: data.toString(),
|
|
4208
4236
|
timestamp: Date.now()
|
|
4209
4237
|
};
|
|
4210
|
-
event.sender.send(
|
|
4238
|
+
event.sender.send(handle.IPC.ws.message, { requestId, message });
|
|
4211
4239
|
});
|
|
4212
4240
|
ws.on("error", (err) => {
|
|
4213
|
-
event.sender.send(
|
|
4241
|
+
event.sender.send(handle.IPC.ws.status, { requestId, status: "error", error: err.message });
|
|
4214
4242
|
connections.delete(requestId);
|
|
4215
4243
|
});
|
|
4216
4244
|
ws.on("close", () => {
|
|
4217
|
-
event.sender.send(
|
|
4245
|
+
event.sender.send(handle.IPC.ws.status, { requestId, status: "disconnected" });
|
|
4218
4246
|
connections.delete(requestId);
|
|
4219
4247
|
});
|
|
4220
4248
|
});
|
|
4221
|
-
ipc.
|
|
4249
|
+
handle.handleIpc(ipc, handle.IPC.ws.send, async (_event, requestId, data) => {
|
|
4222
4250
|
const ws = connections.get(requestId);
|
|
4223
4251
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
4224
4252
|
throw new Error("WebSocket is not connected");
|
|
4225
4253
|
}
|
|
4226
4254
|
ws.send(data);
|
|
4227
4255
|
});
|
|
4228
|
-
ipc.
|
|
4256
|
+
handle.handleIpc(ipc, handle.IPC.ws.disconnect, async (_event, requestId) => {
|
|
4229
4257
|
const ws = connections.get(requestId);
|
|
4230
4258
|
if (ws) {
|
|
4231
4259
|
ws.close();
|
|
@@ -4516,7 +4544,7 @@ ${body}
|
|
|
4516
4544
|
</html>`;
|
|
4517
4545
|
}
|
|
4518
4546
|
function registerDocsHandlers(ipc) {
|
|
4519
|
-
ipc.
|
|
4547
|
+
handle.handleIpc(ipc, handle.IPC.docs.generate, async (_event, payload) => {
|
|
4520
4548
|
if (payload.format === "html") {
|
|
4521
4549
|
return generateHtml(payload);
|
|
4522
4550
|
}
|
|
@@ -4564,9 +4592,9 @@ async function resolveSnapshotSpec(relPath) {
|
|
|
4564
4592
|
return { specPath: tmp };
|
|
4565
4593
|
}
|
|
4566
4594
|
function registerContractHandlers(ipc) {
|
|
4567
|
-
ipc.
|
|
4595
|
+
handle.handleIpc(ipc, handle.IPC.contract.run, async (_e, payload) => {
|
|
4568
4596
|
ipcValidate.validateContractRunPayload(payload);
|
|
4569
|
-
const { mode, requests, envVars, collectionVars = {}, requestBaseUrl } = payload;
|
|
4597
|
+
const { mode, requests, envVars, collectionVars = {}, requestBaseUrl, providerBaseUrl, stateHandlerUrl } = payload;
|
|
4570
4598
|
let { specUrl, specPath } = payload;
|
|
4571
4599
|
if (payload.specSnapshotRelPath) {
|
|
4572
4600
|
const resolved = await resolveSnapshotSpec(payload.specSnapshotRelPath);
|
|
@@ -4578,15 +4606,27 @@ function registerContractHandlers(ipc) {
|
|
|
4578
4606
|
return snapshots.runConsumerContracts(requests, envVars, collectionVars);
|
|
4579
4607
|
case "provider":
|
|
4580
4608
|
return snapshots.runProviderVerification(requests, envVars, specUrl, specPath, requestBaseUrl);
|
|
4609
|
+
case "provider-live":
|
|
4610
|
+
return snapshots.runLiveProviderVerification(requests, envVars, collectionVars, providerBaseUrl, stateHandlerUrl);
|
|
4581
4611
|
case "bidirectional":
|
|
4582
4612
|
return snapshots.runBidirectional(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
|
|
4583
4613
|
}
|
|
4584
4614
|
});
|
|
4585
|
-
ipc.
|
|
4615
|
+
handle.handleIpc(ipc, handle.IPC.contract.inferSchema, (_e, jsonBody) => {
|
|
4586
4616
|
const schema = inferSchemaFromJson(jsonBody);
|
|
4587
4617
|
return schema ? JSON.stringify(schema, null, 2) : null;
|
|
4588
4618
|
});
|
|
4589
|
-
ipc.
|
|
4619
|
+
handle.handleIpc(ipc, handle.IPC.contract.exportReportHtml, async (_e, report, meta = {}) => {
|
|
4620
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
4621
|
+
title: "Save contract report",
|
|
4622
|
+
defaultPath: `contract-report-${report.mode}.html`,
|
|
4623
|
+
filters: [{ name: "HTML", extensions: ["html"] }]
|
|
4624
|
+
});
|
|
4625
|
+
if (canceled || !filePath) return false;
|
|
4626
|
+
await promises.writeFile(filePath, snapshots.reportToHtml(report, { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...meta }), "utf8");
|
|
4627
|
+
return true;
|
|
4628
|
+
});
|
|
4629
|
+
handle.handleIpc(ipc, handle.IPC.contract.captureSnapshot, async (_e, opts) => {
|
|
4590
4630
|
const dir = getWorkspaceDir();
|
|
4591
4631
|
if (!dir) throw new Error("No workspace open — cannot capture snapshot.");
|
|
4592
4632
|
const snapshot = await snapshots.captureSnapshot(dir, opts);
|
|
@@ -4594,17 +4634,17 @@ function registerContractHandlers(ipc) {
|
|
|
4594
4634
|
if (!relPath) throw new Error("Snapshot created but relPath was not attached.");
|
|
4595
4635
|
return { relPath, snapshot };
|
|
4596
4636
|
});
|
|
4597
|
-
ipc.
|
|
4637
|
+
handle.handleIpc(ipc, handle.IPC.contract.listSnapshots, async (_e, registered = []) => {
|
|
4598
4638
|
const dir = getWorkspaceDir();
|
|
4599
4639
|
if (!dir) return [];
|
|
4600
4640
|
return snapshots.listSnapshots(dir, registered);
|
|
4601
4641
|
});
|
|
4602
|
-
ipc.
|
|
4642
|
+
handle.handleIpc(ipc, handle.IPC.contract.loadSnapshot, async (_e, relPath) => {
|
|
4603
4643
|
const dir = getWorkspaceDir();
|
|
4604
4644
|
if (!dir) throw new Error("No workspace open.");
|
|
4605
4645
|
return snapshots.loadSnapshot(dir, relPath);
|
|
4606
4646
|
});
|
|
4607
|
-
ipc.
|
|
4647
|
+
handle.handleIpc(ipc, handle.IPC.contract.deleteSnapshot, async (_e, relPath) => {
|
|
4608
4648
|
const dir = getWorkspaceDir();
|
|
4609
4649
|
if (!dir) return;
|
|
4610
4650
|
await snapshots.deleteSnapshot(dir, relPath);
|
|
@@ -4616,7 +4656,7 @@ function git() {
|
|
|
4616
4656
|
return simpleGit.simpleGit(dir);
|
|
4617
4657
|
}
|
|
4618
4658
|
function registerGitHandlers(ipc) {
|
|
4619
|
-
ipc.
|
|
4659
|
+
handle.handleIpc(ipc, handle.IPC.git.isRepo, async () => {
|
|
4620
4660
|
const dir = getWorkspaceDir();
|
|
4621
4661
|
if (!dir) return false;
|
|
4622
4662
|
try {
|
|
@@ -4626,12 +4666,12 @@ function registerGitHandlers(ipc) {
|
|
|
4626
4666
|
return false;
|
|
4627
4667
|
}
|
|
4628
4668
|
});
|
|
4629
|
-
ipc.
|
|
4669
|
+
handle.handleIpc(ipc, handle.IPC.git.init, async () => {
|
|
4630
4670
|
await git().init();
|
|
4631
4671
|
const dir = getWorkspaceDir();
|
|
4632
4672
|
if (dir) await ensureGitignore(dir);
|
|
4633
4673
|
});
|
|
4634
|
-
ipc.
|
|
4674
|
+
handle.handleIpc(ipc, handle.IPC.git.status, async () => {
|
|
4635
4675
|
const result = await git().status();
|
|
4636
4676
|
return {
|
|
4637
4677
|
staged: result.staged.map((f) => ({ path: f, status: resolveStatus(result, f, true) })),
|
|
@@ -4644,40 +4684,40 @@ function registerGitHandlers(ipc) {
|
|
|
4644
4684
|
remote: result.tracking ?? null
|
|
4645
4685
|
};
|
|
4646
4686
|
});
|
|
4647
|
-
ipc.
|
|
4687
|
+
handle.handleIpc(ipc, handle.IPC.git.resolveOurs, async (_e, filePath) => {
|
|
4648
4688
|
const g = git();
|
|
4649
4689
|
await g.checkout(["--ours", "--", filePath]);
|
|
4650
4690
|
await g.add([filePath]);
|
|
4651
4691
|
});
|
|
4652
|
-
ipc.
|
|
4692
|
+
handle.handleIpc(ipc, handle.IPC.git.resolveTheirs, async (_e, filePath) => {
|
|
4653
4693
|
const g = git();
|
|
4654
4694
|
await g.checkout(["--theirs", "--", filePath]);
|
|
4655
4695
|
await g.add([filePath]);
|
|
4656
4696
|
});
|
|
4657
|
-
ipc.
|
|
4697
|
+
handle.handleIpc(ipc, handle.IPC.git.markResolved, async (_e, filePath) => {
|
|
4658
4698
|
await git().add([filePath]);
|
|
4659
4699
|
});
|
|
4660
|
-
ipc.
|
|
4700
|
+
handle.handleIpc(ipc, handle.IPC.git.diff, async (_e, filePath) => {
|
|
4661
4701
|
if (filePath) return git().diff(["--", filePath]);
|
|
4662
4702
|
return git().diff();
|
|
4663
4703
|
});
|
|
4664
|
-
ipc.
|
|
4704
|
+
handle.handleIpc(ipc, handle.IPC.git.diffStaged, async (_e, filePath) => {
|
|
4665
4705
|
if (filePath) return git().diff(["--cached", "--", filePath]);
|
|
4666
4706
|
return git().diff(["--cached"]);
|
|
4667
4707
|
});
|
|
4668
|
-
ipc.
|
|
4708
|
+
handle.handleIpc(ipc, handle.IPC.git.stage, async (_e, paths) => {
|
|
4669
4709
|
await git().add(paths);
|
|
4670
4710
|
});
|
|
4671
|
-
ipc.
|
|
4711
|
+
handle.handleIpc(ipc, handle.IPC.git.unstage, async (_e, paths) => {
|
|
4672
4712
|
await git().reset(["HEAD", "--", ...paths]);
|
|
4673
4713
|
});
|
|
4674
|
-
ipc.
|
|
4714
|
+
handle.handleIpc(ipc, handle.IPC.git.stageAll, async () => {
|
|
4675
4715
|
await git().add(["."]);
|
|
4676
4716
|
});
|
|
4677
|
-
ipc.
|
|
4717
|
+
handle.handleIpc(ipc, handle.IPC.git.commit, async (_e, message) => {
|
|
4678
4718
|
await git().commit(message);
|
|
4679
4719
|
});
|
|
4680
|
-
ipc.
|
|
4720
|
+
handle.handleIpc(ipc, handle.IPC.git.log, async (_e, limit = 50) => {
|
|
4681
4721
|
const result = await git().log({ maxCount: limit });
|
|
4682
4722
|
return result.all.map((c) => ({
|
|
4683
4723
|
hash: c.hash,
|
|
@@ -4688,7 +4728,7 @@ function registerGitHandlers(ipc) {
|
|
|
4688
4728
|
date: c.date
|
|
4689
4729
|
}));
|
|
4690
4730
|
});
|
|
4691
|
-
ipc.
|
|
4731
|
+
handle.handleIpc(ipc, handle.IPC.git.branches, async () => {
|
|
4692
4732
|
const raw = await git().raw([
|
|
4693
4733
|
"for-each-ref",
|
|
4694
4734
|
"--format=%(refname:short)|%(HEAD)|%(upstream:short)|%(upstream:track)",
|
|
@@ -4720,7 +4760,7 @@ function registerGitHandlers(ipc) {
|
|
|
4720
4760
|
}
|
|
4721
4761
|
return branches;
|
|
4722
4762
|
});
|
|
4723
|
-
ipc.
|
|
4763
|
+
handle.handleIpc(ipc, handle.IPC.git.checkout, async (_e, branch, create) => {
|
|
4724
4764
|
if (create) {
|
|
4725
4765
|
await git().checkoutLocalBranch(branch);
|
|
4726
4766
|
return;
|
|
@@ -4739,13 +4779,13 @@ function registerGitHandlers(ipc) {
|
|
|
4739
4779
|
}
|
|
4740
4780
|
await git().checkout(branch);
|
|
4741
4781
|
});
|
|
4742
|
-
ipc.
|
|
4782
|
+
handle.handleIpc(ipc, handle.IPC.git.deleteBranch, async (_e, name, force = false) => {
|
|
4743
4783
|
await git().deleteLocalBranch(name, force);
|
|
4744
4784
|
});
|
|
4745
|
-
ipc.
|
|
4785
|
+
handle.handleIpc(ipc, handle.IPC.git.pull, async () => {
|
|
4746
4786
|
await git().pull();
|
|
4747
4787
|
});
|
|
4748
|
-
ipc.
|
|
4788
|
+
handle.handleIpc(ipc, handle.IPC.git.push, async (_e, setUpstream) => {
|
|
4749
4789
|
if (setUpstream) {
|
|
4750
4790
|
const status = await git().status();
|
|
4751
4791
|
await git().push(["--set-upstream", "origin", status.current ?? "main"]);
|
|
@@ -4753,20 +4793,20 @@ function registerGitHandlers(ipc) {
|
|
|
4753
4793
|
await git().push();
|
|
4754
4794
|
}
|
|
4755
4795
|
});
|
|
4756
|
-
ipc.
|
|
4796
|
+
handle.handleIpc(ipc, handle.IPC.git.remotes, async () => {
|
|
4757
4797
|
const result = await git().getRemotes(true);
|
|
4758
4798
|
return result.map((r) => ({ name: r.name, url: r.refs.fetch || r.refs.push || "" }));
|
|
4759
4799
|
});
|
|
4760
|
-
ipc.
|
|
4800
|
+
handle.handleIpc(ipc, handle.IPC.git.addRemote, async (_e, name, url) => {
|
|
4761
4801
|
await git().addRemote(name, url);
|
|
4762
4802
|
});
|
|
4763
|
-
ipc.
|
|
4803
|
+
handle.handleIpc(ipc, handle.IPC.git.setRemoteUrl, async (_e, name, url) => {
|
|
4764
4804
|
await git().remote(["set-url", name, url]);
|
|
4765
4805
|
});
|
|
4766
|
-
ipc.
|
|
4806
|
+
handle.handleIpc(ipc, handle.IPC.git.removeRemote, async (_e, name) => {
|
|
4767
4807
|
await git().removeRemote(name);
|
|
4768
4808
|
});
|
|
4769
|
-
ipc.
|
|
4809
|
+
handle.handleIpc(ipc, handle.IPC.git.writeCiFile, async (_e, relPath, content) => {
|
|
4770
4810
|
const wsDir = getWorkspaceDir();
|
|
4771
4811
|
if (!wsDir) throw new Error("No workspace open");
|
|
4772
4812
|
const fullPath = path.join(wsDir, relPath);
|
|
@@ -4785,20 +4825,20 @@ function resolveStatus(result, filePath, staged) {
|
|
|
4785
4825
|
return "modified";
|
|
4786
4826
|
}
|
|
4787
4827
|
function registerRecordHandlers(ipc, getWebContents) {
|
|
4788
|
-
ipc.
|
|
4828
|
+
handle.handleIpc(ipc, handle.IPC.record.start, async (_e, config) => {
|
|
4789
4829
|
await recorder.startRecorder(config);
|
|
4790
4830
|
recorder.setRecorderHitCallback((entry) => {
|
|
4791
|
-
getWebContents()?.send(
|
|
4831
|
+
getWebContents()?.send(handle.IPC.record.hit, entry);
|
|
4792
4832
|
});
|
|
4793
4833
|
});
|
|
4794
|
-
ipc.
|
|
4834
|
+
handle.handleIpc(ipc, handle.IPC.record.stop, async () => {
|
|
4795
4835
|
const session = recorder.stopRecorder();
|
|
4796
4836
|
recorder.setRecorderHitCallback(null);
|
|
4797
4837
|
return session;
|
|
4798
4838
|
});
|
|
4799
|
-
ipc.
|
|
4800
|
-
ipc.
|
|
4801
|
-
ipc.
|
|
4839
|
+
handle.handleIpc(ipc, handle.IPC.record.isRunning, () => recorder.isRecorderRunning());
|
|
4840
|
+
handle.handleIpc(ipc, handle.IPC.record.entries, () => recorder.getRecorderEntries());
|
|
4841
|
+
handle.handleIpc(ipc, handle.IPC.record.toMock, (_e, entries, upstream, name, port) => {
|
|
4802
4842
|
return recorder.entriesToMockServer(entries, upstream, name, port);
|
|
4803
4843
|
});
|
|
4804
4844
|
}
|
|
@@ -4892,7 +4932,7 @@ electron.app.whenReady().then(async () => {
|
|
|
4892
4932
|
if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
|
|
4893
4933
|
await authBuilder.initSecretStore(electron.app.getPath("userData"));
|
|
4894
4934
|
registerFileHandlers(electron.ipcMain);
|
|
4895
|
-
|
|
4935
|
+
registerRequestHandler(electron.ipcMain);
|
|
4896
4936
|
authBuilder.registerSecretHandlers(electron.ipcMain);
|
|
4897
4937
|
registerImportHandlers(electron.ipcMain);
|
|
4898
4938
|
registerGenerateHandlers(electron.ipcMain);
|
|
@@ -4905,7 +4945,7 @@ electron.app.whenReady().then(async () => {
|
|
|
4905
4945
|
registerContractHandlers(electron.ipcMain);
|
|
4906
4946
|
registerGitHandlers(electron.ipcMain);
|
|
4907
4947
|
registerRecordHandlers(electron.ipcMain, () => electron.BrowserWindow.getAllWindows()[0]?.webContents ?? null);
|
|
4908
|
-
electron.ipcMain.
|
|
4948
|
+
handle.handleIpc(electron.ipcMain, handle.IPC.shell.openExternal, (_e, url) => electron.shell.openExternal(url));
|
|
4909
4949
|
createWindow();
|
|
4910
4950
|
electron.app.on("activate", () => {
|
|
4911
4951
|
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
|