@testsmith/api-spector 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/main/chunks/mock-server-C0RlwZf_.js +240 -0
- package/out/main/chunks/request-handler-9MdHOWVf.js +1041 -0
- package/out/main/index.js +54 -519
- package/out/main/mock.js +3 -1
- package/out/main/runner.js +28 -23
- package/out/preload/index.js +4 -0
- package/out/renderer/assets/{index-x-2wMDPZ.js → index-C67sPzTG.js} +5986 -5315
- package/out/renderer/assets/index-YkI5DFME.css +2 -0
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/out/main/chunks/mock-server-ZB7zpXh9.js +0 -102
- package/out/main/chunks/script-runner-CmdLWmKN.js +0 -511
- package/out/renderer/assets/index-DRGkbz0a.css +0 -2
package/out/main/index.js
CHANGED
|
@@ -25,19 +25,19 @@ const electron = require("electron");
|
|
|
25
25
|
const path = require("path");
|
|
26
26
|
const fs = require("fs");
|
|
27
27
|
const promises = require("fs/promises");
|
|
28
|
-
const
|
|
29
|
-
const undici = require("undici");
|
|
30
|
-
const crypto = require("crypto");
|
|
28
|
+
const requestHandler = require("./chunks/request-handler-9MdHOWVf.js");
|
|
31
29
|
const uuid = require("uuid");
|
|
32
30
|
const jsYaml = require("js-yaml");
|
|
31
|
+
const undici = require("undici");
|
|
33
32
|
const JSZip = require("jszip");
|
|
34
|
-
const mockServer = require("./chunks/mock-server-
|
|
33
|
+
const mockServer = require("./chunks/mock-server-C0RlwZf_.js");
|
|
35
34
|
const http = require("http");
|
|
36
35
|
const WebSocket = require("ws");
|
|
37
36
|
const https = require("https");
|
|
38
37
|
const Ajv = require("ajv");
|
|
39
|
-
require("
|
|
38
|
+
require("crypto");
|
|
40
39
|
require("dayjs");
|
|
40
|
+
require("vm");
|
|
41
41
|
require("tv4");
|
|
42
42
|
require("jsonpath-plus");
|
|
43
43
|
require("@xmldom/xmldom");
|
|
@@ -69,7 +69,7 @@ function registerFileHandlers(ipc) {
|
|
|
69
69
|
const wsPath = result.filePaths[0];
|
|
70
70
|
workspaceDir = path.dirname(wsPath);
|
|
71
71
|
workspaceFile = wsPath;
|
|
72
|
-
await
|
|
72
|
+
await requestHandler.loadGlobals(workspaceDir);
|
|
73
73
|
await saveLastWorkspacePath(wsPath);
|
|
74
74
|
const raw = await promises.readFile(wsPath, "utf8");
|
|
75
75
|
return { workspace: JSON.parse(raw), workspacePath: wsPath };
|
|
@@ -83,7 +83,7 @@ function registerFileHandlers(ipc) {
|
|
|
83
83
|
if (result.canceled || !result.filePath) return null;
|
|
84
84
|
workspaceDir = path.dirname(result.filePath);
|
|
85
85
|
workspaceFile = result.filePath;
|
|
86
|
-
await
|
|
86
|
+
await requestHandler.loadGlobals(workspaceDir);
|
|
87
87
|
await promises.mkdir(path.join(workspaceDir, "collections"), { recursive: true });
|
|
88
88
|
await promises.mkdir(path.join(workspaceDir, "environments"), { recursive: true });
|
|
89
89
|
const gitignore = "# api Spector — never commit secrets\n*.secrets\n.env.local\n";
|
|
@@ -147,10 +147,10 @@ function registerFileHandlers(ipc) {
|
|
|
147
147
|
await promises.writeFile(result.filePath, content, "utf8");
|
|
148
148
|
return true;
|
|
149
149
|
});
|
|
150
|
-
ipc.handle("globals:get", () =>
|
|
150
|
+
ipc.handle("globals:get", () => requestHandler.getGlobals());
|
|
151
151
|
ipc.handle("globals:set", async (_e, patch) => {
|
|
152
|
-
|
|
153
|
-
await
|
|
152
|
+
requestHandler.setGlobals(patch);
|
|
153
|
+
await requestHandler.persistGlobals();
|
|
154
154
|
});
|
|
155
155
|
ipc.handle("file:closeWorkspace", async () => {
|
|
156
156
|
workspaceDir = null;
|
|
@@ -164,7 +164,7 @@ function registerFileHandlers(ipc) {
|
|
|
164
164
|
try {
|
|
165
165
|
workspaceDir = path.dirname(wsPath);
|
|
166
166
|
workspaceFile = wsPath;
|
|
167
|
-
await
|
|
167
|
+
await requestHandler.loadGlobals(workspaceDir);
|
|
168
168
|
const raw = await promises.readFile(wsPath, "utf8");
|
|
169
169
|
return { workspace: JSON.parse(raw), workspacePath: wsPath };
|
|
170
170
|
} catch {
|
|
@@ -175,471 +175,6 @@ function registerFileHandlers(ipc) {
|
|
|
175
175
|
function getWorkspaceDir() {
|
|
176
176
|
return workspaceDir;
|
|
177
177
|
}
|
|
178
|
-
async function buildAuthHeaders(auth, vars) {
|
|
179
|
-
const headers = {};
|
|
180
|
-
if (auth.type === "bearer") {
|
|
181
|
-
let token = auth.token ?? "";
|
|
182
|
-
if (!token && auth.tokenSecretRef) token = await scriptRunner.getSecret(auth.tokenSecretRef) ?? "";
|
|
183
|
-
token = scriptRunner.interpolate(token, vars);
|
|
184
|
-
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
185
|
-
}
|
|
186
|
-
if (auth.type === "basic") {
|
|
187
|
-
let password = auth.password ?? "";
|
|
188
|
-
if (!password && auth.passwordSecretRef) password = await scriptRunner.getSecret(auth.passwordSecretRef) ?? "";
|
|
189
|
-
password = scriptRunner.interpolate(password, vars);
|
|
190
|
-
const username = scriptRunner.interpolate(auth.username ?? "", vars);
|
|
191
|
-
headers["Authorization"] = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
|
192
|
-
}
|
|
193
|
-
if (auth.type === "apikey" && auth.apiKeyIn === "header") {
|
|
194
|
-
let value = auth.apiKeyValue ?? "";
|
|
195
|
-
if (!value && auth.apiKeySecretRef) value = await scriptRunner.getSecret(auth.apiKeySecretRef) ?? "";
|
|
196
|
-
value = scriptRunner.interpolate(value, vars);
|
|
197
|
-
headers[auth.apiKeyName ?? "X-API-Key"] = value;
|
|
198
|
-
}
|
|
199
|
-
if (auth.type === "oauth2") {
|
|
200
|
-
const now = Date.now();
|
|
201
|
-
if (auth.oauth2CachedToken && auth.oauth2TokenExpiry && auth.oauth2TokenExpiry > now + 5e3) {
|
|
202
|
-
headers["Authorization"] = `Bearer ${auth.oauth2CachedToken}`;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return headers;
|
|
206
|
-
}
|
|
207
|
-
async function buildApiKeyParam(auth, vars) {
|
|
208
|
-
if (auth.type !== "apikey" || auth.apiKeyIn !== "query") return null;
|
|
209
|
-
let value = auth.apiKeyValue ?? "";
|
|
210
|
-
if (!value && auth.apiKeySecretRef) value = await scriptRunner.getSecret(auth.apiKeySecretRef) ?? "";
|
|
211
|
-
value = scriptRunner.interpolate(value, vars);
|
|
212
|
-
return { key: auth.apiKeyName ?? "apikey", value };
|
|
213
|
-
}
|
|
214
|
-
function parseDigestChallenge(wwwAuth) {
|
|
215
|
-
const extract = (key) => {
|
|
216
|
-
const m = new RegExp(`${key}="([^"]*)"`, "i").exec(wwwAuth);
|
|
217
|
-
return m ? m[1] : "";
|
|
218
|
-
};
|
|
219
|
-
const extractUnquoted = (key) => {
|
|
220
|
-
const m = new RegExp(`${key}=([^,\\s]+)`, "i").exec(wwwAuth);
|
|
221
|
-
return m ? m[1] : "";
|
|
222
|
-
};
|
|
223
|
-
return {
|
|
224
|
-
realm: extract("realm"),
|
|
225
|
-
nonce: extract("nonce"),
|
|
226
|
-
qop: extract("qop") || extractUnquoted("qop") || void 0,
|
|
227
|
-
algorithm: extract("algorithm") || extractUnquoted("algorithm") || "MD5",
|
|
228
|
-
opaque: extract("opaque") || void 0
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function md5(s) {
|
|
232
|
-
return crypto.createHash("md5").update(s).digest("hex");
|
|
233
|
-
}
|
|
234
|
-
function buildDigestAuthHeader(challenge, username, password, method, uri) {
|
|
235
|
-
const { realm, nonce, qop, algorithm, opaque } = challenge;
|
|
236
|
-
const algo = (algorithm ?? "MD5").toUpperCase();
|
|
237
|
-
const ha1 = algo === "MD5-SESS" ? md5(`${md5(`${username}:${realm}:${password}`)}:${nonce}:`) : md5(`${username}:${realm}:${password}`);
|
|
238
|
-
const ha2 = md5(`${method}:${uri}`);
|
|
239
|
-
let response;
|
|
240
|
-
let nc;
|
|
241
|
-
let cnonce;
|
|
242
|
-
if (qop === "auth" || qop === "auth-int") {
|
|
243
|
-
nc = "00000001";
|
|
244
|
-
cnonce = crypto.randomBytes(8).toString("hex");
|
|
245
|
-
response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`);
|
|
246
|
-
} else {
|
|
247
|
-
response = md5(`${ha1}:${nonce}:${ha2}`);
|
|
248
|
-
}
|
|
249
|
-
let header = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}", response="${response}"`;
|
|
250
|
-
if (qop) header += `, qop=${qop}`;
|
|
251
|
-
if (nc) header += `, nc=${nc}`;
|
|
252
|
-
if (cnonce) header += `, cnonce="${cnonce}"`;
|
|
253
|
-
if (opaque) header += `, opaque="${opaque}"`;
|
|
254
|
-
if (algo !== "MD5") header += `, algorithm=${algo}`;
|
|
255
|
-
return header;
|
|
256
|
-
}
|
|
257
|
-
async function performDigestAuth(url, method, auth, vars, fetchFn) {
|
|
258
|
-
const probeResp = await fetchFn(url, { method, headers: {} });
|
|
259
|
-
if (probeResp.status !== 401) return null;
|
|
260
|
-
const wwwAuth = probeResp.headers.get("www-authenticate") ?? "";
|
|
261
|
-
if (!wwwAuth.toLowerCase().startsWith("digest")) return null;
|
|
262
|
-
const challenge = parseDigestChallenge(wwwAuth);
|
|
263
|
-
let password = auth.password ?? "";
|
|
264
|
-
if (!password && auth.passwordSecretRef) password = await scriptRunner.getSecret(auth.passwordSecretRef) ?? "";
|
|
265
|
-
password = scriptRunner.interpolate(password, vars);
|
|
266
|
-
const username = scriptRunner.interpolate(auth.username ?? "", vars);
|
|
267
|
-
let uri = "/";
|
|
268
|
-
try {
|
|
269
|
-
uri = new URL(url).pathname + (new URL(url).search ?? "");
|
|
270
|
-
} catch {
|
|
271
|
-
}
|
|
272
|
-
return buildDigestAuthHeader(challenge, username, password, method, uri);
|
|
273
|
-
}
|
|
274
|
-
async function performNtlmRequest(_url, _method, _auth, _vars) {
|
|
275
|
-
throw new Error(
|
|
276
|
-
'NTLM auth is not yet implemented. Add "httpntlm" to package.json dependencies and implement performNtlmRequest in auth-builder.ts.'
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
async function fetchOAuth2Token(auth, vars) {
|
|
280
|
-
const flow = auth.oauth2Flow ?? "client_credentials";
|
|
281
|
-
if (flow === "authorization_code") {
|
|
282
|
-
throw new Error("authorization_code flow requires the oauth2:startFlow IPC call from the renderer.");
|
|
283
|
-
}
|
|
284
|
-
if (flow === "implicit") {
|
|
285
|
-
throw new Error("implicit flow cannot be performed server-side — tokens must be obtained via the browser redirect.");
|
|
286
|
-
}
|
|
287
|
-
const tokenUrl = scriptRunner.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
288
|
-
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required.");
|
|
289
|
-
const clientId = scriptRunner.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
290
|
-
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
291
|
-
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
292
|
-
clientSecret = await scriptRunner.getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
293
|
-
}
|
|
294
|
-
clientSecret = scriptRunner.interpolate(clientSecret, vars);
|
|
295
|
-
const params = new URLSearchParams();
|
|
296
|
-
params.set("grant_type", flow === "password" ? "password" : "client_credentials");
|
|
297
|
-
params.set("client_id", clientId);
|
|
298
|
-
params.set("client_secret", clientSecret);
|
|
299
|
-
if (auth.oauth2Scopes) params.set("scope", auth.oauth2Scopes);
|
|
300
|
-
if (flow === "password") {
|
|
301
|
-
let password = auth.password ?? "";
|
|
302
|
-
if (!password && auth.passwordSecretRef) password = await scriptRunner.getSecret(auth.passwordSecretRef) ?? "";
|
|
303
|
-
password = scriptRunner.interpolate(password, vars);
|
|
304
|
-
params.set("username", scriptRunner.interpolate(auth.username ?? "", vars));
|
|
305
|
-
params.set("password", password);
|
|
306
|
-
}
|
|
307
|
-
const { fetch: nodeFetch } = await import("undici");
|
|
308
|
-
const resp = await nodeFetch(tokenUrl, {
|
|
309
|
-
method: "POST",
|
|
310
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
311
|
-
body: params.toString()
|
|
312
|
-
});
|
|
313
|
-
if (!resp.ok) {
|
|
314
|
-
const body = await resp.text();
|
|
315
|
-
throw new Error(`OAuth 2.0 token request failed (${resp.status}): ${body}`);
|
|
316
|
-
}
|
|
317
|
-
const json = await resp.json();
|
|
318
|
-
const accessToken = String(json["access_token"] ?? "");
|
|
319
|
-
if (!accessToken) throw new Error("OAuth 2.0: token response missing access_token.");
|
|
320
|
-
const expiresIn = Number(json["expires_in"] ?? 3600);
|
|
321
|
-
const expiresAt = Date.now() + expiresIn * 1e3;
|
|
322
|
-
return {
|
|
323
|
-
accessToken,
|
|
324
|
-
expiresAt,
|
|
325
|
-
refreshToken: json["refresh_token"] ? String(json["refresh_token"]) : void 0
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
function maskPii(data, patterns) {
|
|
329
|
-
if (!patterns.length) return data;
|
|
330
|
-
try {
|
|
331
|
-
const obj = JSON.parse(data);
|
|
332
|
-
const masked = maskObject(obj, patterns);
|
|
333
|
-
return JSON.stringify(masked);
|
|
334
|
-
} catch {
|
|
335
|
-
return data;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
function maskObject(obj, patterns) {
|
|
339
|
-
if (Array.isArray(obj)) return obj.map((item) => maskObject(item, patterns));
|
|
340
|
-
if (obj && typeof obj === "object") {
|
|
341
|
-
const result = {};
|
|
342
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
343
|
-
if (patterns.some((p) => k.toLowerCase().includes(p.toLowerCase()))) {
|
|
344
|
-
result[k] = "[REDACTED]";
|
|
345
|
-
} else {
|
|
346
|
-
result[k] = maskObject(v, patterns);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return result;
|
|
350
|
-
}
|
|
351
|
-
return obj;
|
|
352
|
-
}
|
|
353
|
-
function maskHeaders(headers, patterns) {
|
|
354
|
-
if (!patterns.length) return headers;
|
|
355
|
-
const alwaysMask = ["authorization", "cookie", "set-cookie"];
|
|
356
|
-
const result = {};
|
|
357
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
358
|
-
const lower = k.toLowerCase();
|
|
359
|
-
if (alwaysMask.includes(lower) || patterns.some((p) => lower.includes(p.toLowerCase()))) {
|
|
360
|
-
result[k] = "[REDACTED]";
|
|
361
|
-
} else {
|
|
362
|
-
result[k] = v;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return result;
|
|
366
|
-
}
|
|
367
|
-
async function buildDispatcher$1(proxy, tls) {
|
|
368
|
-
const connectOpts = {};
|
|
369
|
-
let hasTls = false;
|
|
370
|
-
if (tls) {
|
|
371
|
-
hasTls = true;
|
|
372
|
-
if (tls.rejectUnauthorized !== void 0) {
|
|
373
|
-
connectOpts["rejectUnauthorized"] = tls.rejectUnauthorized;
|
|
374
|
-
}
|
|
375
|
-
if (tls.caCertPath) {
|
|
376
|
-
try {
|
|
377
|
-
connectOpts["ca"] = await promises.readFile(tls.caCertPath);
|
|
378
|
-
} catch {
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
if (tls.clientCertPath) {
|
|
382
|
-
try {
|
|
383
|
-
connectOpts["cert"] = await promises.readFile(tls.clientCertPath);
|
|
384
|
-
} catch {
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
if (tls.clientKeyPath) {
|
|
388
|
-
try {
|
|
389
|
-
connectOpts["key"] = await promises.readFile(tls.clientKeyPath);
|
|
390
|
-
} catch {
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
if (proxy?.url) {
|
|
395
|
-
const proxyUri = proxy.auth ? proxy.url.replace("://", `://${encodeURIComponent(proxy.auth.username)}:${encodeURIComponent(proxy.auth.password)}@`) : proxy.url;
|
|
396
|
-
return new undici.ProxyAgent({
|
|
397
|
-
uri: proxyUri,
|
|
398
|
-
...hasTls ? { connect: connectOpts } : {}
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
if (hasTls) {
|
|
402
|
-
return new undici.Agent({ connect: connectOpts });
|
|
403
|
-
}
|
|
404
|
-
return void 0;
|
|
405
|
-
}
|
|
406
|
-
function registerRequestHandler(ipc) {
|
|
407
|
-
ipc.handle("request:send", async (_e, payload) => {
|
|
408
|
-
const {
|
|
409
|
-
request: req,
|
|
410
|
-
environment,
|
|
411
|
-
collectionVars,
|
|
412
|
-
globals: payloadGlobals,
|
|
413
|
-
proxy,
|
|
414
|
-
tls,
|
|
415
|
-
piiMaskPatterns = []
|
|
416
|
-
} = payload;
|
|
417
|
-
const start = Date.now();
|
|
418
|
-
const liveGlobals = scriptRunner.getGlobals();
|
|
419
|
-
const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
|
|
420
|
-
const envVars = await scriptRunner.buildEnvVars(environment);
|
|
421
|
-
let localVars = {};
|
|
422
|
-
const decryptionWarnings = [];
|
|
423
|
-
if (environment) {
|
|
424
|
-
const masterKeySet = Boolean(process.env["API_SPECTOR_MASTER_KEY"]);
|
|
425
|
-
for (const v of environment.variables) {
|
|
426
|
-
if (!v.enabled || !v.secret || !v.secretEncrypted) continue;
|
|
427
|
-
if (!masterKeySet) {
|
|
428
|
-
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.`);
|
|
429
|
-
} else if (envVars[v.key] === void 0) {
|
|
430
|
-
decryptionWarnings.push(`[warn] Secret "${v.key}" could not be decrypted: wrong password or corrupted data.`);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
let vars = scriptRunner.mergeVars(envVars, collectionVars, mergedGlobals, localVars);
|
|
435
|
-
let preScriptMeta = { consoleOutput: [] };
|
|
436
|
-
let updatedCollectionVars = { ...collectionVars };
|
|
437
|
-
let updatedEnvVars = { ...envVars };
|
|
438
|
-
let updatedGlobals = { ...mergedGlobals };
|
|
439
|
-
if (req.preRequestScript?.trim()) {
|
|
440
|
-
const result = await scriptRunner.runScript(req.preRequestScript, {
|
|
441
|
-
envVars: { ...envVars },
|
|
442
|
-
collectionVars: { ...collectionVars },
|
|
443
|
-
globals: { ...mergedGlobals },
|
|
444
|
-
localVars: {}
|
|
445
|
-
});
|
|
446
|
-
preScriptMeta = { error: result.error, consoleOutput: result.consoleOutput };
|
|
447
|
-
localVars = result.updatedLocalVars;
|
|
448
|
-
updatedEnvVars = result.updatedEnvVars;
|
|
449
|
-
updatedCollectionVars = result.updatedCollectionVars;
|
|
450
|
-
updatedGlobals = result.updatedGlobals;
|
|
451
|
-
scriptRunner.patchGlobals(result.updatedGlobals);
|
|
452
|
-
await scriptRunner.persistGlobals();
|
|
453
|
-
vars = scriptRunner.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars);
|
|
454
|
-
}
|
|
455
|
-
let response;
|
|
456
|
-
let sentRequest = { method: req.method, url: "", headers: {} };
|
|
457
|
-
const resolvedUrl = scriptRunner.buildUrl(req.url, req.params, vars);
|
|
458
|
-
const secretValues = /* @__PURE__ */ new Set();
|
|
459
|
-
if (environment) {
|
|
460
|
-
for (const v of environment.variables) {
|
|
461
|
-
if (!v.enabled) continue;
|
|
462
|
-
if ((v.secret || v.envRef) && envVars[v.key]) {
|
|
463
|
-
secretValues.add(envVars[v.key]);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
function redactSecrets(s) {
|
|
468
|
-
if (!secretValues.size) return s;
|
|
469
|
-
let result = s;
|
|
470
|
-
for (const secret of secretValues) {
|
|
471
|
-
if (secret) result = result.split(secret).join("[*****]");
|
|
472
|
-
}
|
|
473
|
-
return result;
|
|
474
|
-
}
|
|
475
|
-
function redactSentRequest(sr) {
|
|
476
|
-
const headers = {};
|
|
477
|
-
for (const [k, v] of Object.entries(sr.headers)) {
|
|
478
|
-
headers[k] = redactSecrets(v);
|
|
479
|
-
}
|
|
480
|
-
return {
|
|
481
|
-
method: sr.method,
|
|
482
|
-
url: redactSecrets(sr.url),
|
|
483
|
-
headers,
|
|
484
|
-
body: sr.body !== void 0 ? redactSecrets(sr.body) : void 0
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
try {
|
|
488
|
-
const dispatcher = await buildDispatcher$1(proxy, tls);
|
|
489
|
-
if (req.auth.type === "oauth2") {
|
|
490
|
-
const now = Date.now();
|
|
491
|
-
const tokenMissing = !req.auth.oauth2CachedToken;
|
|
492
|
-
const tokenExpired = req.auth.oauth2TokenExpiry ? req.auth.oauth2TokenExpiry <= now + 5e3 : true;
|
|
493
|
-
if (tokenMissing || tokenExpired) {
|
|
494
|
-
const result = await fetchOAuth2Token(req.auth, vars);
|
|
495
|
-
req.auth.oauth2CachedToken = result.accessToken;
|
|
496
|
-
req.auth.oauth2TokenExpiry = result.expiresAt;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
const authHeaders = await buildAuthHeaders(req.auth, vars);
|
|
500
|
-
const apiKeyParam = await buildApiKeyParam(req.auth, vars);
|
|
501
|
-
let finalUrl = resolvedUrl;
|
|
502
|
-
if (apiKeyParam) {
|
|
503
|
-
const sep = finalUrl.includes("?") ? "&" : "?";
|
|
504
|
-
finalUrl += `${sep}${encodeURIComponent(apiKeyParam.key)}=${encodeURIComponent(apiKeyParam.value)}`;
|
|
505
|
-
}
|
|
506
|
-
const buildHeaders2 = () => {
|
|
507
|
-
const h = new undici.Headers();
|
|
508
|
-
for (const header of req.headers) {
|
|
509
|
-
if (header.enabled && header.key) {
|
|
510
|
-
h.set(scriptRunner.interpolate(header.key, vars), scriptRunner.interpolate(header.value, vars));
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
for (const [k, v] of Object.entries(authHeaders)) h.set(k, v);
|
|
514
|
-
return h;
|
|
515
|
-
};
|
|
516
|
-
let body;
|
|
517
|
-
if (req.body.mode === "json" && req.body.json) {
|
|
518
|
-
body = scriptRunner.interpolate(req.body.json, vars);
|
|
519
|
-
} else if (req.body.mode === "form" && req.body.form) {
|
|
520
|
-
body = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${encodeURIComponent(scriptRunner.interpolate(p.key, vars))}=${encodeURIComponent(scriptRunner.interpolate(p.value, vars))}`).join("&");
|
|
521
|
-
} else if (req.body.mode === "raw" && req.body.raw) {
|
|
522
|
-
body = scriptRunner.interpolate(req.body.raw, vars);
|
|
523
|
-
} else if (req.body.mode === "graphql" && req.body.graphql) {
|
|
524
|
-
const gql = req.body.graphql;
|
|
525
|
-
const gqlBody = { query: scriptRunner.interpolate(gql.query, vars) };
|
|
526
|
-
const rawVars = gql.variables?.trim();
|
|
527
|
-
if (rawVars) {
|
|
528
|
-
try {
|
|
529
|
-
gqlBody.variables = JSON.parse(scriptRunner.interpolate(rawVars, vars));
|
|
530
|
-
} catch {
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
if (gql.operationName?.trim()) gqlBody.operationName = gql.operationName.trim();
|
|
534
|
-
body = JSON.stringify(gqlBody);
|
|
535
|
-
} else if (req.body.mode === "soap" && req.body.soap) {
|
|
536
|
-
const soap = req.body.soap;
|
|
537
|
-
body = scriptRunner.interpolate(soap.envelope, vars);
|
|
538
|
-
}
|
|
539
|
-
const methodHasBody = !["GET", "HEAD"].includes(req.method);
|
|
540
|
-
const doFetch = async (overrideHeaders) => {
|
|
541
|
-
const h = overrideHeaders ?? buildHeaders2();
|
|
542
|
-
if (body !== void 0) {
|
|
543
|
-
if (!h.has("content-type")) {
|
|
544
|
-
if (req.body.mode === "json" || req.body.mode === "graphql") h.set("Content-Type", "application/json");
|
|
545
|
-
else if (req.body.mode === "form") h.set("Content-Type", "application/x-www-form-urlencoded");
|
|
546
|
-
else if (req.body.mode === "raw") h.set("Content-Type", req.body.rawContentType ?? "text/plain");
|
|
547
|
-
else if (req.body.mode === "soap") h.set("Content-Type", "text/xml; charset=utf-8");
|
|
548
|
-
}
|
|
549
|
-
if (req.body.mode === "soap" && req.body.soap?.soapAction && !h.has("soapaction")) {
|
|
550
|
-
h.set("SOAPAction", req.body.soap.soapAction);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
const capturedHeaders = {};
|
|
554
|
-
h.forEach((value, key) => {
|
|
555
|
-
capturedHeaders[key] = value;
|
|
556
|
-
});
|
|
557
|
-
sentRequest = { method: req.method, url: finalUrl, headers: capturedHeaders, body: methodHasBody ? body : void 0 };
|
|
558
|
-
return undici.fetch(finalUrl, {
|
|
559
|
-
method: req.method,
|
|
560
|
-
headers: h,
|
|
561
|
-
body: methodHasBody ? body : void 0,
|
|
562
|
-
dispatcher
|
|
563
|
-
});
|
|
564
|
-
};
|
|
565
|
-
let fetchResp;
|
|
566
|
-
if (req.auth.type === "ntlm") {
|
|
567
|
-
await performNtlmRequest(finalUrl, req.method, req.auth, vars);
|
|
568
|
-
fetchResp = await doFetch();
|
|
569
|
-
} else if (req.auth.type === "digest") {
|
|
570
|
-
const probeFetch = async (url, init) => {
|
|
571
|
-
return undici.fetch(url, {
|
|
572
|
-
...init,
|
|
573
|
-
dispatcher
|
|
574
|
-
});
|
|
575
|
-
};
|
|
576
|
-
const digestHeader = await performDigestAuth(finalUrl, req.method, req.auth, vars, probeFetch);
|
|
577
|
-
const h = buildHeaders2();
|
|
578
|
-
if (digestHeader) h.set("Authorization", digestHeader);
|
|
579
|
-
fetchResp = await doFetch(h);
|
|
580
|
-
} else {
|
|
581
|
-
fetchResp = await doFetch();
|
|
582
|
-
}
|
|
583
|
-
const responseBody = await fetchResp.text();
|
|
584
|
-
const durationMs = Date.now() - start;
|
|
585
|
-
const rawResponseHeaders = {};
|
|
586
|
-
fetchResp.headers.forEach((value, key) => {
|
|
587
|
-
rawResponseHeaders[key] = value;
|
|
588
|
-
});
|
|
589
|
-
const maskedBody = maskPii(responseBody, piiMaskPatterns);
|
|
590
|
-
const maskedHeaders = maskHeaders(rawResponseHeaders, piiMaskPatterns);
|
|
591
|
-
response = {
|
|
592
|
-
status: fetchResp.status,
|
|
593
|
-
statusText: fetchResp.statusText,
|
|
594
|
-
headers: maskedHeaders,
|
|
595
|
-
body: maskedBody,
|
|
596
|
-
bodySize: Buffer.byteLength(responseBody, "utf8"),
|
|
597
|
-
durationMs
|
|
598
|
-
};
|
|
599
|
-
} catch (err) {
|
|
600
|
-
response = {
|
|
601
|
-
status: 0,
|
|
602
|
-
statusText: "Error",
|
|
603
|
-
headers: {},
|
|
604
|
-
body: "",
|
|
605
|
-
bodySize: 0,
|
|
606
|
-
durationMs: Date.now() - start,
|
|
607
|
-
error: err instanceof Error ? err.message : String(err)
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
let postTestResults = [];
|
|
611
|
-
let postConsole = [];
|
|
612
|
-
let postError;
|
|
613
|
-
if (req.postRequestScript?.trim() && !response.error) {
|
|
614
|
-
const result = await scriptRunner.runScript(req.postRequestScript, {
|
|
615
|
-
envVars: { ...updatedEnvVars },
|
|
616
|
-
collectionVars: { ...updatedCollectionVars },
|
|
617
|
-
globals: { ...updatedGlobals },
|
|
618
|
-
localVars: { ...localVars },
|
|
619
|
-
response
|
|
620
|
-
});
|
|
621
|
-
postTestResults = result.testResults;
|
|
622
|
-
postConsole = result.consoleOutput;
|
|
623
|
-
postError = result.error;
|
|
624
|
-
updatedEnvVars = result.updatedEnvVars;
|
|
625
|
-
updatedCollectionVars = result.updatedCollectionVars;
|
|
626
|
-
updatedGlobals = result.updatedGlobals;
|
|
627
|
-
scriptRunner.patchGlobals(result.updatedGlobals);
|
|
628
|
-
await scriptRunner.persistGlobals();
|
|
629
|
-
}
|
|
630
|
-
const scriptResult = {
|
|
631
|
-
testResults: postTestResults,
|
|
632
|
-
consoleOutput: [...decryptionWarnings, ...preScriptMeta.consoleOutput, ...postConsole],
|
|
633
|
-
updatedEnvVars,
|
|
634
|
-
updatedCollectionVars,
|
|
635
|
-
updatedGlobals,
|
|
636
|
-
resolvedUrl,
|
|
637
|
-
preScriptError: preScriptMeta.error,
|
|
638
|
-
postScriptError: postError
|
|
639
|
-
};
|
|
640
|
-
return { response, scriptResult, sentRequest: redactSentRequest(sentRequest) };
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
178
|
const POSTMAN_RULES = [
|
|
644
179
|
// Variables — environment
|
|
645
180
|
[/\bpm\.environment\.get\(/g, "sp.environment.get("],
|
|
@@ -2699,13 +2234,13 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
|
|
|
2699
2234
|
resolvedUrl: "",
|
|
2700
2235
|
status: "running"
|
|
2701
2236
|
};
|
|
2702
|
-
let vars =
|
|
2237
|
+
let vars = requestHandler.mergeVars(envVars, collectionVars, globals, localVars);
|
|
2703
2238
|
let updatedEnvVars = { ...envVars };
|
|
2704
2239
|
let updatedCollectionVars = { ...collectionVars };
|
|
2705
2240
|
let updatedGlobals = { ...globals };
|
|
2706
2241
|
let preScriptError;
|
|
2707
2242
|
if (req.preRequestScript?.trim()) {
|
|
2708
|
-
const r = await
|
|
2243
|
+
const r = await requestHandler.runScript(req.preRequestScript, {
|
|
2709
2244
|
envVars: { ...envVars },
|
|
2710
2245
|
collectionVars: { ...collectionVars },
|
|
2711
2246
|
globals: { ...globals },
|
|
@@ -2716,11 +2251,11 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
|
|
|
2716
2251
|
updatedEnvVars = r.updatedEnvVars;
|
|
2717
2252
|
updatedCollectionVars = r.updatedCollectionVars;
|
|
2718
2253
|
updatedGlobals = r.updatedGlobals;
|
|
2719
|
-
|
|
2720
|
-
await
|
|
2721
|
-
vars =
|
|
2254
|
+
requestHandler.patchGlobals(r.updatedGlobals);
|
|
2255
|
+
await requestHandler.persistGlobals();
|
|
2256
|
+
vars = requestHandler.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars);
|
|
2722
2257
|
}
|
|
2723
|
-
const resolvedUrl =
|
|
2258
|
+
const resolvedUrl = requestHandler.buildUrl(req.url, req.params, vars);
|
|
2724
2259
|
base.resolvedUrl = resolvedUrl;
|
|
2725
2260
|
const start = Date.now();
|
|
2726
2261
|
try {
|
|
@@ -2729,23 +2264,23 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
|
|
|
2729
2264
|
const tokenMissing = !req.auth.oauth2CachedToken;
|
|
2730
2265
|
const tokenExpired = req.auth.oauth2TokenExpiry ? req.auth.oauth2TokenExpiry <= now + 5e3 : true;
|
|
2731
2266
|
if (tokenMissing || tokenExpired) {
|
|
2732
|
-
const result = await fetchOAuth2Token(req.auth, vars);
|
|
2267
|
+
const result = await requestHandler.fetchOAuth2Token(req.auth, vars);
|
|
2733
2268
|
req.auth.oauth2CachedToken = result.accessToken;
|
|
2734
2269
|
req.auth.oauth2TokenExpiry = result.expiresAt;
|
|
2735
2270
|
}
|
|
2736
2271
|
}
|
|
2737
|
-
const authHeaders = await buildAuthHeaders(req.auth, vars);
|
|
2272
|
+
const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
|
|
2738
2273
|
const headers = new undici.Headers();
|
|
2739
2274
|
for (const h of req.headers) {
|
|
2740
|
-
if (h.enabled && h.key) headers.set(
|
|
2275
|
+
if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
|
|
2741
2276
|
}
|
|
2742
2277
|
for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
|
|
2743
2278
|
let body;
|
|
2744
2279
|
if (req.body.mode === "json" && req.body.json) {
|
|
2745
|
-
body =
|
|
2280
|
+
body = requestHandler.interpolate(req.body.json, vars);
|
|
2746
2281
|
if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
|
|
2747
2282
|
} else if (req.body.mode === "raw" && req.body.raw) {
|
|
2748
|
-
body =
|
|
2283
|
+
body = requestHandler.interpolate(req.body.raw, vars);
|
|
2749
2284
|
if (!headers.has("content-type")) headers.set("Content-Type", req.body.rawContentType ?? "text/plain");
|
|
2750
2285
|
}
|
|
2751
2286
|
const methodHasBody = !["GET", "HEAD"].includes(req.method);
|
|
@@ -2757,14 +2292,14 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
|
|
|
2757
2292
|
});
|
|
2758
2293
|
let fetchResp;
|
|
2759
2294
|
if (req.auth.type === "ntlm") {
|
|
2760
|
-
await performNtlmRequest(resolvedUrl, req.method, req.auth, vars);
|
|
2295
|
+
await requestHandler.performNtlmRequest(resolvedUrl, req.method, req.auth, vars);
|
|
2761
2296
|
fetchResp = await doFetch(headers);
|
|
2762
2297
|
} else if (req.auth.type === "digest") {
|
|
2763
2298
|
const probeFetch = (url, init) => undici.fetch(url, {
|
|
2764
2299
|
...init,
|
|
2765
2300
|
dispatcher
|
|
2766
2301
|
});
|
|
2767
|
-
const digestHeader = await performDigestAuth(resolvedUrl, req.method, req.auth, vars, probeFetch);
|
|
2302
|
+
const digestHeader = await requestHandler.performDigestAuth(resolvedUrl, req.method, req.auth, vars, probeFetch);
|
|
2768
2303
|
if (digestHeader) headers.set("Authorization", digestHeader);
|
|
2769
2304
|
fetchResp = await doFetch(headers);
|
|
2770
2305
|
} else {
|
|
@@ -2776,8 +2311,8 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
|
|
|
2776
2311
|
fetchResp.headers.forEach((v, k) => {
|
|
2777
2312
|
rawRespHeaders[k] = v;
|
|
2778
2313
|
});
|
|
2779
|
-
const maskedBody = maskPii(responseBody, piiMaskPatterns);
|
|
2780
|
-
const maskedHeaders = maskHeaders(rawRespHeaders, piiMaskPatterns);
|
|
2314
|
+
const maskedBody = requestHandler.maskPii(responseBody, piiMaskPatterns);
|
|
2315
|
+
const maskedHeaders = requestHandler.maskHeaders(rawRespHeaders, piiMaskPatterns);
|
|
2781
2316
|
const response = {
|
|
2782
2317
|
status: fetchResp.status,
|
|
2783
2318
|
statusText: fetchResp.statusText,
|
|
@@ -2790,7 +2325,7 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
|
|
|
2790
2325
|
let consoleOutput = [];
|
|
2791
2326
|
let postScriptError;
|
|
2792
2327
|
if (req.postRequestScript?.trim()) {
|
|
2793
|
-
const r = await
|
|
2328
|
+
const r = await requestHandler.runScript(req.postRequestScript, {
|
|
2794
2329
|
envVars: updatedEnvVars,
|
|
2795
2330
|
collectionVars: updatedCollectionVars,
|
|
2796
2331
|
globals: updatedGlobals,
|
|
@@ -2803,8 +2338,8 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
|
|
|
2803
2338
|
updatedEnvVars = r.updatedEnvVars;
|
|
2804
2339
|
updatedCollectionVars = r.updatedCollectionVars;
|
|
2805
2340
|
updatedGlobals = r.updatedGlobals;
|
|
2806
|
-
|
|
2807
|
-
await
|
|
2341
|
+
requestHandler.patchGlobals(r.updatedGlobals);
|
|
2342
|
+
await requestHandler.persistGlobals();
|
|
2808
2343
|
}
|
|
2809
2344
|
const allPassed = testResults.every((t) => t.passed);
|
|
2810
2345
|
const status = postScriptError ? "error" : testResults.length > 0 ? allPassed ? "passed" : "failed" : "passed";
|
|
@@ -2853,8 +2388,8 @@ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
|
2853
2388
|
function registerRunnerHandler(ipc) {
|
|
2854
2389
|
ipc.handle("runner:start", async (event, payload) => {
|
|
2855
2390
|
const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
|
|
2856
|
-
const envVars = await
|
|
2857
|
-
const liveGlobals =
|
|
2391
|
+
const envVars = await requestHandler.buildEnvVars(environment);
|
|
2392
|
+
const liveGlobals = requestHandler.getGlobals();
|
|
2858
2393
|
const globals = { ...payloadGlobals, ...liveGlobals };
|
|
2859
2394
|
const dispatcher = await buildDispatcher(proxy, tls);
|
|
2860
2395
|
const summary = { total: items.length, passed: 0, failed: 0, errors: 0, durationMs: 0 };
|
|
@@ -2920,14 +2455,14 @@ function registerOAuth2Handlers(ipc) {
|
|
|
2920
2455
|
ipc.handle("oauth2:startFlow", async (_e, auth, vars) => {
|
|
2921
2456
|
const port = auth.oauth2RedirectPort ?? 9876;
|
|
2922
2457
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
2923
|
-
const authUrl =
|
|
2924
|
-
const tokenUrl =
|
|
2925
|
-
const clientId =
|
|
2458
|
+
const authUrl = requestHandler.interpolate(auth.oauth2AuthUrl ?? "", vars);
|
|
2459
|
+
const tokenUrl = requestHandler.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
2460
|
+
const clientId = requestHandler.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
2926
2461
|
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
2927
2462
|
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
2928
|
-
clientSecret = await
|
|
2463
|
+
clientSecret = await requestHandler.getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
2929
2464
|
}
|
|
2930
|
-
clientSecret =
|
|
2465
|
+
clientSecret = requestHandler.interpolate(clientSecret, vars);
|
|
2931
2466
|
if (!authUrl) throw new Error("OAuth 2.0: authUrl is required for authorization_code flow.");
|
|
2932
2467
|
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for authorization_code flow.");
|
|
2933
2468
|
if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
|
|
@@ -3002,13 +2537,13 @@ function registerOAuth2Handlers(ipc) {
|
|
|
3002
2537
|
};
|
|
3003
2538
|
});
|
|
3004
2539
|
ipc.handle("oauth2:refreshToken", async (_e, auth, vars, refreshToken) => {
|
|
3005
|
-
const tokenUrl =
|
|
3006
|
-
const clientId =
|
|
2540
|
+
const tokenUrl = requestHandler.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
2541
|
+
const clientId = requestHandler.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
3007
2542
|
let clientSecret = auth.oauth2ClientSecret ?? "";
|
|
3008
2543
|
if (!clientSecret && auth.oauth2ClientSecretRef) {
|
|
3009
|
-
clientSecret = await
|
|
2544
|
+
clientSecret = await requestHandler.getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
3010
2545
|
}
|
|
3011
|
-
clientSecret =
|
|
2546
|
+
clientSecret = requestHandler.interpolate(clientSecret, vars);
|
|
3012
2547
|
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for refresh.");
|
|
3013
2548
|
const { fetch: nodeFetch } = await import("undici");
|
|
3014
2549
|
const params = new URLSearchParams();
|
|
@@ -3456,21 +2991,21 @@ function validateConsumerResponse(contract, actualStatus, actualHeaders, bodyTex
|
|
|
3456
2991
|
return violations;
|
|
3457
2992
|
}
|
|
3458
2993
|
async function executeContract(req, vars) {
|
|
3459
|
-
const url =
|
|
2994
|
+
const url = requestHandler.buildUrl(req.url, req.params, vars);
|
|
3460
2995
|
const start = Date.now();
|
|
3461
2996
|
try {
|
|
3462
2997
|
const headers = new undici.Headers();
|
|
3463
2998
|
for (const h of req.headers) {
|
|
3464
|
-
if (h.enabled && h.key) headers.set(
|
|
2999
|
+
if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
|
|
3465
3000
|
}
|
|
3466
|
-
const authHeaders = await buildAuthHeaders(req.auth, vars);
|
|
3001
|
+
const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
|
|
3467
3002
|
for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
|
|
3468
3003
|
let body;
|
|
3469
3004
|
if (req.body.mode === "json" && req.body.json) {
|
|
3470
|
-
body =
|
|
3005
|
+
body = requestHandler.interpolate(req.body.json, vars);
|
|
3471
3006
|
if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
|
|
3472
3007
|
} else if (req.body.mode === "raw" && req.body.raw) {
|
|
3473
|
-
body =
|
|
3008
|
+
body = requestHandler.interpolate(req.body.raw, vars);
|
|
3474
3009
|
}
|
|
3475
3010
|
const resp = await undici.fetch(url, {
|
|
3476
3011
|
method: req.method,
|
|
@@ -3625,7 +3160,7 @@ function validateRequestAgainstSpec(spec, req, envVars, requestBaseUrl) {
|
|
|
3625
3160
|
const jsonContent = content["application/json"];
|
|
3626
3161
|
if (jsonContent?.["schema"]) {
|
|
3627
3162
|
try {
|
|
3628
|
-
const data = JSON.parse(
|
|
3163
|
+
const data = JSON.parse(requestHandler.interpolate(req.body.json, vars));
|
|
3629
3164
|
const schema = resolveSchema(spec, jsonContent["schema"]);
|
|
3630
3165
|
const validate = ajv.compile(schema);
|
|
3631
3166
|
if (!validate(data)) {
|
|
@@ -3761,18 +3296,18 @@ function getProviderResponseSchema(spec, req, envVars, statusCode, requestBaseUr
|
|
|
3761
3296
|
return null;
|
|
3762
3297
|
}
|
|
3763
3298
|
async function executeRequest(req, vars) {
|
|
3764
|
-
const url =
|
|
3299
|
+
const url = requestHandler.buildUrl(req.url, req.params, vars);
|
|
3765
3300
|
const start = Date.now();
|
|
3766
3301
|
try {
|
|
3767
3302
|
const headers = new undici.Headers();
|
|
3768
3303
|
for (const h of req.headers) {
|
|
3769
|
-
if (h.enabled && h.key) headers.set(
|
|
3304
|
+
if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
|
|
3770
3305
|
}
|
|
3771
|
-
const authHeaders = await buildAuthHeaders(req.auth, vars);
|
|
3306
|
+
const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
|
|
3772
3307
|
for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
|
|
3773
3308
|
let body;
|
|
3774
3309
|
if (req.body.mode === "json" && req.body.json) {
|
|
3775
|
-
body =
|
|
3310
|
+
body = requestHandler.interpolate(req.body.json, vars);
|
|
3776
3311
|
if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
|
|
3777
3312
|
}
|
|
3778
3313
|
const resp = await undici.fetch(url, {
|
|
@@ -3798,7 +3333,7 @@ async function runBidirectional(requests, envVars, collectionVars = {}, specUrl,
|
|
|
3798
3333
|
(r) => r.contract && (r.contract.statusCode !== void 0 || r.contract.bodySchema || r.contract.headers?.length)
|
|
3799
3334
|
);
|
|
3800
3335
|
const results = await Promise.all(contractRequests.map(async (req) => {
|
|
3801
|
-
const url =
|
|
3336
|
+
const url = requestHandler.buildUrl(req.url, req.params, vars);
|
|
3802
3337
|
const violations = [];
|
|
3803
3338
|
const expectedStatus = req.contract.statusCode ?? 200;
|
|
3804
3339
|
const consumerSchema = req.contract.bodySchema ? (() => {
|
|
@@ -3984,10 +3519,10 @@ function createWindow() {
|
|
|
3984
3519
|
});
|
|
3985
3520
|
}
|
|
3986
3521
|
electron.app.whenReady().then(async () => {
|
|
3987
|
-
await
|
|
3522
|
+
await requestHandler.initSecretStore(electron.app.getPath("userData"));
|
|
3988
3523
|
registerFileHandlers(electron.ipcMain);
|
|
3989
|
-
registerRequestHandler(electron.ipcMain);
|
|
3990
|
-
|
|
3524
|
+
requestHandler.registerRequestHandler(electron.ipcMain);
|
|
3525
|
+
requestHandler.registerSecretHandlers(electron.ipcMain);
|
|
3991
3526
|
registerImportHandlers(electron.ipcMain);
|
|
3992
3527
|
registerGenerateHandlers(electron.ipcMain);
|
|
3993
3528
|
registerRunnerHandler(electron.ipcMain);
|