@testsmith/api-spector 0.6.1 → 0.6.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.
Files changed (30) hide show
  1. package/out/main/agents.js +2 -1
  2. package/out/main/chunks/{snapshots-Y33M1SEy.js → broker-client-WEymdqAK.js} +119 -10
  3. package/out/main/chunks/{cli-common-BKYgsbGB.js → cli-common-vASoDfQH.js} +3 -2
  4. package/out/main/chunks/data-files-Br2h3Dt2.js +85 -0
  5. package/out/main/chunks/escape-BwPM4Tf6.js +9 -0
  6. package/out/main/chunks/{handle-BFaeFA0A.js → handle-CL-nWtYA.js} +11 -2
  7. package/out/main/chunks/{import-D9LH0isB.js → import-DdXhO56X.js} +2 -2
  8. package/out/main/chunks/{request-collection-BQIo1765.js → request-collection-GzK4QgvT.js} +78 -17
  9. package/out/main/chunks/{request-exec-5_3279w1.js → request-exec-DAFA6Fap.js} +19 -1
  10. package/out/main/chunks/{soap-handler-CWn3o6Py.js → soap-handler-BAPCALkN.js} +2 -2
  11. package/out/main/compare.js +2 -1
  12. package/out/main/contract.js +50 -147
  13. package/out/main/coverage.js +2 -1
  14. package/out/main/generate-tests.js +2 -1
  15. package/out/main/index.js +134 -36
  16. package/out/main/lib.js +2 -2
  17. package/out/main/mock.js +2 -1
  18. package/out/main/record.js +2 -1
  19. package/out/main/runner.js +117 -20
  20. package/out/main/wsdl.js +5 -4
  21. package/out/preload/index.js +21 -3
  22. package/out/renderer/assets/{GraphQLEditor-8wU1UeBL.js → GraphQLEditor-MImdgXb4.js} +3 -1
  23. package/out/renderer/assets/{SoapEditor-DQwLLqCX.js → SoapEditor-rxb3heue.js} +4 -2
  24. package/out/renderer/assets/ajv-2MAg6kGF.js +6153 -0
  25. package/out/renderer/assets/codemirror-F6m3Hm1M.js +26552 -0
  26. package/out/renderer/assets/index-B3mtLdU3.css +2 -0
  27. package/out/renderer/assets/{index-CdxTj7i6.js → index-ByGIqJ2H.js} +17526 -49312
  28. package/out/renderer/index.html +4 -2
  29. package/package.json +1 -1
  30. package/out/renderer/assets/index-CkHujZU3.css +0 -2
@@ -2,7 +2,8 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
5
+ const cliCommon = require("./chunks/cli-common-vASoDfQH.js");
6
+ require("./chunks/data-files-Br2h3Dt2.js");
6
7
  require("crypto");
