@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/runner.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
const promises = require("fs/promises");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
5
|
+
const authBuilder = require("./chunks/auth-builder-CUs9yzOF.js");
|
|
6
|
+
const requestCollection = require("./chunks/request-collection-8TOVNXE0.js");
|
|
7
|
+
const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
|
|
8
8
|
require("crypto");
|
|
9
9
|
require("http");
|
|
10
|
+
require("./chunks/handle-C0IQL-Vl.js");
|
|
10
11
|
require("dayjs");
|
|
11
12
|
require("vm");
|
|
13
|
+
require("undici");
|
|
12
14
|
require("tv4");
|
|
13
15
|
require("jsonpath-plus");
|
|
14
16
|
require("@xmldom/xmldom");
|
|
15
17
|
require("ajv");
|
|
16
|
-
require("./chunks/ipc-validate-CscN4HfG.js");
|
|
17
18
|
function buildJsonReport(results, summary, meta = {}) {
|
|
18
19
|
return JSON.stringify({
|
|
19
20
|
timestamp: meta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -337,267 +338,24 @@ ${failures.join("\n")}
|
|
|
337
338
|
];
|
|
338
339
|
return lines.join("\n") + "\n";
|
|
339
340
|
}
|
|
340
|
-
const C = {
|
|
341
|
-
reset: "\x1B[0m",
|
|
342
|
-
bold: "\x1B[1m",
|
|
343
|
-
dim: "\x1B[2m",
|
|
344
|
-
green: "\x1B[32m",
|
|
345
|
-
red: "\x1B[31m",
|
|
346
|
-
yellow: "\x1B[33m",
|
|
347
|
-
cyan: "\x1B[36m",
|
|
348
|
-
gray: "\x1B[90m",
|
|
349
|
-
white: "\x1B[97m"
|
|
350
|
-
};
|
|
351
|
-
function color(str, ...codes) {
|
|
352
|
-
return process.stdout.isTTY ? codes.join("") + str + C.reset : str;
|
|
353
|
-
}
|
|
354
|
-
function parseArgs(argv) {
|
|
355
|
-
const args = {};
|
|
356
|
-
for (let i = 0; i < argv.length; i++) {
|
|
357
|
-
const arg = argv[i];
|
|
358
|
-
if (arg.startsWith("--")) {
|
|
359
|
-
const key = arg.slice(2);
|
|
360
|
-
const next = argv[i + 1];
|
|
361
|
-
if (!next || next.startsWith("--")) {
|
|
362
|
-
args[key] = true;
|
|
363
|
-
} else {
|
|
364
|
-
args[key] = next;
|
|
365
|
-
i++;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return args;
|
|
370
|
-
}
|
|
371
|
-
async function resolveWorkspacePath(wsPath) {
|
|
372
|
-
const s = await promises.stat(wsPath);
|
|
373
|
-
if (!s.isDirectory()) return wsPath;
|
|
374
|
-
const entries = await promises.readdir(wsPath);
|
|
375
|
-
const spector = entries.find((e) => e.endsWith(".spector"));
|
|
376
|
-
if (!spector) throw new Error(`No .spector workspace file found in directory: ${wsPath}`);
|
|
377
|
-
return path.join(wsPath, spector);
|
|
378
|
-
}
|
|
379
|
-
async function loadWorkspace(wsPath) {
|
|
380
|
-
const resolved = await resolveWorkspacePath(wsPath);
|
|
381
|
-
const raw = await promises.readFile(resolved, "utf8");
|
|
382
|
-
return { workspace: JSON.parse(raw), dir: path.dirname(path.resolve(resolved)) };
|
|
383
|
-
}
|
|
384
|
-
async function loadCollections(workspace, dir) {
|
|
385
|
-
const cols = [];
|
|
386
|
-
for (const relPath of workspace.collections) {
|
|
387
|
-
try {
|
|
388
|
-
const raw = await promises.readFile(path.join(dir, relPath), "utf8");
|
|
389
|
-
cols.push(JSON.parse(raw));
|
|
390
|
-
} catch (_e) {
|
|
391
|
-
console.error(color(` [warn] Could not load collection: ${relPath}`, C.yellow));
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return cols;
|
|
395
|
-
}
|
|
396
|
-
async function loadEnvironments(workspace, dir) {
|
|
397
|
-
const envs = [];
|
|
398
|
-
for (const relPath of workspace.environments) {
|
|
399
|
-
try {
|
|
400
|
-
const raw = await promises.readFile(path.join(dir, relPath), "utf8");
|
|
401
|
-
envs.push(JSON.parse(raw));
|
|
402
|
-
} catch (_e) {
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return envs;
|
|
406
|
-
}
|
|
407
|
-
async function executeRequest(req, collectionVars, envVars, globals, localVars, verbose, tls, piiMaskPatterns = []) {
|
|
408
|
-
if (!req.headers) req.headers = [];
|
|
409
|
-
if (!req.params) req.params = [];
|
|
410
|
-
if (!req.body) req.body = { mode: "none" };
|
|
411
|
-
if (!req.auth) req.auth = { type: "none" };
|
|
412
|
-
const base = {
|
|
413
|
-
requestId: req.id,
|
|
414
|
-
name: req.name,
|
|
415
|
-
method: req.method,
|
|
416
|
-
resolvedUrl: req.url,
|
|
417
|
-
status: "running"
|
|
418
|
-
};
|
|
419
|
-
let updatedEnvVars = { ...envVars };
|
|
420
|
-
let updatedCollectionVars = { ...collectionVars };
|
|
421
|
-
let updatedGlobals = { ...globals };
|
|
422
|
-
let preScriptError;
|
|
423
|
-
const dynamicVars = await authBuilder.buildDynamicVars();
|
|
424
|
-
let vars = authBuilder.mergeVars(envVars, collectionVars, globals, localVars, dynamicVars);
|
|
425
|
-
if (req.preRequestScript?.trim()) {
|
|
426
|
-
const r = await requestCollection.runScript(authBuilder.interpolate(req.preRequestScript, vars), {
|
|
427
|
-
envVars: { ...envVars },
|
|
428
|
-
collectionVars: { ...collectionVars },
|
|
429
|
-
globals: { ...globals },
|
|
430
|
-
localVars: { ...localVars },
|
|
431
|
-
piiMaskPatterns
|
|
432
|
-
});
|
|
433
|
-
preScriptError = r.error;
|
|
434
|
-
localVars = r.updatedLocalVars;
|
|
435
|
-
updatedEnvVars = r.updatedEnvVars;
|
|
436
|
-
updatedCollectionVars = r.updatedCollectionVars;
|
|
437
|
-
updatedGlobals = r.updatedGlobals;
|
|
438
|
-
requestCollection.patchGlobals(r.updatedGlobals);
|
|
439
|
-
await requestCollection.persistGlobals();
|
|
440
|
-
if (verbose && r.consoleOutput.length) r.consoleOutput.forEach((l) => console.log(color(` [pre] ${l}`, C.gray)));
|
|
441
|
-
if (r.error) console.error(color(` [pre-script error] ${r.error}`, C.red));
|
|
442
|
-
}
|
|
443
|
-
vars = authBuilder.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
|
|
444
|
-
const resolvedUrl = authBuilder.buildUrl(req.url, req.params, vars);
|
|
445
|
-
base.resolvedUrl = resolvedUrl;
|
|
446
|
-
const start = Date.now();
|
|
447
|
-
try {
|
|
448
|
-
const authH = {};
|
|
449
|
-
if (req.auth.type === "bearer") {
|
|
450
|
-
let token = req.auth.token ?? "";
|
|
451
|
-
if (!token && req.auth.tokenSecretRef) token = await authBuilder.getSecret(req.auth.tokenSecretRef) ?? "";
|
|
452
|
-
if (token) authH["Authorization"] = `Bearer ${authBuilder.interpolate(token, vars)}`;
|
|
453
|
-
}
|
|
454
|
-
const headers = new undici.Headers();
|
|
455
|
-
for (const h of req.headers) {
|
|
456
|
-
if (h.enabled && h.key) headers.set(authBuilder.interpolate(h.key, vars), authBuilder.interpolate(h.value, vars));
|
|
457
|
-
}
|
|
458
|
-
for (const [k, v] of Object.entries(authH)) headers.set(k, v);
|
|
459
|
-
let body;
|
|
460
|
-
if (req.body.mode === "json" && req.body.json) {
|
|
461
|
-
body = authBuilder.interpolate(req.body.json, vars);
|
|
462
|
-
if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
|
|
463
|
-
} else if (req.body.mode === "raw" && req.body.raw) {
|
|
464
|
-
body = authBuilder.interpolate(req.body.raw, vars);
|
|
465
|
-
if (!headers.has("content-type")) headers.set("Content-Type", req.body.rawContentType ?? "text/plain");
|
|
466
|
-
} else if (req.body.mode === "graphql" && req.body.graphql) {
|
|
467
|
-
const gql = req.body.graphql;
|
|
468
|
-
const gqlBody = { query: authBuilder.interpolate(gql.query, vars) };
|
|
469
|
-
const rawVars = gql.variables?.trim();
|
|
470
|
-
if (rawVars) {
|
|
471
|
-
try {
|
|
472
|
-
gqlBody.variables = JSON.parse(authBuilder.interpolate(rawVars, vars));
|
|
473
|
-
} catch {
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (gql.operationName?.trim()) gqlBody.operationName = gql.operationName.trim();
|
|
477
|
-
body = JSON.stringify(gqlBody);
|
|
478
|
-
if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
|
|
479
|
-
} else if (req.body.mode === "soap" && req.body.soap) {
|
|
480
|
-
body = authBuilder.interpolate(req.body.soap.envelope, vars);
|
|
481
|
-
if (!headers.has("content-type")) headers.set("Content-Type", "text/xml; charset=utf-8");
|
|
482
|
-
if (req.body.soap.soapAction && !headers.has("soapaction")) {
|
|
483
|
-
headers.set("SOAPAction", req.body.soap.soapAction);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
const dispatcher = await requestCollection.buildDispatcher(void 0, tls);
|
|
487
|
-
const fetchResp = await undici.fetch(resolvedUrl, {
|
|
488
|
-
method: req.method,
|
|
489
|
-
headers,
|
|
490
|
-
body: ["GET", "HEAD"].includes(req.method) ? void 0 : body,
|
|
491
|
-
...dispatcher ? { dispatcher } : {}
|
|
492
|
-
});
|
|
493
|
-
const responseBody = await fetchResp.text();
|
|
494
|
-
const durationMs = Date.now() - start;
|
|
495
|
-
const respHeaders = {};
|
|
496
|
-
fetchResp.headers.forEach((v, k) => {
|
|
497
|
-
respHeaders[k] = v;
|
|
498
|
-
});
|
|
499
|
-
const response = {
|
|
500
|
-
status: fetchResp.status,
|
|
501
|
-
statusText: fetchResp.statusText,
|
|
502
|
-
headers: respHeaders,
|
|
503
|
-
body: responseBody,
|
|
504
|
-
bodySize: Buffer.byteLength(responseBody, "utf8"),
|
|
505
|
-
durationMs
|
|
506
|
-
};
|
|
507
|
-
const protocolFaultTests = requestCollection.buildProtocolFaultTests(req.body.mode, responseBody);
|
|
508
|
-
let testResults = [...protocolFaultTests];
|
|
509
|
-
let consoleOutput = [];
|
|
510
|
-
let postScriptError;
|
|
511
|
-
if (req.postRequestScript?.trim()) {
|
|
512
|
-
const r = await requestCollection.runScript(authBuilder.interpolate(req.postRequestScript, vars), {
|
|
513
|
-
envVars: updatedEnvVars,
|
|
514
|
-
collectionVars: updatedCollectionVars,
|
|
515
|
-
globals: updatedGlobals,
|
|
516
|
-
localVars,
|
|
517
|
-
response,
|
|
518
|
-
piiMaskPatterns
|
|
519
|
-
});
|
|
520
|
-
testResults = [...protocolFaultTests, ...r.testResults];
|
|
521
|
-
consoleOutput = r.consoleOutput;
|
|
522
|
-
postScriptError = r.error;
|
|
523
|
-
updatedEnvVars = r.updatedEnvVars;
|
|
524
|
-
updatedCollectionVars = r.updatedCollectionVars;
|
|
525
|
-
updatedGlobals = r.updatedGlobals;
|
|
526
|
-
localVars = r.updatedLocalVars;
|
|
527
|
-
requestCollection.patchGlobals(r.updatedGlobals);
|
|
528
|
-
await requestCollection.persistGlobals();
|
|
529
|
-
if (verbose && r.consoleOutput.length) r.consoleOutput.forEach((l) => console.log(color(` [post] ${l}`, C.gray)));
|
|
530
|
-
}
|
|
531
|
-
const allPassed = testResults.every((t) => t.passed);
|
|
532
|
-
const httpFailed = fetchResp.status >= 400;
|
|
533
|
-
const hasTests = testResults.length > 0;
|
|
534
|
-
const status = postScriptError ? "error" : hasTests ? allPassed ? "passed" : "failed" : httpFailed ? "failed" : "passed";
|
|
535
|
-
if (httpFailed && testResults.length === 0) {
|
|
536
|
-
testResults = [
|
|
537
|
-
...testResults,
|
|
538
|
-
{
|
|
539
|
-
name: `HTTP status ${fetchResp.status} ${fetchResp.statusText}`.trim(),
|
|
540
|
-
passed: false,
|
|
541
|
-
error: `Request returned ${fetchResp.status} — no assertion was defined to verify the status code.`
|
|
542
|
-
}
|
|
543
|
-
];
|
|
544
|
-
}
|
|
545
|
-
const reqHeaders = {};
|
|
546
|
-
headers.forEach((v, k) => {
|
|
547
|
-
reqHeaders[k] = v;
|
|
548
|
-
});
|
|
549
|
-
return {
|
|
550
|
-
result: {
|
|
551
|
-
...base,
|
|
552
|
-
status,
|
|
553
|
-
httpStatus: fetchResp.status,
|
|
554
|
-
durationMs,
|
|
555
|
-
testResults,
|
|
556
|
-
consoleOutput,
|
|
557
|
-
preScriptError,
|
|
558
|
-
postScriptError,
|
|
559
|
-
sentRequest: { headers: reqHeaders, body },
|
|
560
|
-
receivedResponse: response
|
|
561
|
-
},
|
|
562
|
-
updatedEnvVars,
|
|
563
|
-
updatedCollectionVars,
|
|
564
|
-
updatedGlobals,
|
|
565
|
-
updatedLocalVars: localVars
|
|
566
|
-
};
|
|
567
|
-
} catch (err) {
|
|
568
|
-
return {
|
|
569
|
-
result: {
|
|
570
|
-
...base,
|
|
571
|
-
status: "error",
|
|
572
|
-
durationMs: Date.now() - start,
|
|
573
|
-
error: err instanceof Error ? err.message : String(err),
|
|
574
|
-
preScriptError
|
|
575
|
-
},
|
|
576
|
-
updatedEnvVars,
|
|
577
|
-
updatedCollectionVars,
|
|
578
|
-
updatedGlobals,
|
|
579
|
-
updatedLocalVars: localVars
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
341
|
function printResult(r, verbose) {
|
|
584
|
-
const icon = r.status === "passed" ? color("✓", C.green, C.bold) : r.status === "failed" ? color("✗", C.red, C.bold) : r.status === "skipped" ? color("○", C.gray, C.bold) : color("⚠", C.yellow, C.bold);
|
|
585
|
-
const http = r.httpStatus ? color(` ${r.httpStatus}`, r.httpStatus < 400 ? C.green : C.red) : "";
|
|
586
|
-
const dur = r.durationMs !== void 0 ? color(` ${r.durationMs}ms`, C.gray) : "";
|
|
587
|
-
const method = color(r.method.padEnd(7), C.cyan);
|
|
588
|
-
const hookTag = r.isHook && r.hookType ? color(` [${r.hookType.toUpperCase()}]`, C.yellow) : "";
|
|
342
|
+
const icon = r.status === "passed" ? cliCommon.color("✓", cliCommon.C.green, cliCommon.C.bold) : r.status === "failed" ? cliCommon.color("✗", cliCommon.C.red, cliCommon.C.bold) : r.status === "skipped" ? cliCommon.color("○", cliCommon.C.gray, cliCommon.C.bold) : cliCommon.color("⚠", cliCommon.C.yellow, cliCommon.C.bold);
|
|
343
|
+
const http = r.httpStatus ? cliCommon.color(` ${r.httpStatus}`, r.httpStatus < 400 ? cliCommon.C.green : cliCommon.C.red) : "";
|
|
344
|
+
const dur = r.durationMs !== void 0 ? cliCommon.color(` ${r.durationMs}ms`, cliCommon.C.gray) : "";
|
|
345
|
+
const method = cliCommon.color(r.method.padEnd(7), cliCommon.C.cyan);
|
|
346
|
+
const hookTag = r.isHook && r.hookType ? cliCommon.color(` [${r.hookType.toUpperCase()}]`, cliCommon.C.yellow) : "";
|
|
589
347
|
console.log(` ${icon} ${method} ${r.name}${hookTag}${http}${dur}`);
|
|
590
|
-
if (verbose) console.log(color(` ${r.resolvedUrl}`, C.gray));
|
|
348
|
+
if (verbose) console.log(cliCommon.color(` ${r.resolvedUrl}`, cliCommon.C.gray));
|
|
591
349
|
if (r.testResults?.length) {
|
|
592
350
|
for (const t of r.testResults) {
|
|
593
|
-
const ti = t.passed ? color(" ✓", C.green) : color(" ✗", C.red);
|
|
594
|
-
console.log(`${ti} ${t.name}${t.error ? color(` — ${t.error}`, C.red) : ""}`);
|
|
351
|
+
const ti = t.passed ? cliCommon.color(" ✓", cliCommon.C.green) : cliCommon.color(" ✗", cliCommon.C.red);
|
|
352
|
+
console.log(`${ti} ${t.name}${t.error ? cliCommon.color(` — ${t.error}`, cliCommon.C.red) : ""}`);
|
|
595
353
|
}
|
|
596
354
|
}
|
|
597
|
-
if (r.error) console.log(color(` Error: ${r.error}`, C.red));
|
|
355
|
+
if (r.error) console.log(cliCommon.color(` Error: ${r.error}`, cliCommon.C.red));
|
|
598
356
|
}
|
|
599
357
|
async function main() {
|
|
600
|
-
const args = parseArgs(process.argv.slice(2));
|
|
358
|
+
const args = cliCommon.parseArgs(process.argv.slice(2));
|
|
601
359
|
if (args.help) {
|
|
602
360
|
console.log(
|
|
603
361
|
"\nUsage:\n api-spector run --workspace <path> [--environment <name>] [--tags <a,b>]\n [--collection <name>] [--output <path>] [--format json|junit]\n [--verbose] [--bail]\n"
|
|
@@ -606,7 +364,7 @@ async function main() {
|
|
|
606
364
|
}
|
|
607
365
|
const wsPath = args.workspace;
|
|
608
366
|
if (!wsPath) {
|
|
609
|
-
console.error(color("Error: --workspace is required", C.red));
|
|
367
|
+
console.error(cliCommon.color("Error: --workspace is required", cliCommon.C.red));
|
|
610
368
|
process.exit(1);
|
|
611
369
|
}
|
|
612
370
|
const filterTags = args.tags ? args.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
@@ -621,24 +379,26 @@ async function main() {
|
|
|
621
379
|
let workspace, wsDir;
|
|
622
380
|
try {
|
|
623
381
|
;
|
|
624
|
-
({ workspace, dir: wsDir } = await loadWorkspace(wsPath));
|
|
382
|
+
({ workspace, dir: wsDir } = await cliCommon.loadWorkspace(wsPath));
|
|
625
383
|
} catch {
|
|
626
|
-
console.error(color(`Error: could not read workspace file: ${wsPath}`, C.red));
|
|
384
|
+
console.error(cliCommon.color(`Error: could not read workspace file: ${wsPath}`, cliCommon.C.red));
|
|
627
385
|
process.exit(1);
|
|
628
386
|
}
|
|
629
387
|
await requestCollection.loadGlobals(wsDir);
|
|
630
|
-
const collections = await loadCollections(workspace, wsDir
|
|
631
|
-
|
|
388
|
+
const collections = await cliCommon.loadCollections(workspace, wsDir, {
|
|
389
|
+
onError: (relPath) => console.error(cliCommon.color(` [warn] Could not load collection: ${relPath}`, cliCommon.C.yellow))
|
|
390
|
+
});
|
|
391
|
+
const environments = await cliCommon.loadEnvironments(workspace, wsDir);
|
|
632
392
|
const env = envName ? environments.find((e) => e.name.toLowerCase() === envName.toLowerCase()) ?? null : null;
|
|
633
393
|
if (envName && !env) {
|
|
634
|
-
console.warn(color(`Warning: environment "${envName}" not found. Running without environment.`, C.yellow));
|
|
394
|
+
console.warn(cliCommon.color(`Warning: environment "${envName}" not found. Running without environment.`, cliCommon.C.yellow));
|
|
635
395
|
}
|
|
636
|
-
const version = `v${"0.3.
|
|
396
|
+
const version = `v${"0.3.3"}`;
|
|
637
397
|
console.log("");
|
|
638
|
-
console.log(color(" API Test Runner" + (version ? ` ${version}` : ""), C.bold, C.white));
|
|
639
|
-
console.log(color(` Workspace: ${wsPath}`, C.gray));
|
|
640
|
-
console.log(color(` Environment: ${env?.name ?? "(none)"}`, C.gray));
|
|
641
|
-
if (filterTags.length) console.log(color(` Tags: ${filterTags.join(", ")}`, C.gray));
|
|
398
|
+
console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
|
|
399
|
+
console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
|
|
400
|
+
console.log(cliCommon.color(` Environment: ${env?.name ?? "(none)"}`, cliCommon.C.gray));
|
|
401
|
+
if (filterTags.length) console.log(cliCommon.color(` Tags: ${filterTags.join(", ")}`, cliCommon.C.gray));
|
|
642
402
|
console.log("");
|
|
643
403
|
const envVarsSnapshot = await authBuilder.buildEnvVars(env);
|
|
644
404
|
const secretValuesToMask = (env?.variables ?? []).filter((v) => v.secret && v.enabled).map((v) => envVarsSnapshot[v.key]).filter((v) => typeof v === "string" && v.length > 0);
|
|
@@ -679,7 +439,7 @@ async function main() {
|
|
|
679
439
|
let runGlobals = requestCollection.getGlobals();
|
|
680
440
|
let runCollectionVars = { ...col.collectionVariables ?? {} };
|
|
681
441
|
let runLocalVars = {};
|
|
682
|
-
console.log(color(` ┌ ${col.name}`, C.bold, C.white));
|
|
442
|
+
console.log(cliCommon.color(` ┌ ${col.name}`, cliCommon.C.bold, cliCommon.C.white));
|
|
683
443
|
const workspaceTls = workspace.settings?.tls;
|
|
684
444
|
const effectiveTls = col.tls ? { ...workspaceTls, ...col.tls } : workspaceTls;
|
|
685
445
|
for (const item of items) {
|
|
@@ -695,32 +455,15 @@ async function main() {
|
|
|
695
455
|
}
|
|
696
456
|
let bailed = false;
|
|
697
457
|
let lastPrintedScope = null;
|
|
698
|
-
const
|
|
699
|
-
const
|
|
458
|
+
const skipTracker = new requestCollection.HookSkipTracker();
|
|
459
|
+
const dispatcher = await requestCollection.buildDispatcher(void 0, effectiveTls);
|
|
460
|
+
const onScriptOutput = (phase, lines, error) => {
|
|
461
|
+
if (verbose && lines.length) lines.forEach((l) => console.log(cliCommon.color(` [${phase}] ${l}`, cliCommon.C.gray)));
|
|
462
|
+
if (phase === "pre" && error) console.error(cliCommon.color(` [pre-script error] ${error}`, cliCommon.C.red));
|
|
463
|
+
};
|
|
700
464
|
for (const item of items) {
|
|
701
|
-
const { isHook, hookType, scopeId
|
|
702
|
-
|
|
703
|
-
if (isHook) {
|
|
704
|
-
if (hookType === "beforeAll") {
|
|
705
|
-
if ((scopeAncestors ?? []).some((id) => failedScopes.has(id))) {
|
|
706
|
-
skipReason = "Skipped — outer scope hook failed";
|
|
707
|
-
}
|
|
708
|
-
} else if (hookType === "before") {
|
|
709
|
-
const allScopes = [...scopeAncestors ?? [], scopeId].filter(Boolean);
|
|
710
|
-
if (allScopes.some((id) => failedScopes.has(id))) {
|
|
711
|
-
skipReason = "Skipped — scope hook failed";
|
|
712
|
-
} else if (mainRequestId && skipRequests.has(mainRequestId)) {
|
|
713
|
-
skipReason = "Skipped — before hook failed";
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
} else {
|
|
717
|
-
const allScopes = [...scopeAncestors ?? [], scopeId].filter(Boolean);
|
|
718
|
-
if (allScopes.some((id) => failedScopes.has(id))) {
|
|
719
|
-
skipReason = "Skipped — beforeAll hook failed";
|
|
720
|
-
} else if (skipRequests.has(item.request.id)) {
|
|
721
|
-
skipReason = "Skipped — before hook failed";
|
|
722
|
-
}
|
|
723
|
-
}
|
|
465
|
+
const { isHook, hookType, scopeId } = item;
|
|
466
|
+
const skipReason = skipTracker.shouldSkip(item);
|
|
724
467
|
let result;
|
|
725
468
|
if (skipReason) {
|
|
726
469
|
result = {
|
|
@@ -736,16 +479,17 @@ async function main() {
|
|
|
736
479
|
scopePath: item.scopePath
|
|
737
480
|
};
|
|
738
481
|
} else {
|
|
739
|
-
const out = await
|
|
740
|
-
item.request,
|
|
741
|
-
{ ...item.collectionVars, ...runCollectionVars },
|
|
742
|
-
runEnvVars,
|
|
743
|
-
runGlobals,
|
|
744
|
-
{ ...runLocalVars },
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
482
|
+
const out = await requestCollection.executeRunnerRequest({
|
|
483
|
+
req: item.request,
|
|
484
|
+
collectionVars: { ...item.collectionVars, ...runCollectionVars },
|
|
485
|
+
envVars: runEnvVars,
|
|
486
|
+
globals: runGlobals,
|
|
487
|
+
localVars: { ...runLocalVars },
|
|
488
|
+
dispatcher,
|
|
489
|
+
piiMaskPatterns: piiPatterns,
|
|
490
|
+
tls: effectiveTls,
|
|
491
|
+
onScriptOutput
|
|
492
|
+
});
|
|
749
493
|
result = out.result;
|
|
750
494
|
runEnvVars = out.updatedEnvVars;
|
|
751
495
|
runCollectionVars = out.updatedCollectionVars;
|
|
@@ -755,17 +499,11 @@ async function main() {
|
|
|
755
499
|
result.hookType = hookType;
|
|
756
500
|
result.scopeId = scopeId;
|
|
757
501
|
result.scopePath = item.scopePath;
|
|
758
|
-
|
|
759
|
-
if (isHook && hookType === "beforeAll" && scopeId) {
|
|
760
|
-
failedScopes.add(scopeId);
|
|
761
|
-
} else if (isHook && hookType === "before" && mainRequestId) {
|
|
762
|
-
skipRequests.add(mainRequestId);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
502
|
+
skipTracker.recordResult(item, result.status);
|
|
765
503
|
}
|
|
766
504
|
const scopeKey = (item.scopePath ?? []).join(" / ");
|
|
767
505
|
if (scopeKey !== lastPrintedScope) {
|
|
768
|
-
if (scopeKey) console.log(color(` ${scopeKey}`, C.gray, C.bold));
|
|
506
|
+
if (scopeKey) console.log(cliCommon.color(` ${scopeKey}`, cliCommon.C.gray, cliCommon.C.bold));
|
|
769
507
|
lastPrintedScope = scopeKey;
|
|
770
508
|
}
|
|
771
509
|
printResult(result, verbose);
|
|
@@ -776,7 +514,7 @@ async function main() {
|
|
|
776
514
|
else if (result.status === "skipped") summary.skipped++;
|
|
777
515
|
else summary.errors++;
|
|
778
516
|
if (bail && (result.status === "failed" || result.status === "error")) {
|
|
779
|
-
console.log(color("\n Bailing after first failure.", C.yellow));
|
|
517
|
+
console.log(cliCommon.color("\n Bailing after first failure.", cliCommon.C.yellow));
|
|
780
518
|
bailed = true;
|
|
781
519
|
break;
|
|
782
520
|
}
|
|
@@ -785,11 +523,11 @@ async function main() {
|
|
|
785
523
|
if (bailed) break;
|
|
786
524
|
}
|
|
787
525
|
summary.durationMs = Date.now() - totalStart;
|
|
788
|
-
const passStr = color(`${summary.passed} passed`, C.green, C.bold);
|
|
789
|
-
const failStr = summary.failed > 0 ? color(` · ${summary.failed} failed`, C.red, C.bold) : "";
|
|
790
|
-
const errStr = summary.errors > 0 ? color(` · ${summary.errors} errors`, C.yellow, C.bold) : "";
|
|
791
|
-
const skipStr = summary.skipped > 0 ? color(` · ${summary.skipped} skipped`, C.gray, C.bold) : "";
|
|
792
|
-
const totalStr = color(` · ${summary.total} total · ${summary.durationMs}ms`, C.gray);
|
|
526
|
+
const passStr = cliCommon.color(`${summary.passed} passed`, cliCommon.C.green, cliCommon.C.bold);
|
|
527
|
+
const failStr = summary.failed > 0 ? cliCommon.color(` · ${summary.failed} failed`, cliCommon.C.red, cliCommon.C.bold) : "";
|
|
528
|
+
const errStr = summary.errors > 0 ? cliCommon.color(` · ${summary.errors} errors`, cliCommon.C.yellow, cliCommon.C.bold) : "";
|
|
529
|
+
const skipStr = summary.skipped > 0 ? cliCommon.color(` · ${summary.skipped} skipped`, cliCommon.C.gray, cliCommon.C.bold) : "";
|
|
530
|
+
const totalStr = cliCommon.color(` · ${summary.total} total · ${summary.durationMs}ms`, cliCommon.C.gray);
|
|
793
531
|
console.log(` ${passStr}${failStr}${errStr}${skipStr}${totalStr}
|
|
794
532
|
`);
|
|
795
533
|
if (outputPath) {
|
|
@@ -797,12 +535,12 @@ async function main() {
|
|
|
797
535
|
const maskedResults = allResults.map(maskResult);
|
|
798
536
|
const report = outputFormat === "junit" ? buildJUnitReport(maskedResults, summary, meta) : outputFormat === "html" ? buildHtmlReport(maskedResults, summary, meta) : buildJsonReport(maskedResults, summary, meta);
|
|
799
537
|
await promises.writeFile(path.resolve(outputPath), report, "utf8");
|
|
800
|
-
console.log(color(` Report written: ${outputPath} (${outputFormat})
|
|
801
|
-
`, C.gray));
|
|
538
|
+
console.log(cliCommon.color(` Report written: ${outputPath} (${outputFormat})
|
|
539
|
+
`, cliCommon.C.gray));
|
|
802
540
|
}
|
|
803
541
|
process.exit(summary.failed + summary.errors > 0 ? 1 : 0);
|
|
804
542
|
}
|
|
805
543
|
main().catch((err) => {
|
|
806
|
-
console.error(color(`Fatal: ${err.message}`, C.red));
|
|
544
|
+
console.error(cliCommon.color(`Fatal: ${err.message}`, cliCommon.C.red));
|
|
807
545
|
process.exit(2);
|
|
808
546
|
});
|
package/out/main/wsdl.js
CHANGED
|
@@ -4,27 +4,12 @@ const promises = require("fs/promises");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const https = require("https");
|
|
6
6
|
const http = require("http");
|
|
7
|
-
const soapHandler = require("./chunks/soap-handler-
|
|
8
|
-
const _import = require("./chunks/import-
|
|
7
|
+
const soapHandler = require("./chunks/soap-handler-B9x_YCtj.js");
|
|
8
|
+
const _import = require("./chunks/import-C5Ngq8hk.js");
|
|
9
|
+
const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
|
|
10
|
+
require("./chunks/handle-C0IQL-Vl.js");
|
|
9
11
|
require("@xmldom/xmldom");
|
|
10
12
|
require("uuid");
|
|
11
|
-
function parseArgs(argv) {
|
|
12
|
-
const args = {};
|
|
13
|
-
for (let i = 0; i < argv.length; i++) {
|
|
14
|
-
const arg = argv[i];
|
|
15
|
-
if (arg.startsWith("--")) {
|
|
16
|
-
const key = arg.slice(2);
|
|
17
|
-
const next = argv[i + 1];
|
|
18
|
-
if (!next || next.startsWith("--")) {
|
|
19
|
-
args[key] = true;
|
|
20
|
-
} else {
|
|
21
|
-
args[key] = next;
|
|
22
|
-
i++;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return args;
|
|
27
|
-
}
|
|
28
13
|
function fetchUrl(url) {
|
|
29
14
|
return new Promise((resolveP, rejectP) => {
|
|
30
15
|
const lib = url.startsWith("https") ? https : http;
|
|
@@ -41,19 +26,6 @@ function fetchUrl(url) {
|
|
|
41
26
|
});
|
|
42
27
|
});
|
|
43
28
|
}
|
|
44
|
-
async function resolveWorkspacePath(wsPath) {
|
|
45
|
-
const s = await promises.stat(wsPath);
|
|
46
|
-
if (!s.isDirectory()) return wsPath;
|
|
47
|
-
const entries = await promises.readdir(wsPath);
|
|
48
|
-
const spector = entries.find((e) => e.endsWith(".spector"));
|
|
49
|
-
if (!spector) throw new Error(`No .spector workspace file found in directory: ${wsPath}`);
|
|
50
|
-
return path.join(wsPath, spector);
|
|
51
|
-
}
|
|
52
|
-
async function loadWorkspace(wsPath) {
|
|
53
|
-
const resolved = await resolveWorkspacePath(wsPath);
|
|
54
|
-
const raw = await promises.readFile(resolved, "utf8");
|
|
55
|
-
return { workspace: JSON.parse(raw), dir: path.dirname(path.resolve(resolved)), file: resolved };
|
|
56
|
-
}
|
|
57
29
|
async function ensureDir(dir) {
|
|
58
30
|
await promises.mkdir(dir, { recursive: true });
|
|
59
31
|
}
|
|
@@ -89,16 +61,8 @@ async function cmdDescribe(args) {
|
|
|
89
61
|
console.log("");
|
|
90
62
|
}
|
|
91
63
|
async function loadExistingMockPorts(workspace, dir) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const raw = await promises.readFile(path.join(dir, relPath), "utf8");
|
|
96
|
-
const m = JSON.parse(raw);
|
|
97
|
-
if (typeof m.port === "number") ports.push(m.port);
|
|
98
|
-
} catch {
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return ports;
|
|
64
|
+
const mocks = await cliCommon.loadMocks(workspace, dir);
|
|
65
|
+
return mocks.map((m) => m.port).filter((p) => typeof p === "number");
|
|
102
66
|
}
|
|
103
67
|
async function cmdImportCollection(args) {
|
|
104
68
|
const url = typeof args["url"] === "string" ? args["url"] : void 0;
|
|
@@ -112,7 +76,7 @@ async function cmdImportCollection(args) {
|
|
|
112
76
|
process.exit(2);
|
|
113
77
|
}
|
|
114
78
|
const wsdlText = await fetchUrl(url);
|
|
115
|
-
const { workspace, dir, file } = await loadWorkspace(wsArg);
|
|
79
|
+
const { workspace, dir, file } = await cliCommon.loadWorkspace(wsArg);
|
|
116
80
|
const name = typeof args["name"] === "string" ? args["name"] : void 0;
|
|
117
81
|
const { collection } = _import.importWsdl(wsdlText, { name });
|
|
118
82
|
const relPath = _import.defaultCollectionRelPath(workspace, collection);
|
|
@@ -135,7 +99,7 @@ async function cmdImportMock(args) {
|
|
|
135
99
|
process.exit(2);
|
|
136
100
|
}
|
|
137
101
|
const wsdlText = await fetchUrl(url);
|
|
138
|
-
const { workspace, dir, file } = await loadWorkspace(wsArg);
|
|
102
|
+
const { workspace, dir, file } = await cliCommon.loadWorkspace(wsArg);
|
|
139
103
|
const existingPorts = await loadExistingMockPorts(workspace, dir);
|
|
140
104
|
const name = typeof args["name"] === "string" ? args["name"] : void 0;
|
|
141
105
|
const { mock } = _import.importWsdl(wsdlText, { name, existingMockPorts: existingPorts });
|
|
@@ -153,7 +117,7 @@ async function cmdImportMock(args) {
|
|
|
153
117
|
}
|
|
154
118
|
async function main() {
|
|
155
119
|
const [, , sub, ...rest] = process.argv;
|
|
156
|
-
const args = parseArgs(rest);
|
|
120
|
+
const args = cliCommon.parseArgs(rest);
|
|
157
121
|
if (sub === "describe") return cmdDescribe(args);
|
|
158
122
|
if (sub === "import-collection") return cmdImportCollection(args);
|
|
159
123
|
if (sub === "import-mock") return cmdImportMock(args);
|