7
8
  const AGENTS = {
8
9
  claude: {
@@ -3,10 +3,11 @@ const promises = require("fs/promises");
3
3
  const path = require("path");
4
4
  const crypto = require("crypto");
5
5
  const undici = require("undici");
6
- const requestExec = require("./request-exec-5_3279w1.js");
6
+ const requestExec = require("./request-exec-DAFA6Fap.js");
7
7
  const Ajv = require("ajv");
8
8
  const jsYaml = require("js-yaml");
9
9
  require("http");
10
+ const escape = require("./escape-BwPM4Tf6.js");
10
11
  const MATCH_KEY = "__match";
11
12
  function isMatcher$1(node) {
12
13
  return typeof node === "object" && node !== null && !Array.isArray(node) && typeof node[MATCH_KEY] === "string";
@@ -873,7 +874,7 @@ function findOperation(spec, method, reqUrl, requestBaseUrl) {
873
874
  function validateRequestAgainstSpec(spec, req, envVars, requestBaseUrl) {
874
875
  const violations = [];
875
876
  const vars = envVars;
876
- const url = req.url.replace(/\{\{([^}]+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`);
877
+ const url = requestExec.interpolate(req.url, vars);
877
878
  const match = findOperation(spec, req.method, url, requestBaseUrl);
878
879
  if (!match) {
879
880
  violations.push({
@@ -932,7 +933,7 @@ async function runProviderVerification(requests, envVars, collectionVars = {}, s
932
933
  const activeRequests = requests.filter((r) => !r.disabled);
933
934
  const results = activeRequests.map((req) => {
934
935
  const violations = validateRequestAgainstSpec(spec, req, vars, requestBaseUrl);
935
- const url = req.url.replace(/\{\{([^}]+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`);
936
+ const url = requestExec.interpolate(req.url, vars);
936
937
  return {
937
938
  requestId: req.id,
938
939
  requestName: req.name,
@@ -1121,7 +1122,7 @@ function checkSchemaCompatibility(consumerSchema, providerSchema, path2 = "") {
1121
1122
  return violations;
1122
1123
  }
1123
1124
  function getProviderResponseSchema(spec, req, envVars, statusCode, requestBaseUrl) {
1124
- const url = req.url.replace(/\{\{([^}]+)\}\}/g, (_, k) => envVars[k] ?? `{{${k}}}`);
1125
+ const url = requestExec.interpolate(req.url, envVars);
1125
1126
  const match = findOperation(spec, req.method, url, requestBaseUrl);
1126
1127
  if (!match) return null;
1127
1128
  const responses = match.operation["responses"] ?? {};
@@ -1280,7 +1281,7 @@ async function listResults(dir) {
1280
1281
  }
1281
1282
  return out;
1282
1283
  }
1283
- async function canIDeploy(dir, pacticipant, version, env) {
1284
+ async function canIDeploy$1(dir, pacticipant, version, env) {
1284
1285
  const file = resultPath(dir, pacticipant, version);
1285
1286
  let currentlyDeployed;
1286
1287
  if (env) {
@@ -1323,7 +1324,7 @@ async function loadEnvironment(dir, env) {
1323
1324
  return null;
1324
1325
  }
1325
1326
  }
1326
- async function recordDeployment(dir, env, pacticipant, version, now) {
1327
+ async function recordDeployment$1(dir, env, pacticipant, version, now) {
1327
1328
  const state = await loadEnvironment(dir, env) ?? { name: env, deployed: {} };
1328
1329
  const previous = state.deployed[pacticipant];
1329
1330
  state.deployed[pacticipant] = { version, recordedAt: now };
@@ -1756,7 +1757,7 @@ const ajv = new Ajv({ allErrors: true, strict: false });
1756
1757
  const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
1757
1758
  function specContextFor(spec, req, vars, requestBaseUrl) {
1758
1759
  if (!spec) return null;
1759
- const url = req.url.replace(/\{\{([^}]+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`);
1760
+ const url = requestExec.interpolate(req.url, vars);
1760
1761
  const match = findOperation(spec, req.method, url, requestBaseUrl);
1761
1762
  if (!match) return null;
1762
1763
  const op = match.operation;
@@ -1982,9 +1983,7 @@ async function runFuzz(opts) {
1982
1983
  skippedNoBody
1983
1984
  };
1984
1985
  }
1985
- function esc(s) {
1986
- return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1987
- }
1986
+ const esc = escape.escapeHtml;
1988
1987
  function modeLabel(mode) {
1989
1988
  if (mode === "bidirectional") return "Bi-directional";
1990
1989
  if (mode === "provider-live") return "Provider (live)";
@@ -2299,12 +2298,117 @@ async function deleteSnapshot(workspaceDir, relPath) {
2299
2298
  } catch {
2300
2299
  }
2301
2300
  }
2301
+ const DEFAULT_CLOUD_ENDPOINT = "https://api-spector.dev";
2302
+ function resolveCloudEndpoint() {
2303
+ return (process.env["API_SPECTOR_CLOUD_ENDPOINT"] || DEFAULT_CLOUD_ENDPOINT).replace(/\/+$/, "");
2304
+ }
2305
+ function brokerConfigFromEnv() {
2306
+ return {
2307
+ endpoint: resolveCloudEndpoint(),
2308
+ token: process.env["API_SPECTOR_TOKEN"] || ""
2309
+ };
2310
+ }
2311
+ async function brokerFetch(cfg, path2, method, body) {
2312
+ if (!cfg.token) throw new Error("No API token. Set API_SPECTOR_TOKEN (create one in the cloud dashboard under Tokens).");
2313
+ const url = cfg.endpoint + path2;
2314
+ let res;
2315
+ try {
2316
+ res = await undici.fetch(url, {
2317
+ method,
2318
+ headers: {
2319
+ "Content-Type": "application/json",
2320
+ Accept: "application/json",
2321
+ Authorization: `Bearer ${cfg.token}`
2322
+ },
2323
+ body: body === void 0 ? void 0 : JSON.stringify(body)
2324
+ });
2325
+ } catch (err) {
2326
+ throw new Error(`Could not reach ${url}: ${err.message}`);
2327
+ }
2328
+ const text = await res.text();
2329
+ let json;
2330
+ try {
2331
+ json = text ? JSON.parse(text) : {};
2332
+ } catch {
2333
+ json = { raw: text };
2334
+ }
2335
+ return { status: res.status, ok: res.ok, json };
2336
+ }
2337
+ function fail(r, what) {
2338
+ const detail = r.json?.error || r.json?.message || (r.status === 401 ? "Unauthorized (check API_SPECTOR_TOKEN)" : `HTTP ${r.status}`);
2339
+ throw new Error(`${what}: ${detail}`);
2340
+ }
2341
+ async function publishPact(cfg, input) {
2342
+ const r = await brokerFetch(cfg, "/api/contracts", "PUT", {
2343
+ consumer: input.consumer,
2344
+ consumerVersion: input.consumerVersion,
2345
+ provider: input.provider,
2346
+ content: input.pact
2347
+ });
2348
+ if (!r.ok) fail(r, "Publish pact failed");
2349
+ }
2350
+ async function publishSpec(cfg, input) {
2351
+ const r = await brokerFetch(cfg, "/api/provider-contracts", "PUT", {
2352
+ pacticipant: input.pacticipant,
2353
+ version: input.version,
2354
+ spec: input.spec,
2355
+ // Optional provider self-verification (its own tests run against this spec).
2356
+ ...input.results ? { results: input.results } : {}
2357
+ });
2358
+ if (!r.ok) fail(r, "Publish spec failed");
2359
+ }
2360
+ async function canIDeploy(cfg, input) {
2361
+ const q = new URLSearchParams({ pacticipant: input.pacticipant, version: input.version, environment: input.environment }).toString();
2362
+ const r = await brokerFetch(cfg, "/can-i-deploy?" + q, "GET");
2363
+ if (r.status !== 200 && r.status !== 409) fail(r, "can-i-deploy failed");
2364
+ return { deployable: r.status === 200, reason: r.json?.summary?.reason || "" };
2365
+ }
2366
+ async function deployPreview(cfg, input) {
2367
+ const q = new URLSearchParams({ pacticipant: input.pacticipant, version: input.version, environment: input.environment }).toString();
2368
+ const r = await brokerFetch(cfg, "/api/deploy-preview?" + q, "GET");
2369
+ if (!r.ok) fail(r, "deploy-preview failed");
2370
+ return r.json;
2371
+ }
2372
+ async function recordDeployment(cfg, input) {
2373
+ const path2 = `/pacticipants/${encodeURIComponent(input.pacticipant)}/versions/${encodeURIComponent(input.version)}/deployed-versions/environment/${encodeURIComponent(input.environment)}`;
2374
+ const r = await brokerFetch(cfg, path2, "POST", {});
2375
+ if (!r.ok) fail(r, "record-deployment failed");
2376
+ }
2377
+ async function checkCompatibility(cfg, input) {
2378
+ const q = new URLSearchParams({ consumer: input.consumer, consumerVersion: input.consumerVersion, provider: input.provider, providerVersion: input.providerVersion }).toString();
2379
+ const r = await brokerFetch(cfg, "/api/compatibility?" + q, "GET");
2380
+ if (r.status !== 200 && r.status !== 409) fail(r, "Compatibility check failed");
2381
+ return { compatible: r.json?.compatible === true, checks: r.json?.checks ?? [] };
2382
+ }
2383
+ async function fetchContracts(cfg, input = {}) {
2384
+ const q = new URLSearchParams();
2385
+ if (input.consumer) q.set("consumer", input.consumer);
2386
+ if (input.provider) q.set("provider", input.provider);
2387
+ const suffix = q.toString() ? "?" + q.toString() : "";
2388
+ const r = await brokerFetch(cfg, "/api/contracts" + suffix, "GET");
2389
+ if (!r.ok) fail(r, "Fetch contracts failed");
2390
+ return r.json?.contracts ?? [];
2391
+ }
2392
+ async function publishVerification(cfg, input) {
2393
+ const r = await brokerFetch(cfg, "/api/verifications", "POST", {
2394
+ contractId: input.contractId,
2395
+ providerVersion: input.providerVersion,
2396
+ success: input.success,
2397
+ ...input.buildUrl ? { buildUrl: input.buildUrl } : {}
2398
+ });
2399
+ if (!r.ok) fail(r, "Publish verification failed");
2400
+ }
2401
+ exports.brokerConfigFromEnv = brokerConfigFromEnv;
2302
2402
  exports.canIDeploy = canIDeploy;
2403
+ exports.canIDeploy$1 = canIDeploy$1;
2303
2404
  exports.captureSnapshot = captureSnapshot;
2405
+ exports.checkCompatibility = checkCompatibility;
2304
2406
  exports.dashboardToHtml = dashboardToHtml;
2305
2407
  exports.deleteSnapshot = deleteSnapshot;
2408
+ exports.deployPreview = deployPreview;
2306
2409
  exports.designContractToPact = designContractToPact;
2307
2410
  exports.exportPact = exportPact;
2411
+ exports.fetchContracts = fetchContracts;
2308
2412
  exports.fuzzReportToHtml = fuzzReportToHtml;
2309
2413
  exports.hasContract = hasContract;
2310
2414
  exports.importPact = importPact;
@@ -2314,10 +2418,15 @@ exports.listSnapshots = listSnapshots;
2314
2418
  exports.loadDesignContractRequests = loadDesignContractRequests;
2315
2419
  exports.loadSnapshot = loadSnapshot;
2316
2420
  exports.pactToCollection = pactToCollection;
2421
+ exports.publishPact = publishPact;
2422
+ exports.publishSpec = publishSpec;
2423
+ exports.publishVerification = publishVerification;
2317
2424
  exports.recordDeployment = recordDeployment;
2425
+ exports.recordDeployment$1 = recordDeployment$1;
2318
2426
  exports.recordResult = recordResult;
2319
2427
  exports.relPathOf = relPathOf;
2320
2428
  exports.reportToHtml = reportToHtml;
2429
+ exports.resolveCloudEndpoint = resolveCloudEndpoint;
2321
2430
  exports.runBidirectional = runBidirectional;
2322
2431
  exports.runConsumerContracts = runConsumerContracts;
2323
2432
  exports.runFuzz = runFuzz;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  const promises = require("fs/promises");
3
3
  const path = require("path");
4
+ const dataFiles = require("./data-files-Br2h3Dt2.js");
4
5
  require("crypto");
5
6
  const C = {
6
7
  reset: "\x1B[0m",
@@ -57,8 +58,8 @@ async function loadCollections(workspace, dir, opts = {}) {
57
58
  const cols = [];
58
59
  for (const relPath of workspace.collections) {
59
60
  try {
60
- const raw = await promises.readFile(path.join(dir, relPath), "utf8");
61
- const col = JSON.parse(raw);
61
+ const fullPath = path.join(dir, relPath);
62
+ const col = await dataFiles.inlineDataSets(JSON.parse(await promises.readFile(fullPath, "utf8")), path.dirname(fullPath));
62
63
  if (!opts.filterName || col.name === opts.filterName) cols.push(col);
63
64
  } catch {
64
65
  opts.onError?.(relPath);
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ const path = require("path");
3
+ const promises = require("fs/promises");
4
+ function toCSV(ds) {
5
+ const escape = (s) => s.includes(",") || s.includes('"') || s.includes("\n") ? `"${s.replace(/"/g, '""')}"` : s;
6
+ return [ds.columns, ...ds.rows].map((row) => row.map(escape).join(",")).join("\n");
7
+ }
8
+ function parseCSV(text) {
9
+ const lines = text.trim().split(/\r?\n/).filter(Boolean);
10
+ if (lines.length === 0) return { columns: [], rows: [] };
11
+ function splitRow(line) {
12
+ const cells = [];
13
+ let cur = "";
14
+ let inQuote = false;
15
+ for (let i = 0; i < line.length; i++) {
16
+ const ch = line[i];
17
+ if (ch === '"') {
18
+ if (inQuote && line[i + 1] === '"') {
19
+ cur += '"';
20
+ i++;
21
+ } else inQuote = !inQuote;
22
+ } else if (ch === "," && !inQuote) {
23
+ cells.push(cur.trim());
24
+ cur = "";
25
+ } else {
26
+ cur += ch;
27
+ }
28
+ }
29
+ cells.push(cur.trim());
30
+ return cells;
31
+ }
32
+ const columns = splitRow(lines[0]);
33
+ const rows = lines.slice(1).map(splitRow);
34
+ return { columns, rows };
35
+ }
36
+ const DATA_DIR = "data";
37
+ const hasRows = (ds) => !!ds && ds.columns.length > 0;
38
+ const refFor = (id) => `${DATA_DIR}/${id}.csv`;
39
+ async function externalizeDataSets(col, colDir) {
40
+ const clone = JSON.parse(JSON.stringify(col));
41
+ const writes = [];
42
+ const removals = [];
43
+ const handle = (h) => {
44
+ if (hasRows(h.dataSet)) {
45
+ const ref = refFor(h.id);
46
+ writes.push({ path: path.join(colDir, ref), content: toCSV(h.dataSet) });
47
+ h.dataSetRef = ref;
48
+ } else if (h.dataSet || h.dataSetRef) {
49
+ removals.push(path.join(colDir, refFor(h.id)));
50
+ delete h.dataSetRef;
51
+ }
52
+ delete h.dataSet;
53
+ };
54
+ handle(clone);
55
+ const walk = (f) => {
56
+ handle(f);
57
+ f.folders.forEach(walk);
58
+ };
59
+ walk(clone.rootFolder);
60
+ if (writes.length) await promises.mkdir(path.join(colDir, DATA_DIR), { recursive: true });
61
+ await Promise.all(writes.map((w) => promises.writeFile(w.path, w.content, "utf8")));
62
+ await Promise.all(removals.map((p) => promises.unlink(p).catch(() => {
63
+ })));
64
+ return clone;
65
+ }
66
+ async function inlineDataSets(col, colDir) {
67
+ const load = async (h) => {
68
+ if (!h.dataSetRef) return;
69
+ try {
70
+ const text = await promises.readFile(path.join(colDir, h.dataSetRef), "utf8");
71
+ h.dataSet = parseCSV(text);
72
+ } catch {
73
+ }
74
+ delete h.dataSetRef;
75
+ };
76
+ await load(col);
77
+ const walk = async (f) => {
78
+ await load(f);
79
+ await Promise.all(f.folders.map(walk));
80
+ };
81
+ await walk(col.rootFolder);
82
+ return col;
83
+ }
84
+ exports.externalizeDataSets = externalizeDataSets;
85
+ exports.inlineDataSets = inlineDataSets;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ function escapeHtml(s) {
3
+ return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4
+ }
5
+ function escapeXml(s) {
6
+ return escapeHtml(s).replace(/'/g, "&apos;");
7
+ }
8
+ exports.escapeHtml = escapeHtml;
9
+ exports.escapeXml = escapeXml;
@@ -39,7 +39,13 @@ const IPC = {
39
39
  secret: {
40
40
  checkMasterKey: "secret:checkMasterKey",
41
41
  setMasterKey: "secret:setMasterKey",
42
- set: "secret:set"
42
+ set: "secret:set",
43
+ has: "secret:has",
44
+ delete: "secret:delete"
45
+ },
46
+ // ─── AI (optional; key lives only in the OS keychain) ────────────────────────
47
+ ai: {
48
+ generateDocs: "ai:generateDocs"
43
49
  },
44
50
  // ─── Global variables ──────────────────────────────────────────────────────
45
51
  globals: {
@@ -163,6 +169,7 @@ const IPC = {
163
169
  branches: "git:branches",
164
170
  checkout: "git:checkout",
165
171
  deleteBranch: "git:deleteBranch",
172
+ fetch: "git:fetch",
166
173
  pull: "git:pull",
167
174
  push: "git:push",
168
175
  remotes: "git:remotes",
@@ -172,7 +179,9 @@ const IPC = {
172
179
  writeCiFile: "git:writeCiFile",
173
180
  resolveOurs: "git:resolveOurs",
174
181
  resolveTheirs: "git:resolveTheirs",
175
- markResolved: "git:markResolved"
182
+ markResolved: "git:markResolved",
183
+ readConflict: "git:readConflict",
184
+ writeResolved: "git:writeResolved"
176
185
  },
177
186
  // ─── Recorder ──────────────────────────────────────────────────────────────
178
187
  record: {
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const uuid = require("uuid");
4
- const soapHandler = require("./soap-handler-CWn3o6Py.js");
5
- require("./handle-BFaeFA0A.js");
4
+ const soapHandler = require("./soap-handler-BAPCALkN.js");
5
+ require("./handle-CL-nWtYA.js");
6
6
  require("https");
7
7
  require("http");
8
8
  require("@xmldom/xmldom");
@@ -25,17 +25,49 @@ function findFolder(root, id) {
25
25
  }
26
26
  return null;
27
27
  }
28
+ function flattenFolderRequests(folder, requests) {
29
+ const out = [];
30
+ for (const child of orderedChildren(folder)) {
31
+ if (child.type === "request") {
32
+ const r = requests[child.id];
33
+ if (r && !r.disabled) out.push(r);
34
+ } else {
35
+ const sub = folder.folders.find((f) => f.id === child.id);
36
+ if (sub && !sub.disabled) out.push(...flattenFolderRequests(sub, requests));
37
+ }
38
+ }
39
+ return out;
40
+ }
28
41
  function makeHook(req, collectionVars, hookType, scopeId, scopeAncestors, scopePath, mainRequestId) {
29
42
  return { request: req, collectionVars, isHook: true, hookType, scopeId, scopeAncestors, scopePath, mainRequestId };
30
43
  }
31
44
  function buildFolderPlan(folder, requests, collectionVars, filterTags, scopeId, ancestorIds, parentPath, wrappers, isRoot) {
32
45
  const result = [];
33
46
  const scopePath = isRoot ? [] : [...parentPath, folder.name];
34
- const folderReqs = folder.requestIds.map((id) => requests[id]).filter((r) => r && !r.disabled);
35
- const beforeAllHooks = folderReqs.filter((r) => r.hookType === "beforeAll");
36
- const beforeHooks = folderReqs.filter((r) => r.hookType === "before");
37
- const afterHooks = folderReqs.filter((r) => r.hookType === "after");
38
- const afterAllHooks = folderReqs.filter((r) => r.hookType === "afterAll");
47
+ const folderReqs = orderedChildren(folder).filter((c) => c.type === "request").map((c) => requests[c.id]).filter((r) => r && !r.disabled);
48
+ const beforeAllHooks = [];
49
+ const beforeHooks = [];
50
+ const afterHooks = [];
51
+ const afterAllHooks = [];
52
+ const hookFolderIds = /* @__PURE__ */ new Set();
53
+ const pushHook = (ht, reqs) => {
54
+ if (ht === "beforeAll") beforeAllHooks.push(...reqs);
55
+ else if (ht === "before") beforeHooks.push(...reqs);
56
+ else if (ht === "after") afterHooks.push(...reqs);
57
+ else if (ht === "afterAll") afterAllHooks.push(...reqs);
58
+ };
59
+ for (const child of orderedChildren(folder)) {
60
+ if (child.type === "request") {
61
+ const r = requests[child.id];
62
+ if (r && !r.disabled && r.hookType) pushHook(r.hookType, [r]);
63
+ } else {
64
+ const sub = folder.folders.find((f) => f.id === child.id);
65
+ if (sub && !sub.disabled && sub.hookType) {
66
+ hookFolderIds.add(sub.id);
67
+ pushHook(sub.hookType, flattenFolderRequests(sub, requests));
68
+ }
69
+ }
70
+ }
39
71
  const regularReqs = folderReqs.filter((r) => !r.hookType);
40
72
  const myWrapper = { scopeId, ancestors: ancestorIds, scopePath, before: beforeHooks, after: afterHooks };
41
73
  const allWrappers = [...wrappers, myWrapper];
@@ -63,7 +95,8 @@ function buildFolderPlan(folder, requests, collectionVars, filterTags, scopeId,
63
95
  }
64
96
  } else {
65
97
  const sub = subById.get(child.id);
66
- if (!sub) continue;
98
+ if (!sub || sub.disabled) continue;
99
+ if (hookFolderIds.has(sub.id)) continue;
67
100
  const folderTags = sub.tags ?? [];
68
101
  const effectiveFilter = filterTags.length === 0 ? filterTags : folderTags.some((t) => filterTags.includes(t)) ? [] : filterTags;
69
102
  result.push(...buildFolderPlan(
@@ -151,8 +184,9 @@ function resolveInheritedAuthAndHeaders(requestId, collection) {
151
184
  }
152
185
  function buildRunPlan(collection, folderId, filterTags) {
153
186
  const collectionVars = collection.collectionVariables ?? {};
187
+ const tag = (items) => items.map((i) => ({ ...i, collection: collection.name }));
154
188
  {
155
- return buildFolderPlan(
189
+ return tag(buildFolderPlan(
156
190
  collection.rootFolder,
157
191
  collection.requests,
158
192
  collectionVars,
@@ -162,18 +196,42 @@ function buildRunPlan(collection, folderId, filterTags) {
162
196
  [],
163
197
  [],
164
198
  true
165
- );
199
+ ));
166
200
  }
167
201
  }
168
202
  function expandRunPlanWithData(items, ds) {
169
203
  if (!ds || ds.rows.length === 0) return items;
170
204
  return ds.rows.flatMap((row, ri) => {
171
- const rowVars = {};
172
- ds.columns.forEach((col, ci) => {
173
- if (col) rowVars[col] = row[ci] ?? "";
174
- });
175
- return items.map((item) => ({ ...item, dataRow: { ...item.dataRow, ...rowVars }, iterationLabel: `${ri + 1}/${ds.rows.length}` }));
205
+ const parsed = parseDataRow(ds.columns, row);
206
+ return items.map((item) => ({
207
+ ...item,
208
+ dataRow: { ...item.dataRow, ...parsed.dataRow },
209
+ iterationLabel: `${ri + 1}/${ds.rows.length}`,
210
+ ...parsed.expectStatus ? { expectStatus: parsed.expectStatus } : {},
211
+ ...parsed.owaspTag ? { owaspTag: parsed.owaspTag } : {}
212
+ }));
213
+ });
214
+ }
215
+ const RESERVED_STATUS = /* @__PURE__ */ new Set(["expectstatus", "expect_status", "expectedstatus", "expected_status"]);
216
+ const RESERVED_OWASP = /* @__PURE__ */ new Set(["owasp", "owasp_tag", "category"]);
217
+ function parseDataRow(columns, row) {
218
+ const dataRow = {};
219
+ let expectStatus;
220
+ let owaspTag;
221
+ columns.forEach((col, ci) => {
222
+ if (!col) return;
223
+ const key = col.toLowerCase();
224
+ const val = row[ci] ?? "";
225
+ if (RESERVED_STATUS.has(key)) {
226
+ const nums = val.split(/[\s,|]+/).map((x) => parseInt(x, 10)).filter((n) => Number.isFinite(n));
227
+ if (nums.length) expectStatus = nums;
228
+ } else if (RESERVED_OWASP.has(key)) {
229
+ if (val.trim()) owaspTag = val.trim().toUpperCase();
230
+ } else {
231
+ dataRow[col] = val;
232
+ }
176
233
  });
234
+ return { dataRow, expectStatus, owaspTag };
177
235
  }
178
236
  function nearestFolderDataSet(item, collection) {
179
237
  const chain = [item.scopeId, ...item.scopeAncestors ?? []].filter(Boolean);
@@ -192,11 +250,14 @@ function expandFolderDataSets(items, collection) {
192
250
  continue;
193
251
  }
194
252
  ds.rows.forEach((row, ri) => {
195
- const rowVars = {};
196
- ds.columns.forEach((col, ci) => {
197
- if (col) rowVars[col] = row[ci] ?? "";
253
+ const parsed = parseDataRow(ds.columns, row);
254
+ out.push({
255
+ ...item,
256
+ dataRow: { ...item.dataRow, ...parsed.dataRow },
257
+ iterationLabel: `${ri + 1}/${ds.rows.length}`,
258
+ ...parsed.expectStatus ? { expectStatus: parsed.expectStatus } : {},
259
+ ...parsed.owaspTag ? { owaspTag: parsed.owaspTag } : {}
198
260
  });
199
- out.push({ ...item, dataRow: { ...item.dataRow, ...rowVars }, iterationLabel: `${ri + 1}/${ds.rows.length}` });
200
261
  });
201
262
  }
202
263
  return out;
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  const undici = require("undici");
25
25
  const promises = require("fs/promises");
26
26
  const nodeTls = require("tls");
27
- const handle = require("./handle-BFaeFA0A.js");
27
+ const handle = require("./handle-CL-nWtYA.js");
28
28
  const fs = require("node:fs");
29
29
  const os = require("node:os");
30
30
  const path$1 = require("node:path");
@@ -582,6 +582,15 @@ function registerSecretHandlers(ipc) {
582
582
  secretStore[ref] = ss.encryptString(value).toString("base64");
583
583
  await persistSecretStore();
584
584
  });
585
+ handle.handleIpc(ipc, handle.IPC.secret.has, async (_e, ref) => {
586
+ return { has: await getSecret(ref) != null };
587
+ });
588
+ handle.handleIpc(ipc, handle.IPC.secret.delete, async (_e, ref) => {
589
+ if (ref in secretStore) {
590
+ delete secretStore[ref];
591
+ await persistSecretStore();
592
+ }
593
+ });
585
594
  }
586
595
  function decryptSecret(encrypted, salt, iv, password) {
587
596
  const saltBuf = Buffer.from(salt, "base64");
@@ -2159,6 +2168,15 @@ async function executeRunnerRequest(opts) {
2159
2168
  await persistGlobals();
2160
2169
  onScriptOutput?.("post", r.consoleOutput, r.error);
2161
2170
  }
2171
+ if (opts.expectStatus && opts.expectStatus.length > 0) {
2172
+ const tag = opts.owaspTag ? `[${opts.owaspTag}] ` : "";
2173
+ const passed = opts.expectStatus.includes(exchange.status);
2174
+ testResults = [...testResults, {
2175
+ name: `${tag}expected status ${opts.expectStatus.join(" or ")}`,
2176
+ passed,
2177
+ error: passed ? void 0 : `got ${exchange.status} ${exchange.statusText}`
2178
+ }];
2179
+ }
2162
2180
  const status = deriveRunStatus(postScriptError, testResults, exchange.status);
2163
2181
  if (exchange.status >= 400 && testResults.length === 0) {
2164
2182
  testResults = [...testResults, syntheticHttpFailure(exchange.status, exchange.statusText)];
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const handle = require("./handle-BFaeFA0A.js");
2
+ const handle = require("./handle-CL-nWtYA.js");
3
3
  const https = require("https");
4
4
  const http = require("http");
5
5
  const xmldom = require("@xmldom/xmldom");
@@ -315,7 +315,7 @@ function registerSoapHandlers(ipc) {
315
315
  handle.handleIpc(ipc, handle.IPC.wsdl.import, async (_event, opts) => {
316
316
  const { validateWsdlImport } = await Promise.resolve().then(() => require("./ipc-validate-sqUFqRmE.js"));
317
317
  validateWsdlImport(opts);
318
- const { importWsdl } = await Promise.resolve().then(() => require("./import-D9LH0isB.js"));
318
+ const { importWsdl } = await Promise.resolve().then(() => require("./import-DdXhO56X.js"));
319
319
  const wsdlText = opts.xml ?? (opts.url ? await fetchUrl(opts.url) : "");
320
320
  if (!wsdlText) throw new Error("wsdl:import requires either `url` or `xml`");
321
321
  return importWsdl(wsdlText, { name: opts.name, existingMockPorts: opts.existingMockPorts });
@@ -4,8 +4,9 @@ const promises = require("fs/promises");
4
4
  const jsYaml = require("js-yaml");
5
5
  const undici = require("undici");
6
6
  const coverage = require("./chunks/coverage-C54mZEh_.js");
7
- const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
7
+ const cliCommon = require("./chunks/cli-common-vASoDfQH.js");
8
8
  require("path");
9
+ require("./chunks/data-files-Br2h3Dt2.js");
9
10
  require("crypto");
10
11
  const HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
11
12
  function resolveRef(spec, ref) {