@testsmith/api-spector 0.2.0 → 0.2.1

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/index.js CHANGED
@@ -25,7 +25,7 @@ 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 requestHandler = require("./chunks/request-handler-AFBOd__c.js");
28
+ const requestCollection = require("./chunks/request-collection-Dx0ZqB54.js");
29
29
  const uuid = require("uuid");
30
30
  const jsYaml = require("js-yaml");
31
31
  const undici = require("undici");
@@ -64,14 +64,14 @@ function registerFileHandlers(ipc) {
64
64
  ipc.handle("file:openWorkspace", async () => {
65
65
  const result = await electron.dialog.showOpenDialog({
66
66
  title: "Open Workspace",
67
- filters: [{ name: "api Spector Workspace", extensions: ["spector", "json"] }],
67
+ filters: [{ name: "API Spector Workspace", extensions: ["spector", "json"] }],
68
68
  properties: ["openFile"]
69
69
  });
70
70
  if (result.canceled || !result.filePaths[0]) return null;
71
71
  const wsPath = result.filePaths[0];
72
72
  workspaceDir = path.dirname(wsPath);
73
73
  workspaceFile = wsPath;
74
- await requestHandler.loadGlobals(workspaceDir);
74
+ await requestCollection.loadGlobals(workspaceDir);
75
75
  await saveLastWorkspacePath(wsPath);
76
76
  const raw = await promises.readFile(wsPath, "utf8");
77
77
  return { workspace: JSON.parse(raw), workspacePath: wsPath };
@@ -80,15 +80,15 @@ function registerFileHandlers(ipc) {
80
80
  const result = await electron.dialog.showSaveDialog({
81
81
  title: "Create Workspace",
82
82
  defaultPath: "my-workspace.spector",
83
- filters: [{ name: "api Spector Workspace", extensions: ["spector", "json"] }]
83
+ filters: [{ name: "API Spector Workspace", extensions: ["spector", "json"] }]
84
84
  });
85
85
  if (result.canceled || !result.filePath) return null;
86
86
  workspaceDir = path.dirname(result.filePath);
87
87
  workspaceFile = result.filePath;
88
- await requestHandler.loadGlobals(workspaceDir);
88
+ await requestCollection.loadGlobals(workspaceDir);
89
89
  await promises.mkdir(path.join(workspaceDir, "collections"), { recursive: true });
90
90
  await promises.mkdir(path.join(workspaceDir, "environments"), { recursive: true });
91
- const gitignore = "# api Spector — never commit secrets\n*.secrets\n.env.local\n\n# Dependencies\nnode_modules/\n";
91
+ const gitignore = "# API Spector — never commit secrets\n*.secrets\n.env.local\n\n# Dependencies\nnode_modules/\n";
92
92
  await atomicWrite(path.join(workspaceDir, ".gitignore"), gitignore);
93
93
  const ws = {
94
94
  version: "1.0",
@@ -149,10 +149,10 @@ function registerFileHandlers(ipc) {
149
149
  await promises.writeFile(result.filePath, content, "utf8");
150
150
  return true;
151
151
  });
152
- ipc.handle("globals:get", () => requestHandler.getGlobals());
152
+ ipc.handle("globals:get", () => requestCollection.getGlobals());
153
153
  ipc.handle("globals:set", async (_e, patch) => {
154
- requestHandler.setGlobals(patch);
155
- await requestHandler.persistGlobals();
154
+ requestCollection.setGlobals(patch);
155
+ await requestCollection.persistGlobals();
156
156
  });
157
157
  ipc.handle("file:closeWorkspace", async () => {
158
158
  workspaceDir = null;
@@ -166,7 +166,7 @@ function registerFileHandlers(ipc) {
166
166
  try {
167
167
  workspaceDir = path.dirname(wsPath);
168
168
  workspaceFile = wsPath;
169
- await requestHandler.loadGlobals(workspaceDir);
169
+ await requestCollection.loadGlobals(workspaceDir);
170
170
  const raw = await promises.readFile(wsPath, "utf8");
171
171
  return { workspace: JSON.parse(raw), workspacePath: wsPath };
172
172
  } catch {
@@ -983,6 +983,60 @@ function registerImportHandlers(ipc) {
983
983
  return extractSchemasFromUrl(url);
984
984
  });
985
985
  }
986
+ function parsePostScript(script) {
987
+ const assertions = [];
988
+ const extractions = [];
989
+ if (!script?.trim()) return { assertions, extractions };
990
+ const testBlockRegex = /sp\.test\(\s*(['"])((?:(?!\1).|\\.)*)\1\s*,\s*(?:function\s*\(\)|\(\)\s*=>)\s*\{([\s\S]*?)\}\s*\)/g;
991
+ let match;
992
+ while (match = testBlockRegex.exec(script)) {
993
+ const name = match[2];
994
+ const body = match[3];
995
+ const equalMatch = body.match(/sp\.expect\(([^)]+)\)\.to(?:\.be)?\.equal\(([^)]+)\)/);
996
+ if (equalMatch) {
997
+ assertions.push({ name, accessor: equalMatch[1].trim(), kind: "equals", expected: equalMatch[2].trim() });
998
+ continue;
999
+ }
1000
+ const includeMatch = body.match(/sp\.expect\(([^)]+)\)\.to\.(?:include|contain)\(([^)]+)\)/);
1001
+ if (includeMatch) {
1002
+ assertions.push({ name, accessor: includeMatch[1].trim(), kind: "contains", expected: includeMatch[2].trim() });
1003
+ continue;
1004
+ }
1005
+ const existsMatch = body.match(/sp\.expect\(([^)]+)\)\.to\.not\.be\.oneOf\(\[null,\s*undefined\]\)/);
1006
+ if (existsMatch) {
1007
+ assertions.push({ name, accessor: existsMatch[1].trim(), kind: "exists" });
1008
+ continue;
1009
+ }
1010
+ const typeMatch = body.match(/sp\.expect\(([^)]+)\)\.to\.be\.a\(\s*"([^"]+)"\s*\)/);
1011
+ if (typeMatch) {
1012
+ assertions.push({ name, accessor: typeMatch[1].trim(), kind: "type", expected: `"${typeMatch[2]}"` });
1013
+ continue;
1014
+ }
1015
+ const aboveMatch = body.match(/sp\.expect\(([^)]+)\)\.to\.be\.above\((\d+)\)/);
1016
+ if (aboveMatch) {
1017
+ assertions.push({ name, accessor: aboveMatch[1].trim(), kind: "above", expected: aboveMatch[2] });
1018
+ continue;
1019
+ }
1020
+ assertions.push({ name, accessor: "", kind: "status" });
1021
+ }
1022
+ const statusMatch = script.match(/sp\.response\.to\.have\.status\((\d+)\)/);
1023
+ if (statusMatch) {
1024
+ assertions.push({ name: `status is ${statusMatch[1]}`, accessor: "", kind: "status", expected: statusMatch[1] });
1025
+ }
1026
+ const extractRegex = /sp\.(variables|environment|globals)\.set\(\s*"([^"]+)"\s*,\s*(.+)\s*\)\s*;/g;
1027
+ while (match = extractRegex.exec(script)) {
1028
+ const target = match[1];
1029
+ const varName = match[2];
1030
+ let accessor = match[3].trim();
1031
+ const stringWrap = accessor.match(/^String\((.+)\)$/);
1032
+ if (stringWrap) accessor = stringWrap[1];
1033
+ extractions.push({ varName, accessor, target });
1034
+ }
1035
+ return { assertions, extractions };
1036
+ }
1037
+ function accessorToJsonPath(accessor) {
1038
+ return accessor.replace(/^json\.?/, "").replace(/\["([^"]+)"\]/g, "['$1']");
1039
+ }
986
1040
  function safeName(name) {
987
1041
  return name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
988
1042
  }
@@ -1004,7 +1058,7 @@ function buildNameMap$2(root, requests) {
1004
1058
  function visit(folder) {
1005
1059
  for (const id of folder.requestIds) {
1006
1060
  const req = requests[id];
1007
- if (!req) continue;
1061
+ if (!req || req.disabled) continue;
1008
1062
  const base = safeName(req.name);
1009
1063
  let name = base;
1010
1064
  if (used.has(name)) {
@@ -1048,38 +1102,90 @@ function jsonToRfDictPairs(json, vars) {
1048
1102
  return null;
1049
1103
  }
1050
1104
  }
1051
- function buildKeywordsFile(collection, varMap, nameMap) {
1105
+ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
1052
1106
  const lines = [
1053
1107
  "*** Settings ***",
1054
1108
  "Library RequestsLibrary",
1109
+ "Library Collections",
1055
1110
  "Resource variables.resource",
1056
1111
  "",
1057
1112
  "*** Keywords ***"
1058
1113
  ];
1114
+ function processHooks(folder) {
1115
+ for (const reqId of folder.requestIds) {
1116
+ const req = collection.requests[reqId];
1117
+ if (!req || req.disabled || !req.hookType) continue;
1118
+ const kwName = nameMap.get(reqId);
1119
+ if (!kwName) continue;
1120
+ const url = interpolate(req.url, varMap);
1121
+ const method = req.method.charAt(0) + req.method.slice(1).toLowerCase();
1122
+ lines.push(kwName);
1123
+ lines.push(` [Documentation] Hook: ${req.hookType} — ${req.name}`);
1124
+ const { body } = req;
1125
+ const hasBody = body.mode !== "none" && !["GET", "HEAD"].includes(req.method);
1126
+ if (hasBody && body.mode === "json" && body.json) {
1127
+ const bodyPairs = jsonToRfDictPairs(body.json, varMap);
1128
+ if (bodyPairs !== null) {
1129
+ lines.push(` VAR &{body} ${bodyPairs}`);
1130
+ } else {
1131
+ lines.push(` VAR \${body} ${interpolate(body.json, varMap)}`);
1132
+ }
1133
+ }
1134
+ const callArgs = [];
1135
+ if (hasBody && body.mode === "json") callArgs.push("json=${body}");
1136
+ lines.push(` \${response}= ${method} ${url}`);
1137
+ if (callArgs.length) lines.push(` ... ${callArgs.join(" ")}`);
1138
+ const parsed = parsePostScript(req.postRequestScript);
1139
+ for (const e of parsed.extractions) {
1140
+ const jp = accessorToJsonPath(e.accessor);
1141
+ const rfVar = e.varName.replace(/\W+/g, "_").toUpperCase();
1142
+ hookExtractedVars.add(rfVar);
1143
+ lines.push(` \${${rfVar}}= Evaluate str($response.json().get('${jp}', ''))`);
1144
+ lines.push(` Set Suite Variable \${${rfVar}}`);
1145
+ }
1146
+ lines.push(` RETURN \${response}`);
1147
+ lines.push("");
1148
+ }
1149
+ for (const sub of folder.folders) processHooks(sub);
1150
+ }
1151
+ processHooks(collection.rootFolder);
1059
1152
  function processFolder(folder) {
1060
1153
  for (const reqId of folder.requestIds) {
1061
1154
  const req = collection.requests[reqId];
1062
- if (!req) continue;
1155
+ if (!req || req.disabled || req.hookType) continue;
1063
1156
  const kwName = nameMap.get(reqId);
1064
1157
  const url = interpolate(req.url, varMap);
1065
1158
  lines.push(kwName);
1066
1159
  lines.push(` [Documentation] ${req.description || req.name}`);
1160
+ const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
1161
+ const effectiveAuth = req.auth.type !== "none" ? req.auth : inherited.auth ?? req.auth;
1162
+ const allHeaders = [...inherited.headers.filter((h) => h.enabled && h.key), ...req.headers.filter((h) => h.enabled && h.key)];
1067
1163
  const headerPairs = [];
1068
- const { auth } = req;
1069
- if (auth.type === "bearer") {
1070
- const ref = auth.tokenSecretRef ?? "API_TOKEN";
1071
- headerPairs.push(`Authorization=Bearer ${envVar(ref)}`);
1072
- } else if (auth.type === "basic") {
1073
- const passRef = auth.passwordSecretRef ?? "API_PASSWORD";
1074
- const user = auth.username ?? "";
1164
+ if (effectiveAuth.type === "bearer") {
1165
+ const token = effectiveAuth.token ?? "";
1166
+ if (token.includes("{{")) {
1167
+ const varRef = token.match(/\{\{([^}]+)\}\}/)?.[1]?.trim();
1168
+ const rfVar = varRef ? varRef.replace(/\W+/g, "_").toUpperCase() : "";
1169
+ if (rfVar && hookExtractedVars.has(rfVar)) {
1170
+ headerPairs.push(`Authorization=Bearer \${${rfVar}}`);
1171
+ } else {
1172
+ headerPairs.push(`Authorization=Bearer ${interpolate(token, varMap)}`);
1173
+ }
1174
+ } else {
1175
+ const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
1176
+ headerPairs.push(`Authorization=Bearer ${envVar(ref)}`);
1177
+ }
1178
+ } else if (effectiveAuth.type === "basic") {
1179
+ const passRef = effectiveAuth.passwordSecretRef ?? "API_PASSWORD";
1180
+ const user = effectiveAuth.username ?? "";
1075
1181
  lines.push(` \${credentials}= Evaluate base64.b64encode(f"${user}:${envVar(passRef)}".encode()).decode() base64`);
1076
1182
  headerPairs.push(`Authorization=Basic \${credentials}`);
1077
- } else if (auth.type === "apikey" && auth.apiKeyIn === "header") {
1078
- const keyRef = auth.apiKeySecretRef ?? "API_KEY";
1079
- const keyName = auth.apiKeyName ?? "X-API-Key";
1183
+ } else if (effectiveAuth.type === "apikey" && effectiveAuth.apiKeyIn === "header") {
1184
+ const keyRef = effectiveAuth.apiKeySecretRef ?? "API_KEY";
1185
+ const keyName = effectiveAuth.apiKeyName ?? "X-API-Key";
1080
1186
  headerPairs.push(`${keyName}=${envVar(keyRef)}`);
1081
1187
  }
1082
- for (const h of req.headers.filter((h2) => h2.enabled && h2.key)) {
1188
+ for (const h of allHeaders) {
1083
1189
  headerPairs.push(`${h.key}=${interpolate(h.value, varMap)}`);
1084
1190
  }
1085
1191
  if (headerPairs.length) {
@@ -1120,23 +1226,72 @@ function buildKeywordsFile(collection, varMap, nameMap) {
1120
1226
  function buildTestSuite(collection, environment, nameMap) {
1121
1227
  const colName = safeName(collection.name);
1122
1228
  const envName = environment?.name ?? "default";
1229
+ const beforeAllHooks = [];
1230
+ const afterAllHooks = [];
1231
+ function collectAll(folder) {
1232
+ for (const id of folder.requestIds) {
1233
+ const r = collection.requests[id];
1234
+ if (!r || r.disabled) continue;
1235
+ if (r.hookType === "beforeAll") beforeAllHooks.push(r);
1236
+ else if (r.hookType === "afterAll") afterAllHooks.push(r);
1237
+ }
1238
+ for (const sub of folder.folders) collectAll(sub);
1239
+ }
1240
+ collectAll(collection.rootFolder);
1241
+ const suiteSetup = beforeAllHooks.length ? `Suite Setup Run Keywords ${beforeAllHooks.map((h) => nameMap.get(h.id) ?? safeName(h.name)).join(" AND ")}` : `Suite Setup Log Running ${colName} against ${envName} environment`;
1242
+ const suiteTeardown = afterAllHooks.length ? `Suite Teardown Run Keywords ${afterAllHooks.map((h) => nameMap.get(h.id) ?? safeName(h.name)).join(" AND ")}` : "";
1123
1243
  const lines = [
1124
1244
  "*** Settings ***",
1125
1245
  "Resource ../resources/api_keywords.resource",
1126
1246
  "",
1127
- `Suite Setup Log Running ${colName} against ${envName} environment`,
1247
+ suiteSetup,
1248
+ ...suiteTeardown ? [suiteTeardown] : [],
1128
1249
  "",
1129
1250
  "*** Test Cases ***"
1130
1251
  ];
1131
1252
  function processFolder(folder) {
1132
1253
  for (const reqId of folder.requestIds) {
1133
1254
  const req = collection.requests[reqId];
1134
- if (!req) continue;
1255
+ if (!req || req.disabled || req.hookType) continue;
1135
1256
  const kwName = nameMap.get(reqId);
1136
1257
  lines.push(kwName);
1137
1258
  lines.push(` [Documentation] ${req.description || req.name}`);
1138
1259
  lines.push(` \${response}= ${kwName}`);
1139
- lines.push(` Status Should Be 200 \${response}`);
1260
+ const parsed = parsePostScript(req.postRequestScript);
1261
+ if (parsed.assertions.length > 0) {
1262
+ for (const a of parsed.assertions) {
1263
+ const jp = accessorToJsonPath(a.accessor);
1264
+ switch (a.kind) {
1265
+ case "status":
1266
+ lines.push(` Status Should Be ${a.expected ?? 200} \${response}`);
1267
+ break;
1268
+ case "equals": {
1269
+ const expected = a.expected?.replace(/^"|"$/g, "") ?? "";
1270
+ lines.push(` \${value}= Get From Dictionary \${response.json()} ${jp}`);
1271
+ lines.push(` Should Be Equal As Strings \${value} ${expected}`);
1272
+ break;
1273
+ }
1274
+ case "contains": {
1275
+ const expected = a.expected?.replace(/^"|"$/g, "") ?? "";
1276
+ lines.push(` \${value}= Get From Dictionary \${response.json()} ${jp}`);
1277
+ lines.push(` Should Contain \${value} ${expected}`);
1278
+ break;
1279
+ }
1280
+ case "exists":
1281
+ lines.push(` Dictionary Should Contain Key \${response.json()} ${jp}`);
1282
+ break;
1283
+ default:
1284
+ lines.push(` Status Should Be 200 \${response}`);
1285
+ }
1286
+ }
1287
+ } else {
1288
+ lines.push(` Status Should Be 200 \${response}`);
1289
+ }
1290
+ for (const e of parsed.extractions) {
1291
+ const jp = accessorToJsonPath(e.accessor);
1292
+ lines.push(` \${${e.varName}}= Get From Dictionary \${response.json()} ${jp}`);
1293
+ lines.push(` Set Suite Variable \${${e.varName}}`);
1294
+ }
1140
1295
  lines.push("");
1141
1296
  }
1142
1297
  for (const sub of folder.folders) processFolder(sub);
@@ -1195,9 +1350,10 @@ function generateRobotFramework(collection, environment) {
1195
1350
  );
1196
1351
  const nameMap = buildNameMap$2(collection.rootFolder, collection.requests);
1197
1352
  const slug2 = collection.name.replace(/\W+/g, "_").toLowerCase();
1353
+ const hookExtractedVars = /* @__PURE__ */ new Set();
1198
1354
  const contentFiles = [
1199
1355
  { path: "resources/variables.resource", content: buildVariablesFile(environment) },
1200
- { path: "resources/api_keywords.resource", content: buildKeywordsFile(collection, varMap, nameMap) },
1356
+ { path: "resources/api_keywords.resource", content: buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) },
1201
1357
  { path: `tests/test_${slug2}.robot`, content: buildTestSuite(collection, environment, nameMap) }
1202
1358
  ];
1203
1359
  const allPaths = ["requirements.txt", ...contentFiles.map((f) => f.path)];
@@ -1213,8 +1369,11 @@ function slug$3(name) {
1213
1369
  function toEnvVar$3(key) {
1214
1370
  return key.replace(/\W+/g, "_").toUpperCase();
1215
1371
  }
1216
- function interpolatePath$1(value) {
1217
- return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => `\${process.env.${toEnvVar$3(key.trim())} ?? ''}`);
1372
+ function interpolatePath$1(value, sharedVars = /* @__PURE__ */ new Set()) {
1373
+ return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
1374
+ const envKey = toEnvVar$3(key.trim());
1375
+ return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
1376
+ });
1218
1377
  }
1219
1378
  function buildNameMap$1(folder, requests) {
1220
1379
  const map = /* @__PURE__ */ new Map();
@@ -1234,28 +1393,27 @@ function buildNameMap$1(folder, requests) {
1234
1393
  }
1235
1394
  return map;
1236
1395
  }
1237
- function renderJsValue$1(value, indent) {
1396
+ function renderJsValue$1(value, indent, sharedVars = /* @__PURE__ */ new Set()) {
1238
1397
  const next = indent + " ";
1239
1398
  if (value === null) return "null";
1240
1399
  if (typeof value === "boolean" || typeof value === "number") return String(value);
1241
1400
  if (typeof value === "string") {
1242
1401
  if (value.includes("{{")) {
1243
- const s = value.replace(/\{\{([^}]+)\}\}/g, (_, k) => `\${process.env.${toEnvVar$3(k.trim())} ?? ''}`);
1244
- return "`" + s + "`";
1402
+ return "`" + interpolatePath$1(value, sharedVars) + "`";
1245
1403
  }
1246
1404
  return JSON.stringify(value);
1247
1405
  }
1248
1406
  if (Array.isArray(value)) {
1249
1407
  if (!value.length) return "[]";
1250
1408
  return `[
1251
- ${value.map((v) => next + renderJsValue$1(v, next)).join(",\n")},
1409
+ ${value.map((v) => next + renderJsValue$1(v, next, sharedVars)).join(",\n")},
1252
1410
  ${indent}]`;
1253
1411
  }
1254
1412
  if (typeof value === "object") {
1255
1413
  const entries = Object.entries(value);
1256
1414
  if (!entries.length) return "{}";
1257
1415
  return `{
1258
- ${entries.map(([k, v]) => `${next}${k}: ${renderJsValue$1(v, next)}`).join(",\n")},
1416
+ ${entries.map(([k, v]) => `${next}${k}: ${renderJsValue$1(v, next, sharedVars)}`).join(",\n")},
1259
1417
  ${indent}}`;
1260
1418
  }
1261
1419
  return JSON.stringify(value);
@@ -1280,28 +1438,127 @@ export default defineConfig({
1280
1438
  })
1281
1439
  `;
1282
1440
  }
1283
- function buildSpec$1(folderName, folder, requests, nameMap) {
1441
+ function buildHookLines$1(req, sharedVars) {
1442
+ const method = req.method.toLowerCase();
1443
+ const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
1444
+ const pathExpr = path2.includes("{{") ? "`" + interpolatePath$1(path2) + "`" : `'${path2}'`;
1445
+ const headerEntries = [];
1446
+ if (req.auth.type === "bearer") {
1447
+ const token = req.auth.token ?? "";
1448
+ if (token.includes("{{")) {
1449
+ headerEntries.push(`Authorization: \`Bearer ${interpolatePath$1(token)}\``);
1450
+ } else {
1451
+ const ref = req.auth.tokenSecretRef ?? "API_TOKEN";
1452
+ headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar$3(ref)} ?? ''}\``);
1453
+ }
1454
+ }
1455
+ for (const h of req.headers.filter((h2) => h2.enabled && h2.key)) {
1456
+ headerEntries.push(`'${h.key}': \`${interpolatePath$1(h.value)}\``);
1457
+ }
1458
+ const optionParts = [];
1459
+ if (headerEntries.length) {
1460
+ optionParts.push(`headers: { ${headerEntries.join(", ")} }`);
1461
+ }
1462
+ if (req.body.mode === "json" && req.body.json && !["get", "head"].includes(method)) {
1463
+ try {
1464
+ optionParts.push(`data: ${renderJsValue$1(JSON.parse(req.body.json), " ")}`);
1465
+ } catch {
1466
+ }
1467
+ }
1468
+ const opts = optionParts.length ? `, { ${optionParts.join(", ")} }` : "";
1469
+ const lines = [];
1470
+ lines.push(` // ${req.name}`);
1471
+ const parsed = parsePostScript(req.postRequestScript);
1472
+ if (parsed.extractions.length > 0) {
1473
+ lines.push(` const hookResponse = await request.${method}(${pathExpr}${opts});`);
1474
+ lines.push(` const hookJson = await hookResponse.json();`);
1475
+ for (const e of parsed.extractions) {
1476
+ const jsonPath = e.accessor.replace(/^json\.?/, "");
1477
+ const expr = jsonPath ? `hookJson.${jsonPath}` : "hookJson";
1478
+ const varName = toEnvVar$3(e.varName);
1479
+ sharedVars.add(varName);
1480
+ lines.push(` ${varName} = String(${expr});`);
1481
+ }
1482
+ } else {
1483
+ lines.push(` await request.${method}(${pathExpr}${opts});`);
1484
+ }
1485
+ return lines;
1486
+ }
1487
+ function buildSpec$1(folderName, folder, collection, nameMap) {
1488
+ const requests = collection.requests;
1284
1489
  const tests = [];
1490
+ const hooks = requestCollection.getAllApplicableHooks(folder.id, collection);
1491
+ const beforeAllHooks = hooks.beforeAll;
1492
+ const beforeHooks = hooks.before;
1493
+ const afterHooks = hooks.after;
1494
+ const afterAllHooks = hooks.afterAll;
1495
+ const sharedVars = /* @__PURE__ */ new Set();
1496
+ const hookBlocks = [];
1497
+ if (beforeAllHooks.length) {
1498
+ const lines = beforeAllHooks.flatMap((h) => buildHookLines$1(h, sharedVars));
1499
+ hookBlocks.push(` test.beforeAll(async ({ request }) => {
1500
+ ${lines.join("\n")}
1501
+ });
1502
+ `);
1503
+ }
1504
+ if (beforeHooks.length) {
1505
+ const lines = beforeHooks.flatMap((h) => buildHookLines$1(h, sharedVars));
1506
+ hookBlocks.push(` test.beforeEach(async ({ request }) => {
1507
+ ${lines.join("\n")}
1508
+ });
1509
+ `);
1510
+ }
1511
+ if (afterHooks.length) {
1512
+ const lines = afterHooks.flatMap((h) => buildHookLines$1(h, sharedVars));
1513
+ hookBlocks.push(` test.afterEach(async ({ request }) => {
1514
+ ${lines.join("\n")}
1515
+ });
1516
+ `);
1517
+ }
1518
+ if (afterAllHooks.length) {
1519
+ const lines = afterAllHooks.flatMap((h) => buildHookLines$1(h, sharedVars));
1520
+ hookBlocks.push(` test.afterAll(async ({ request }) => {
1521
+ ${lines.join("\n")}
1522
+ });
1523
+ `);
1524
+ }
1525
+ for (const v of sharedVars) {
1526
+ tests.push(` let ${v} = '';`);
1527
+ }
1528
+ if (sharedVars.size) tests.push("");
1529
+ tests.push(...hookBlocks);
1285
1530
  for (const reqId of folder.requestIds) {
1286
1531
  const req = requests[reqId];
1287
- if (!req) continue;
1532
+ if (!req || req.disabled || req.hookType) continue;
1288
1533
  const testName = nameMap.get(reqId) ?? req.name;
1289
1534
  const method = req.method.toLowerCase();
1290
1535
  const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
1291
- const pathExpr = path2.includes("{{") ? "`" + interpolatePath$1(path2) + "`" : `'${path2}'`;
1536
+ const pathExpr = path2.includes("{{") ? "`" + interpolatePath$1(path2, sharedVars) + "`" : `'${path2}'`;
1292
1537
  const optionParts = [];
1538
+ const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
1539
+ const effectiveAuth = req.auth.type !== "none" ? req.auth : inherited.auth ?? req.auth;
1540
+ const allHeaders = [...inherited.headers.filter((h) => h.enabled && h.key), ...req.headers.filter((h) => h.enabled && h.key)];
1293
1541
  const headerEntries = [];
1294
- const { auth } = req;
1295
- if (auth.type === "bearer") {
1296
- const ref = auth.tokenSecretRef ?? "API_TOKEN";
1297
- headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar$3(ref)} ?? ''}\``);
1298
- } else if (auth.type === "apikey" && auth.apiKeyIn === "header") {
1299
- const ref = auth.apiKeySecretRef ?? "API_KEY";
1300
- const name = auth.apiKeyName ?? "X-API-Key";
1301
- headerEntries.push(`'${name}': \`\${process.env.${toEnvVar$3(ref)} ?? ''}\``);
1542
+ if (effectiveAuth.type === "bearer") {
1543
+ const token = effectiveAuth.token ?? "";
1544
+ if (token.includes("{{")) {
1545
+ headerEntries.push(`Authorization: \`Bearer ${interpolatePath$1(token, sharedVars)}\``);
1546
+ } else {
1547
+ const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
1548
+ headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar$3(ref)} ?? ''}\``);
1549
+ }
1550
+ } else if (effectiveAuth.type === "apikey" && effectiveAuth.apiKeyIn === "header") {
1551
+ const val = effectiveAuth.apiKeyValue ?? "";
1552
+ const name = effectiveAuth.apiKeyName ?? "X-API-Key";
1553
+ if (val.includes("{{")) {
1554
+ headerEntries.push(`'${name}': \`${interpolatePath$1(val, sharedVars)}\``);
1555
+ } else {
1556
+ const ref = effectiveAuth.apiKeySecretRef ?? "API_KEY";
1557
+ headerEntries.push(`'${name}': \`\${process.env.${toEnvVar$3(ref)} ?? ''}\``);
1558
+ }
1302
1559
  }
1303
- for (const h of req.headers.filter((h2) => h2.enabled && h2.key)) {
1304
- headerEntries.push(`'${h.key}': \`${interpolatePath$1(h.value)}\``);
1560
+ for (const h of allHeaders) {
1561
+ headerEntries.push(`'${h.key}': \`${interpolatePath$1(h.value, sharedVars)}\``);
1305
1562
  }
1306
1563
  if (headerEntries.length) {
1307
1564
  optionParts.push(` headers: {
@@ -1310,27 +1567,76 @@ function buildSpec$1(folderName, folder, requests, nameMap) {
1310
1567
  }
1311
1568
  const enabledParams = req.params.filter((p) => p.enabled && p.key);
1312
1569
  if (enabledParams.length) {
1313
- const pairs = enabledParams.map((p) => `'${p.key}': '${interpolatePath$1(p.value)}'`).join(", ");
1570
+ const pairs = enabledParams.map(
1571
+ (p) => p.value.includes("{{") ? `'${p.key}': \`${interpolatePath$1(p.value, sharedVars)}\`` : `'${p.key}': '${p.value}'`
1572
+ ).join(", ");
1314
1573
  optionParts.push(` params: { ${pairs} }`);
1315
1574
  }
1316
1575
  const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
1317
1576
  if (hasBody && req.body.mode === "json" && req.body.json) {
1318
1577
  try {
1319
- const rendered = renderJsValue$1(JSON.parse(req.body.json), " ");
1578
+ const rendered = renderJsValue$1(JSON.parse(req.body.json), " ", sharedVars);
1320
1579
  optionParts.push(` data: ${rendered}`);
1321
1580
  } catch {
1322
- optionParts.push(` data: \`${interpolatePath$1(req.body.json)}\``);
1581
+ optionParts.push(` data: \`${interpolatePath$1(req.body.json, sharedVars)}\``);
1323
1582
  }
1324
1583
  }
1325
1584
  const optionsStr = optionParts.length ? `, {
1326
1585
  ${optionParts.join(",\n")},
1327
1586
  }` : "";
1328
- tests.push([
1587
+ const parsed = parsePostScript(req.postRequestScript);
1588
+ const lines = [
1329
1589
  ` test('${testName}', async ({ request }) => {`,
1330
- ` const response = await request.${method}(${pathExpr}${optionsStr});`,
1331
- ` expect(response.ok()).toBeTruthy();`,
1332
- ` });`
1333
- ].join("\n"));
1590
+ ` const response = await request.${method}(${pathExpr}${optionsStr});`
1591
+ ];
1592
+ const needsJson = parsed.assertions.some((a) => a.accessor.startsWith("json")) || parsed.extractions.length > 0;
1593
+ if (needsJson) {
1594
+ lines.push(` const json = await response.json();`);
1595
+ }
1596
+ if (req.schema?.trim()) {
1597
+ lines.push(` // JSON Schema validation`);
1598
+ lines.push(` // Schema: ${req.schema.replace(/\n/g, " ").slice(0, 80)}...`);
1599
+ }
1600
+ if (parsed.assertions.length > 0) {
1601
+ for (const a of parsed.assertions) {
1602
+ const path22 = a.accessor.replace(/^json\.?/, "");
1603
+ const jsonExpr = path22 ? `json.${path22}` : "json";
1604
+ switch (a.kind) {
1605
+ case "status":
1606
+ if (a.expected) {
1607
+ lines.push(` expect(response.status()).toBe(${a.expected});`);
1608
+ } else {
1609
+ lines.push(` expect(response.ok()).toBeTruthy();`);
1610
+ }
1611
+ break;
1612
+ case "equals":
1613
+ lines.push(` expect(${jsonExpr}).toBe(${a.expected});`);
1614
+ break;
1615
+ case "contains":
1616
+ lines.push(` expect(${jsonExpr}).toContain(${a.expected});`);
1617
+ break;
1618
+ case "exists":
1619
+ lines.push(` expect(${jsonExpr}).toBeDefined();`);
1620
+ break;
1621
+ case "type":
1622
+ lines.push(` expect(typeof ${jsonExpr}).toBe(${a.expected});`);
1623
+ break;
1624
+ case "above":
1625
+ lines.push(` expect(${jsonExpr}).toBeGreaterThan(${a.expected});`);
1626
+ break;
1627
+ }
1628
+ }
1629
+ } else {
1630
+ lines.push(` expect(response.ok()).toBeTruthy();`);
1631
+ }
1632
+ for (const e of parsed.extractions) {
1633
+ const path22 = e.accessor.replace(/^json\.?/, "");
1634
+ const expr = path22 ? `json.${path22}` : "json";
1635
+ lines.push(` // Extract: ${e.varName} = ${expr}`);
1636
+ lines.push(` process.env.${toEnvVar$3(e.varName)} = String(${expr});`);
1637
+ }
1638
+ lines.push(` });`);
1639
+ tests.push(lines.join("\n"));
1334
1640
  }
1335
1641
  return `import { test, expect } from '@playwright/test'
1336
1642
 
@@ -1403,7 +1709,7 @@ function generatePlaywright(collection, environment) {
1403
1709
  function processFolder(folder, name) {
1404
1710
  if (folder.requestIds.length > 0) {
1405
1711
  const nameMap = buildNameMap$1(folder, collection.requests);
1406
- files.push({ path: `tests/${slug$3(name)}.spec.ts`, content: buildSpec$1(name, folder, collection.requests, nameMap) });
1712
+ files.push({ path: `tests/${slug$3(name)}.spec.ts`, content: buildSpec$1(name, folder, collection, nameMap) });
1407
1713
  }
1408
1714
  for (const sub of folder.folders) processFolder(sub, sub.name);
1409
1715
  }
@@ -1423,8 +1729,11 @@ function slug$2(name) {
1423
1729
  function toEnvVar$2(key) {
1424
1730
  return key.replace(/\W+/g, "_").toUpperCase();
1425
1731
  }
1426
- function interpolatePath(value) {
1427
- return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => `\${process.env.${toEnvVar$2(key.trim())} ?? ''}`);
1732
+ function interpolatePath(value, sharedVars = /* @__PURE__ */ new Set()) {
1733
+ return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
1734
+ const envKey = toEnvVar$2(key.trim());
1735
+ return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
1736
+ });
1428
1737
  }
1429
1738
  function buildNameMap(folder, requests) {
1430
1739
  const map = /* @__PURE__ */ new Map();
@@ -1444,28 +1753,27 @@ function buildNameMap(folder, requests) {
1444
1753
  }
1445
1754
  return map;
1446
1755
  }
1447
- function renderJsValue(value, indent) {
1756
+ function renderJsValue(value, indent, sharedVars = /* @__PURE__ */ new Set()) {
1448
1757
  const next = indent + " ";
1449
1758
  if (value === null) return "null";
1450
1759
  if (typeof value === "boolean" || typeof value === "number") return String(value);
1451
1760
  if (typeof value === "string") {
1452
1761
  if (value.includes("{{")) {
1453
- const s = value.replace(/\{\{([^}]+)\}\}/g, (_, k) => `\${process.env.${toEnvVar$2(k.trim())} ?? ''}`);
1454
- return "`" + s + "`";
1762
+ return "`" + interpolatePath(value, sharedVars) + "`";
1455
1763
  }
1456
1764
  return JSON.stringify(value);
1457
1765
  }
1458
1766
  if (Array.isArray(value)) {
1459
1767
  if (!value.length) return "[]";
1460
1768
  return `[
1461
- ${value.map((v) => next + renderJsValue(v, next)).join(",\n")},
1769
+ ${value.map((v) => next + renderJsValue(v, next, sharedVars)).join(",\n")},
1462
1770
  ${indent}]`;
1463
1771
  }
1464
1772
  if (typeof value === "object") {
1465
1773
  const entries = Object.entries(value);
1466
1774
  if (!entries.length) return "{}";
1467
1775
  return `{
1468
- ${entries.map(([k, v]) => `${next}${k}: ${renderJsValue(v, next)}`).join(",\n")},
1776
+ ${entries.map(([k, v]) => `${next}${k}: ${renderJsValue(v, next, sharedVars)}`).join(",\n")},
1469
1777
  ${indent}}`;
1470
1778
  }
1471
1779
  return JSON.stringify(value);
@@ -1489,28 +1797,122 @@ module.exports = defineConfig({
1489
1797
  });
1490
1798
  `;
1491
1799
  }
1492
- function buildSpec(folderName, folder, requests, nameMap) {
1800
+ function buildHookLines(req, sharedVars) {
1801
+ const method = req.method.toLowerCase();
1802
+ const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
1803
+ const pathExpr = path2.includes("{{") ? "`" + interpolatePath(path2) + "`" : `'${path2}'`;
1804
+ const headerEntries = [];
1805
+ if (req.auth.type === "bearer") {
1806
+ const token = req.auth.token ?? "";
1807
+ if (token.includes("{{")) {
1808
+ headerEntries.push(`Authorization: \`Bearer ${interpolatePath(token)}\``);
1809
+ } else {
1810
+ const ref = req.auth.tokenSecretRef ?? "API_TOKEN";
1811
+ headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar$2(ref)} ?? ''}\``);
1812
+ }
1813
+ }
1814
+ for (const h of req.headers.filter((h2) => h2.enabled && h2.key)) {
1815
+ headerEntries.push(`'${h.key}': \`${interpolatePath(h.value)}\``);
1816
+ }
1817
+ const optParts = [];
1818
+ if (headerEntries.length) optParts.push(`headers: { ${headerEntries.join(", ")} }`);
1819
+ if (req.body.mode === "json" && req.body.json && !["get", "head"].includes(method)) {
1820
+ try {
1821
+ optParts.push(`data: ${renderJsValue(JSON.parse(req.body.json), " ")}`);
1822
+ } catch {
1823
+ }
1824
+ }
1825
+ const opts = optParts.length ? `, { ${optParts.join(", ")} }` : "";
1826
+ const lines = [` // ${req.name}`];
1827
+ const parsed = parsePostScript(req.postRequestScript);
1828
+ if (parsed.extractions.length > 0) {
1829
+ lines.push(` const hookResponse = await request.${method}(${pathExpr}${opts});`);
1830
+ lines.push(` const hookJson = await hookResponse.json();`);
1831
+ for (const e of parsed.extractions) {
1832
+ const jp = e.accessor.replace(/^json\.?/, "");
1833
+ const expr = jp ? `hookJson.${jp}` : "hookJson";
1834
+ const varName = toEnvVar$2(e.varName);
1835
+ sharedVars.add(varName);
1836
+ lines.push(` ${varName} = String(${expr});`);
1837
+ }
1838
+ } else {
1839
+ lines.push(` await request.${method}(${pathExpr}${opts});`);
1840
+ }
1841
+ return lines;
1842
+ }
1843
+ function buildSpec(folderName, folder, collection, nameMap) {
1844
+ const requests = collection.requests;
1493
1845
  const tests = [];
1846
+ const hooks = requestCollection.getAllApplicableHooks(folder.id, collection);
1847
+ const beforeAllHooks = hooks.beforeAll;
1848
+ const beforeHooks = hooks.before;
1849
+ const afterHooks = hooks.after;
1850
+ const afterAllHooks = hooks.afterAll;
1851
+ const sharedVars = /* @__PURE__ */ new Set();
1852
+ const hookBlocks = [];
1853
+ if (beforeAllHooks.length) {
1854
+ const lines = beforeAllHooks.flatMap((h) => buildHookLines(h, sharedVars));
1855
+ hookBlocks.push(` test.beforeAll(async ({ request }) => {
1856
+ ${lines.join("\n")}
1857
+ });
1858
+ `);
1859
+ }
1860
+ if (beforeHooks.length) {
1861
+ const lines = beforeHooks.flatMap((h) => buildHookLines(h, sharedVars));
1862
+ hookBlocks.push(` test.beforeEach(async ({ request }) => {
1863
+ ${lines.join("\n")}
1864
+ });
1865
+ `);
1866
+ }
1867
+ if (afterHooks.length) {
1868
+ const lines = afterHooks.flatMap((h) => buildHookLines(h, sharedVars));
1869
+ hookBlocks.push(` test.afterEach(async ({ request }) => {
1870
+ ${lines.join("\n")}
1871
+ });
1872
+ `);
1873
+ }
1874
+ if (afterAllHooks.length) {
1875
+ const lines = afterAllHooks.flatMap((h) => buildHookLines(h, sharedVars));
1876
+ hookBlocks.push(` test.afterAll(async ({ request }) => {
1877
+ ${lines.join("\n")}
1878
+ });
1879
+ `);
1880
+ }
1881
+ for (const v of sharedVars) tests.push(` let ${v} = '';`);
1882
+ if (sharedVars.size) tests.push("");
1883
+ tests.push(...hookBlocks);
1494
1884
  for (const reqId of folder.requestIds) {
1495
1885
  const req = requests[reqId];
1496
- if (!req) continue;
1886
+ if (!req || req.disabled || req.hookType) continue;
1497
1887
  const testName = nameMap.get(reqId) ?? req.name;
1498
1888
  const method = req.method.toLowerCase();
1499
1889
  const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
1500
- const pathExpr = path2.includes("{{") ? "`" + interpolatePath(path2) + "`" : `'${path2}'`;
1890
+ const pathExpr = path2.includes("{{") ? "`" + interpolatePath(path2, sharedVars) + "`" : `'${path2}'`;
1501
1891
  const optionParts = [];
1892
+ const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
1893
+ const effectiveAuth = req.auth.type !== "none" ? req.auth : inherited.auth ?? req.auth;
1894
+ const allHeaders = [...inherited.headers.filter((h) => h.enabled && h.key), ...req.headers.filter((h) => h.enabled && h.key)];
1502
1895
  const headerEntries = [];
1503
- const { auth } = req;
1504
- if (auth.type === "bearer") {
1505
- const ref = auth.tokenSecretRef ?? "API_TOKEN";
1506
- headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar$2(ref)} ?? ''}\``);
1507
- } else if (auth.type === "apikey" && auth.apiKeyIn === "header") {
1508
- const ref = auth.apiKeySecretRef ?? "API_KEY";
1509
- const name = auth.apiKeyName ?? "X-API-Key";
1510
- headerEntries.push(`'${name}': \`\${process.env.${toEnvVar$2(ref)} ?? ''}\``);
1896
+ if (effectiveAuth.type === "bearer") {
1897
+ const token = effectiveAuth.token ?? "";
1898
+ if (token.includes("{{")) {
1899
+ headerEntries.push(`Authorization: \`Bearer ${interpolatePath(token, sharedVars)}\``);
1900
+ } else {
1901
+ const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
1902
+ headerEntries.push(`Authorization: \`Bearer \${process.env.${toEnvVar$2(ref)} ?? ''}\``);
1903
+ }
1904
+ } else if (effectiveAuth.type === "apikey" && effectiveAuth.apiKeyIn === "header") {
1905
+ const val = effectiveAuth.apiKeyValue ?? "";
1906
+ const name = effectiveAuth.apiKeyName ?? "X-API-Key";
1907
+ if (val.includes("{{")) {
1908
+ headerEntries.push(`'${name}': \`${interpolatePath(val, sharedVars)}\``);
1909
+ } else {
1910
+ const ref = effectiveAuth.apiKeySecretRef ?? "API_KEY";
1911
+ headerEntries.push(`'${name}': \`\${process.env.${toEnvVar$2(ref)} ?? ''}\``);
1912
+ }
1511
1913
  }
1512
- for (const h of req.headers.filter((h2) => h2.enabled && h2.key)) {
1513
- headerEntries.push(`'${h.key}': \`${interpolatePath(h.value)}\``);
1914
+ for (const h of allHeaders) {
1915
+ headerEntries.push(`'${h.key}': \`${interpolatePath(h.value, sharedVars)}\``);
1514
1916
  }
1515
1917
  if (headerEntries.length) {
1516
1918
  optionParts.push(` headers: {
@@ -1519,27 +1921,67 @@ function buildSpec(folderName, folder, requests, nameMap) {
1519
1921
  }
1520
1922
  const enabledParams = req.params.filter((p) => p.enabled && p.key);
1521
1923
  if (enabledParams.length) {
1522
- const pairs = enabledParams.map((p) => `'${p.key}': '${interpolatePath(p.value)}'`).join(", ");
1924
+ const pairs = enabledParams.map(
1925
+ (p) => p.value.includes("{{") ? `'${p.key}': \`${interpolatePath(p.value, sharedVars)}\`` : `'${p.key}': '${p.value}'`
1926
+ ).join(", ");
1523
1927
  optionParts.push(` params: { ${pairs} }`);
1524
1928
  }
1525
1929
  const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
1526
1930
  if (hasBody && req.body.mode === "json" && req.body.json) {
1527
1931
  try {
1528
- const rendered = renderJsValue(JSON.parse(req.body.json), " ");
1932
+ const rendered = renderJsValue(JSON.parse(req.body.json), " ", sharedVars);
1529
1933
  optionParts.push(` data: ${rendered}`);
1530
1934
  } catch {
1531
- optionParts.push(` data: \`${interpolatePath(req.body.json)}\``);
1935
+ optionParts.push(` data: \`${interpolatePath(req.body.json, sharedVars)}\``);
1532
1936
  }
1533
1937
  }
1534
1938
  const optionsStr = optionParts.length ? `, {
1535
1939
  ${optionParts.join(",\n")},
1536
1940
  }` : "";
1537
- tests.push([
1941
+ const parsed = parsePostScript(req.postRequestScript);
1942
+ const lines = [
1538
1943
  ` test('${testName}', async ({ request }) => {`,
1539
- ` const response = await request.${method}(${pathExpr}${optionsStr});`,
1540
- ` expect(response.ok()).toBeTruthy();`,
1541
- ` });`
1542
- ].join("\n"));
1944
+ ` const response = await request.${method}(${pathExpr}${optionsStr});`
1945
+ ];
1946
+ const needsJson = parsed.assertions.some((a) => a.accessor.startsWith("json")) || parsed.extractions.length > 0;
1947
+ if (needsJson) {
1948
+ lines.push(` const json = await response.json();`);
1949
+ }
1950
+ if (parsed.assertions.length > 0) {
1951
+ for (const a of parsed.assertions) {
1952
+ const path22 = a.accessor.replace(/^json\.?/, "");
1953
+ const jsonExpr = path22 ? `json.${path22}` : "json";
1954
+ switch (a.kind) {
1955
+ case "status":
1956
+ lines.push(a.expected ? ` expect(response.status()).toBe(${a.expected});` : ` expect(response.ok()).toBeTruthy();`);
1957
+ break;
1958
+ case "equals":
1959
+ lines.push(` expect(${jsonExpr}).toBe(${a.expected});`);
1960
+ break;
1961
+ case "contains":
1962
+ lines.push(` expect(${jsonExpr}).toContain(${a.expected});`);
1963
+ break;
1964
+ case "exists":
1965
+ lines.push(` expect(${jsonExpr}).toBeDefined();`);
1966
+ break;
1967
+ case "type":
1968
+ lines.push(` expect(typeof ${jsonExpr}).toBe(${a.expected});`);
1969
+ break;
1970
+ case "above":
1971
+ lines.push(` expect(${jsonExpr}).toBeGreaterThan(${a.expected});`);
1972
+ break;
1973
+ }
1974
+ }
1975
+ } else {
1976
+ lines.push(` expect(response.ok()).toBeTruthy();`);
1977
+ }
1978
+ for (const e of parsed.extractions) {
1979
+ const path22 = e.accessor.replace(/^json\.?/, "");
1980
+ const expr = path22 ? `json.${path22}` : "json";
1981
+ lines.push(` process.env.${toEnvVar$2(e.varName)} = String(${expr});`);
1982
+ }
1983
+ lines.push(` });`);
1984
+ tests.push(lines.join("\n"));
1543
1985
  }
1544
1986
  return `const { test, expect } = require('@playwright/test');
1545
1987
 
@@ -1611,7 +2053,7 @@ function generatePlaywrightJs(collection, environment) {
1611
2053
  function processFolder(folder, name) {
1612
2054
  if (folder.requestIds.length > 0) {
1613
2055
  const nameMap = buildNameMap(folder, collection.requests);
1614
- files.push({ path: `tests/${slug$2(name)}.spec.js`, content: buildSpec(name, folder, collection.requests, nameMap) });
2056
+ files.push({ path: `tests/${slug$2(name)}.spec.js`, content: buildSpec(name, folder, collection, nameMap) });
1615
2057
  }
1616
2058
  for (const sub of folder.folders) processFolder(sub, sub.name);
1617
2059
  }
@@ -1631,8 +2073,11 @@ function slug$1(name) {
1631
2073
  function toEnvVar$1(key) {
1632
2074
  return key.replace(/\W+/g, "_").toUpperCase();
1633
2075
  }
1634
- function interpolateValue$1(value) {
1635
- return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => `\${process.env.${toEnvVar$1(key.trim())} ?? ''}`);
2076
+ function interpolateValue$1(value, sharedVars = /* @__PURE__ */ new Set()) {
2077
+ return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
2078
+ const envKey = toEnvVar$1(key.trim());
2079
+ return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
2080
+ });
1636
2081
  }
1637
2082
  function buildJestConfig$1() {
1638
2083
  return `import type { Config } from 'jest'
@@ -1665,13 +2110,14 @@ ${envComments ? `// ${envComments.replace(/\n/g, "\n// ")}
1665
2110
  export const api = supertest(BASE_URL);
1666
2111
  `;
1667
2112
  }
1668
- function buildTestFile$1(folderName, folder, requests) {
2113
+ function buildTestFile$1(folderName, folder, collection) {
2114
+ const requests = collection.requests;
1669
2115
  const tests = [];
1670
2116
  const used = /* @__PURE__ */ new Set();
1671
2117
  const nameMap = /* @__PURE__ */ new Map();
1672
2118
  for (const id of folder.requestIds) {
1673
2119
  const req = requests[id];
1674
- if (!req) continue;
2120
+ if (!req || req.disabled || req.hookType) continue;
1675
2121
  const base = req.name;
1676
2122
  let name = base;
1677
2123
  if (used.has(name)) {
@@ -1682,38 +2128,127 @@ function buildTestFile$1(folderName, folder, requests) {
1682
2128
  used.add(name);
1683
2129
  nameMap.set(id, name);
1684
2130
  }
2131
+ const hooks = requestCollection.getAllApplicableHooks(folder.id, collection);
2132
+ const beforeAllH = hooks.beforeAll;
2133
+ const afterAllH = hooks.afterAll;
2134
+ const sharedVars = /* @__PURE__ */ new Set();
2135
+ function buildSupertestHookLines(h) {
2136
+ const lines = [` // ${h.name}`];
2137
+ const method = h.method.toLowerCase();
2138
+ const path2 = h.url.replace(/^https?:\/\/[^/]+/, "") || "/";
2139
+ const parsed = parsePostScript(h.postRequestScript);
2140
+ if (parsed.extractions.length > 0) {
2141
+ lines.push(` const hookRes = await api.${method}('${path2}');`);
2142
+ for (const e of parsed.extractions) {
2143
+ const jp = e.accessor.replace(/^json\.?/, "");
2144
+ const expr = jp ? `hookRes.body.${jp}` : "hookRes.body";
2145
+ const varName = toEnvVar$1(e.varName);
2146
+ sharedVars.add(varName);
2147
+ lines.push(` ${varName} = String(${expr});`);
2148
+ }
2149
+ } else {
2150
+ lines.push(` await api.${method}('${path2}');`);
2151
+ }
2152
+ return lines;
2153
+ }
2154
+ const hookBlocks = [];
2155
+ if (beforeAllH.length) {
2156
+ const lines = beforeAllH.flatMap(buildSupertestHookLines);
2157
+ hookBlocks.push(` beforeAll(async () => {
2158
+ ${lines.join("\n")}
2159
+ });
2160
+ `);
2161
+ }
2162
+ if (afterAllH.length) {
2163
+ const lines = afterAllH.flatMap(buildSupertestHookLines);
2164
+ hookBlocks.push(` afterAll(async () => {
2165
+ ${lines.join("\n")}
2166
+ });
2167
+ `);
2168
+ }
2169
+ for (const v of sharedVars) tests.push(` let ${v}: string = '';`);
2170
+ if (sharedVars.size) tests.push("");
2171
+ tests.push(...hookBlocks);
1685
2172
  for (const reqId of folder.requestIds) {
1686
2173
  const req = requests[reqId];
1687
- if (!req) continue;
2174
+ if (!req || req.disabled || req.hookType) continue;
1688
2175
  const method = req.method.toLowerCase();
1689
- const path2 = interpolateValue$1(req.url.replace(/^https?:\/\/[^/]+/, "") || "/");
1690
- const enabledHeaders = req.headers.filter((h) => h.enabled && h.key);
2176
+ const path2 = interpolateValue$1(req.url.replace(/^https?:\/\/[^/]+/, "") || "/", sharedVars);
2177
+ const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
2178
+ const effectiveAuth = req.auth.type !== "none" ? req.auth : inherited.auth ?? req.auth;
2179
+ const allHeaders = [...inherited.headers.filter((h) => h.enabled && h.key), ...req.headers.filter((h) => h.enabled && h.key)];
1691
2180
  const enabledParams = req.params.filter((p) => p.enabled && p.key);
1692
2181
  const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
1693
2182
  const lines = [];
1694
2183
  lines.push(` it('${nameMap.get(reqId)}', async () => {`);
1695
2184
  lines.push(` const res = await api`);
1696
2185
  lines.push(` .${method}(\`${path2}\`)`);
1697
- for (const h of enabledHeaders) {
1698
- lines.push(` .set('${h.key}', \`${interpolateValue$1(h.value)}\`)`);
2186
+ if (effectiveAuth.type === "bearer") {
2187
+ const token = effectiveAuth.token ?? "";
2188
+ if (token.includes("{{")) {
2189
+ lines.push(` .set('Authorization', \`Bearer ${interpolateValue$1(token, sharedVars)}\`)`);
2190
+ } else {
2191
+ const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
2192
+ lines.push(` .set('Authorization', \`Bearer \${process.env.${toEnvVar$1(ref)} ?? ''}\`)`);
2193
+ }
2194
+ }
2195
+ for (const h of allHeaders) {
2196
+ lines.push(` .set('${h.key}', \`${interpolateValue$1(h.value, sharedVars)}\`)`);
1699
2197
  }
1700
2198
  if (enabledParams.length) {
1701
- const pairs = enabledParams.map((p) => `${p.key}: \`${interpolateValue$1(p.value)}\``).join(", ");
2199
+ const pairs = enabledParams.map((p) => `${p.key}: \`${interpolateValue$1(p.value, sharedVars)}\``).join(", ");
1702
2200
  lines.push(` .query({ ${pairs} })`);
1703
2201
  }
1704
2202
  if (hasBody) {
1705
2203
  if (req.body.mode === "json") {
1706
- lines.push(` .send(${req.body.json ?? "{}"})`);
2204
+ const jsonBody = req.body.json ?? "{}";
2205
+ if (jsonBody.includes("{{")) {
2206
+ lines.push(` .send(JSON.parse(\`${interpolateValue$1(jsonBody, sharedVars)}\`))`);
2207
+ } else {
2208
+ lines.push(` .send(${jsonBody})`);
2209
+ }
1707
2210
  } else if (req.body.mode === "form" && req.body.form) {
1708
- const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${interpolateValue$1(p.value)}\``).join(", ");
2211
+ const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${interpolateValue$1(p.value, sharedVars)}\``).join(", ");
1709
2212
  lines.push(` .type('form')`);
1710
2213
  lines.push(` .send({ ${pairs} })`);
1711
2214
  }
1712
2215
  }
1713
2216
  lines[lines.length - 1] += ";";
1714
2217
  lines.push(``);
1715
- lines.push(` expect(res.status).toBe(200);`);
1716
- lines.push(` // expect(res.body).toMatchObject({});`);
2218
+ const parsed = parsePostScript(req.postRequestScript);
2219
+ if (parsed.assertions.length > 0) {
2220
+ for (const a of parsed.assertions) {
2221
+ const path22 = a.accessor.replace(/^json\.?/, "");
2222
+ const bodyExpr = path22 ? `res.body.${path22}` : "res.body";
2223
+ switch (a.kind) {
2224
+ case "status":
2225
+ lines.push(` expect(res.status).toBe(${a.expected ?? 200});`);
2226
+ break;
2227
+ case "equals":
2228
+ lines.push(` expect(${bodyExpr}).toBe(${a.expected});`);
2229
+ break;
2230
+ case "contains":
2231
+ lines.push(` expect(${bodyExpr}).toContain(${a.expected});`);
2232
+ break;
2233
+ case "exists":
2234
+ lines.push(` expect(${bodyExpr}).toBeDefined();`);
2235
+ break;
2236
+ case "type":
2237
+ lines.push(` expect(typeof ${bodyExpr}).toBe(${a.expected});`);
2238
+ break;
2239
+ case "above":
2240
+ lines.push(` expect(${bodyExpr}).toBeGreaterThan(${a.expected});`);
2241
+ break;
2242
+ }
2243
+ }
2244
+ } else {
2245
+ lines.push(` expect(res.status).toBe(200);`);
2246
+ }
2247
+ for (const e of parsed.extractions) {
2248
+ const path22 = e.accessor.replace(/^json\.?/, "");
2249
+ const expr = path22 ? `res.body.${path22}` : "res.body";
2250
+ lines.push(` process.env.${toEnvVar$1(e.varName)} = String(${expr});`);
2251
+ }
1717
2252
  lines.push(` })`);
1718
2253
  tests.push(lines.join("\n"));
1719
2254
  }
@@ -1804,7 +2339,7 @@ function generateSupertestTs(collection, environment) {
1804
2339
  if (folder.requestIds.length > 0) {
1805
2340
  files.push({
1806
2341
  path: `tests/${slug$1(name)}.test.ts`,
1807
- content: buildTestFile$1(name, folder, collection.requests)
2342
+ content: buildTestFile$1(name, folder, collection)
1808
2343
  });
1809
2344
  }
1810
2345
  for (const sub of folder.folders) {
@@ -1831,8 +2366,11 @@ function slug(name) {
1831
2366
  function toEnvVar(key) {
1832
2367
  return key.replace(/\W+/g, "_").toUpperCase();
1833
2368
  }
1834
- function interpolateValue(value) {
1835
- return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => `\${process.env.${toEnvVar(key.trim())} ?? ''}`);
2369
+ function interpolateValue(value, sharedVars = /* @__PURE__ */ new Set()) {
2370
+ return value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
2371
+ const envKey = toEnvVar(key.trim());
2372
+ return sharedVars.has(envKey) ? `\${${envKey}}` : `\${process.env.${envKey} ?? ''}`;
2373
+ });
1836
2374
  }
1837
2375
  function buildJestConfig() {
1838
2376
  return `/** @type {import('jest').Config} */
@@ -1859,13 +2397,14 @@ ${envComments ? `// ${envComments.replace(/\n/g, "\n// ")}
1859
2397
  module.exports.api = supertest(BASE_URL);
1860
2398
  `;
1861
2399
  }
1862
- function buildTestFile(folderName, folder, requests) {
2400
+ function buildTestFile(folderName, folder, collection) {
2401
+ const requests = collection.requests;
1863
2402
  const tests = [];
1864
2403
  const used = /* @__PURE__ */ new Set();
1865
2404
  const nameMap = /* @__PURE__ */ new Map();
1866
2405
  for (const id of folder.requestIds) {
1867
2406
  const req = requests[id];
1868
- if (!req) continue;
2407
+ if (!req || req.disabled || req.hookType) continue;
1869
2408
  const base = req.name;
1870
2409
  let name = base;
1871
2410
  if (used.has(name)) {
@@ -1876,38 +2415,127 @@ function buildTestFile(folderName, folder, requests) {
1876
2415
  used.add(name);
1877
2416
  nameMap.set(id, name);
1878
2417
  }
2418
+ const hooks = requestCollection.getAllApplicableHooks(folder.id, collection);
2419
+ const beforeAllH = hooks.beforeAll;
2420
+ const afterAllH = hooks.afterAll;
2421
+ const sharedVars = /* @__PURE__ */ new Set();
2422
+ function buildJsHookLines(h) {
2423
+ const lines = [` // ${h.name}`];
2424
+ const method = h.method.toLowerCase();
2425
+ const path2 = h.url.replace(/^https?:\/\/[^/]+/, "") || "/";
2426
+ const parsed = parsePostScript(h.postRequestScript);
2427
+ if (parsed.extractions.length > 0) {
2428
+ lines.push(` const hookRes = await api.${method}('${path2}');`);
2429
+ for (const e of parsed.extractions) {
2430
+ const jp = e.accessor.replace(/^json\.?/, "");
2431
+ const expr = jp ? `hookRes.body.${jp}` : "hookRes.body";
2432
+ const varName = toEnvVar(e.varName);
2433
+ sharedVars.add(varName);
2434
+ lines.push(` ${varName} = String(${expr});`);
2435
+ }
2436
+ } else {
2437
+ lines.push(` await api.${method}('${path2}');`);
2438
+ }
2439
+ return lines;
2440
+ }
2441
+ const hookBlocks = [];
2442
+ if (beforeAllH.length) {
2443
+ const lines = beforeAllH.flatMap(buildJsHookLines);
2444
+ hookBlocks.push(` beforeAll(async () => {
2445
+ ${lines.join("\n")}
2446
+ });
2447
+ `);
2448
+ }
2449
+ if (afterAllH.length) {
2450
+ const lines = afterAllH.flatMap(buildJsHookLines);
2451
+ hookBlocks.push(` afterAll(async () => {
2452
+ ${lines.join("\n")}
2453
+ });
2454
+ `);
2455
+ }
2456
+ for (const v of sharedVars) tests.push(` let ${v} = '';`);
2457
+ if (sharedVars.size) tests.push("");
2458
+ tests.push(...hookBlocks);
1879
2459
  for (const reqId of folder.requestIds) {
1880
2460
  const req = requests[reqId];
1881
- if (!req) continue;
2461
+ if (!req || req.disabled || req.hookType) continue;
1882
2462
  const method = req.method.toLowerCase();
1883
- const path2 = interpolateValue(req.url.replace(/^https?:\/\/[^/]+/, "") || "/");
1884
- const enabledHeaders = req.headers.filter((h) => h.enabled && h.key);
2463
+ const path2 = interpolateValue(req.url.replace(/^https?:\/\/[^/]+/, "") || "/", sharedVars);
2464
+ const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
2465
+ const effectiveAuth = req.auth.type !== "none" ? req.auth : inherited.auth ?? req.auth;
2466
+ const allHeaders = [...inherited.headers.filter((h) => h.enabled && h.key), ...req.headers.filter((h) => h.enabled && h.key)];
1885
2467
  const enabledParams = req.params.filter((p) => p.enabled && p.key);
1886
2468
  const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
1887
2469
  const lines = [];
1888
2470
  lines.push(` it('${nameMap.get(reqId)}', async () => {`);
1889
2471
  lines.push(` const res = await api`);
1890
2472
  lines.push(` .${method}(\`${path2}\`)`);
1891
- for (const h of enabledHeaders) {
1892
- lines.push(` .set('${h.key}', \`${interpolateValue(h.value)}\`)`);
2473
+ if (effectiveAuth.type === "bearer") {
2474
+ const token = effectiveAuth.token ?? "";
2475
+ if (token.includes("{{")) {
2476
+ lines.push(` .set('Authorization', \`Bearer ${interpolateValue(token, sharedVars)}\`)`);
2477
+ } else {
2478
+ const ref = effectiveAuth.tokenSecretRef ?? "API_TOKEN";
2479
+ lines.push(` .set('Authorization', \`Bearer \${process.env.${toEnvVar(ref)} ?? ''}\`)`);
2480
+ }
2481
+ }
2482
+ for (const h of allHeaders) {
2483
+ lines.push(` .set('${h.key}', \`${interpolateValue(h.value, sharedVars)}\`)`);
1893
2484
  }
1894
2485
  if (enabledParams.length) {
1895
- const pairs = enabledParams.map((p) => `${p.key}: \`${interpolateValue(p.value)}\``).join(", ");
2486
+ const pairs = enabledParams.map((p) => `${p.key}: \`${interpolateValue(p.value, sharedVars)}\``).join(", ");
1896
2487
  lines.push(` .query({ ${pairs} })`);
1897
2488
  }
1898
2489
  if (hasBody) {
1899
2490
  if (req.body.mode === "json") {
1900
- lines.push(` .send(${req.body.json ?? "{}"})`);
2491
+ const jsonBody = req.body.json ?? "{}";
2492
+ if (jsonBody.includes("{{")) {
2493
+ lines.push(` .send(JSON.parse(\`${interpolateValue(jsonBody, sharedVars)}\`))`);
2494
+ } else {
2495
+ lines.push(` .send(${jsonBody})`);
2496
+ }
1901
2497
  } else if (req.body.mode === "form" && req.body.form) {
1902
- const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${interpolateValue(p.value)}\``).join(", ");
2498
+ const pairs = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${p.key}: \`${interpolateValue(p.value, sharedVars)}\``).join(", ");
1903
2499
  lines.push(` .type('form')`);
1904
2500
  lines.push(` .send({ ${pairs} })`);
1905
2501
  }
1906
2502
  }
1907
2503
  lines[lines.length - 1] += ";";
1908
2504
  lines.push(``);
1909
- lines.push(` expect(res.status).toBe(200);`);
1910
- lines.push(` // expect(res.body).toMatchObject({});`);
2505
+ const parsed = parsePostScript(req.postRequestScript);
2506
+ if (parsed.assertions.length > 0) {
2507
+ for (const a of parsed.assertions) {
2508
+ const path22 = a.accessor.replace(/^json\.?/, "");
2509
+ const bodyExpr = path22 ? `res.body.${path22}` : "res.body";
2510
+ switch (a.kind) {
2511
+ case "status":
2512
+ lines.push(` expect(res.status).toBe(${a.expected ?? 200});`);
2513
+ break;
2514
+ case "equals":
2515
+ lines.push(` expect(${bodyExpr}).toBe(${a.expected});`);
2516
+ break;
2517
+ case "contains":
2518
+ lines.push(` expect(${bodyExpr}).toContain(${a.expected});`);
2519
+ break;
2520
+ case "exists":
2521
+ lines.push(` expect(${bodyExpr}).toBeDefined();`);
2522
+ break;
2523
+ case "type":
2524
+ lines.push(` expect(typeof ${bodyExpr}).toBe(${a.expected});`);
2525
+ break;
2526
+ case "above":
2527
+ lines.push(` expect(${bodyExpr}).toBeGreaterThan(${a.expected});`);
2528
+ break;
2529
+ }
2530
+ }
2531
+ } else {
2532
+ lines.push(` expect(res.status).toBe(200);`);
2533
+ }
2534
+ for (const e of parsed.extractions) {
2535
+ const path22 = e.accessor.replace(/^json\.?/, "");
2536
+ const expr = path22 ? `res.body.${path22}` : "res.body";
2537
+ lines.push(` process.env.${toEnvVar(e.varName)} = String(${expr});`);
2538
+ }
1911
2539
  lines.push(` })`);
1912
2540
  tests.push(lines.join("\n"));
1913
2541
  }
@@ -1980,7 +2608,7 @@ function generateSupertestJs(collection, environment) {
1980
2608
  if (folder.requestIds.length > 0) {
1981
2609
  files.push({
1982
2610
  path: `tests/${slug(name)}.test.js`,
1983
- content: buildTestFile(name, folder, collection.requests)
2611
+ content: buildTestFile(name, folder, collection)
1984
2612
  });
1985
2613
  }
1986
2614
  for (const sub of folder.folders) {
@@ -2007,12 +2635,27 @@ function javaMethod(name) {
2007
2635
  const parts = name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean);
2008
2636
  return parts[0].toLowerCase() + parts.slice(1).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
2009
2637
  }
2638
+ function javaTypeFor(expected) {
2639
+ switch (expected?.replace(/"/g, "")) {
2640
+ case "string":
2641
+ return "String.class";
2642
+ case "number":
2643
+ return "Number.class";
2644
+ case "boolean":
2645
+ return "Boolean.class";
2646
+ default:
2647
+ return "Object.class";
2648
+ }
2649
+ }
2010
2650
  function toEnvConst(key) {
2011
2651
  return key.replace(/\W+/g, "_").toUpperCase();
2012
2652
  }
2013
- function interpolateJava(value) {
2653
+ function interpolateJava(value, sharedVars = /* @__PURE__ */ new Set()) {
2014
2654
  return '"' + value.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
2015
2655
  const envKey = toEnvConst(key.trim());
2656
+ if (sharedVars.has(envKey)) {
2657
+ return `" + ${envKey} + "`;
2658
+ }
2016
2659
  return `" + System.getenv("${envKey}") + "`;
2017
2660
  }) + '"';
2018
2661
  }
@@ -2126,14 +2769,56 @@ public class BaseTest {
2126
2769
  }
2127
2770
  `;
2128
2771
  }
2129
- function buildTestClass(folderName, folder, requests) {
2772
+ function buildTestClass(folderName, folder, collection) {
2773
+ const requests = collection.requests;
2130
2774
  const className = javaClass(folderName) + "Test";
2131
2775
  const methods = [];
2776
+ const hooks = requestCollection.getAllApplicableHooks(folder.id, collection);
2777
+ const beforeAllH = hooks.beforeAll;
2778
+ const afterAllH = hooks.afterAll;
2779
+ const sharedVars = /* @__PURE__ */ new Set();
2780
+ function buildJavaHookLines(h) {
2781
+ const lines = [` // ${h.name}`];
2782
+ const method = h.method.toLowerCase();
2783
+ const path2 = h.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
2784
+ const parsed = parsePostScript(h.postRequestScript);
2785
+ if (parsed.extractions.length > 0) {
2786
+ lines.push(` var hookResponse = given().spec(requestSpec)`);
2787
+ if (h.body.mode === "json" && h.body.json) {
2788
+ const escaped = h.body.json.replace(/"/g, '\\"').replace(/\n/g, "\\n");
2789
+ lines.push(` .body("${escaped}")`);
2790
+ }
2791
+ lines.push(` .when().${method}("${path2}");`);
2792
+ for (const e of parsed.extractions) {
2793
+ const jp = accessorToJsonPath(e.accessor);
2794
+ const varName = toEnvConst(e.varName);
2795
+ sharedVars.add(varName);
2796
+ lines.push(` ${varName} = hookResponse.jsonPath().getString("${jp}");`);
2797
+ }
2798
+ } else {
2799
+ lines.push(` given().spec(requestSpec).when().${method}("${path2}");`);
2800
+ }
2801
+ return lines;
2802
+ }
2803
+ if (beforeAllH.length) {
2804
+ const lines = beforeAllH.flatMap(buildJavaHookLines);
2805
+ methods.push(` @BeforeAll
2806
+ static void beforeAll() {
2807
+ ${lines.join("\n")}
2808
+ }`);
2809
+ }
2810
+ if (afterAllH.length) {
2811
+ const lines = afterAllH.flatMap(buildJavaHookLines);
2812
+ methods.push(` @AfterAll
2813
+ static void afterAll() {
2814
+ ${lines.join("\n")}
2815
+ }`);
2816
+ }
2132
2817
  const usedNames = /* @__PURE__ */ new Set();
2133
2818
  const nameMap = /* @__PURE__ */ new Map();
2134
2819
  for (const reqId of folder.requestIds) {
2135
2820
  const req = requests[reqId];
2136
- if (!req) continue;
2821
+ if (!req || req.disabled || req.hookType) continue;
2137
2822
  const base = javaMethod(req.name);
2138
2823
  let name = base;
2139
2824
  if (usedNames.has(name)) {
@@ -2146,12 +2831,14 @@ function buildTestClass(folderName, folder, requests) {
2146
2831
  }
2147
2832
  for (const reqId of folder.requestIds) {
2148
2833
  const req = requests[reqId];
2149
- if (!req) continue;
2834
+ if (!req || req.disabled || req.hookType) continue;
2150
2835
  const methodName = nameMap.get(reqId);
2151
2836
  const method = req.method.toLowerCase();
2152
2837
  const path2 = req.url.replace(/^https?:\/\/[^/]+/, "").replace(/^\{\{[^}]+\}\}/, "") || "/";
2153
- const javaPath = interpolateJava(path2);
2154
- const enabledHeaders = req.headers.filter((h) => h.enabled && h.key);
2838
+ const javaPath = interpolateJava(path2, sharedVars);
2839
+ const inherited = requestCollection.resolveInheritedAuthAndHeaders(reqId, collection);
2840
+ const effectiveAuth = req.auth.type !== "none" ? req.auth : inherited.auth ?? req.auth;
2841
+ const allHeaders = [...inherited.headers.filter((h) => h.enabled && h.key), ...req.headers.filter((h) => h.enabled && h.key)];
2155
2842
  const enabledParams = req.params.filter((p) => p.enabled && p.key);
2156
2843
  const hasBody = req.body.mode !== "none" && !["get", "head"].includes(method);
2157
2844
  const lines = [];
@@ -2159,34 +2846,82 @@ function buildTestClass(folderName, folder, requests) {
2159
2846
  lines.push(` public void ${methodName}() {`);
2160
2847
  lines.push(` given()`);
2161
2848
  lines.push(` .spec(requestSpec)`);
2162
- for (const h of enabledHeaders) {
2163
- lines.push(` .header("${h.key}", ${interpolateJava(h.value)})`);
2849
+ if (effectiveAuth.type === "bearer") {
2850
+ const token = effectiveAuth.token ?? "";
2851
+ if (token.includes("{{")) {
2852
+ const varRef = token.match(/\{\{([^}]+)\}\}/)?.[1]?.trim();
2853
+ const envKey = varRef ? toEnvConst(varRef) : "";
2854
+ if (envKey && sharedVars.has(envKey)) {
2855
+ lines.push(` .header("Authorization", "Bearer " + ${envKey})`);
2856
+ } else {
2857
+ lines.push(` .header("Authorization", "Bearer " + ${interpolateJava(token, sharedVars)})`);
2858
+ }
2859
+ } else {
2860
+ lines.push(` .header("Authorization", "Bearer " + System.getenv("${toEnvConst(effectiveAuth.tokenSecretRef ?? "API_TOKEN")}"))`);
2861
+ }
2862
+ }
2863
+ for (const h of allHeaders) {
2864
+ lines.push(` .header("${h.key}", ${interpolateJava(h.value, sharedVars)})`);
2164
2865
  }
2165
2866
  for (const p of enabledParams) {
2166
- lines.push(` .queryParam("${p.key}", ${interpolateJava(p.value)})`);
2867
+ lines.push(` .queryParam("${p.key}", ${interpolateJava(p.value, sharedVars)})`);
2167
2868
  }
2168
2869
  if (hasBody) {
2169
2870
  if (req.body.mode === "json" && req.body.json) {
2170
- const escaped = req.body.json.replace(/"/g, '\\"').replace(/\n/g, "\\n");
2171
- lines.push(` .body("${escaped}")`);
2871
+ const escaped = req.body.json.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
2872
+ lines.push(` .body(${interpolateJava(escaped, sharedVars)})`);
2172
2873
  }
2173
2874
  }
2174
2875
  lines.push(` .when()`);
2175
2876
  lines.push(` .${method}(${javaPath})`);
2176
2877
  lines.push(` .then()`);
2177
- lines.push(` .statusCode(200);`);
2178
- lines.push(` // .body("field", equalTo("value"));`);
2878
+ const parsed = parsePostScript(req.postRequestScript);
2879
+ if (parsed.assertions.length > 0) {
2880
+ for (const a of parsed.assertions) {
2881
+ const jp = accessorToJsonPath(a.accessor);
2882
+ switch (a.kind) {
2883
+ case "status":
2884
+ lines.push(` .statusCode(${a.expected ?? 200})`);
2885
+ break;
2886
+ case "equals":
2887
+ if (a.expected?.startsWith('"')) {
2888
+ lines.push(` .body("${jp}", equalTo(${a.expected}))`);
2889
+ } else {
2890
+ lines.push(` .body("${jp}", equalTo(${a.expected}))`);
2891
+ }
2892
+ break;
2893
+ case "contains":
2894
+ lines.push(` .body("${jp}", containsString(${a.expected}))`);
2895
+ break;
2896
+ case "exists":
2897
+ lines.push(` .body("${jp}", notNullValue())`);
2898
+ break;
2899
+ case "type":
2900
+ lines.push(` .body("${jp}", instanceOf(${javaTypeFor(a.expected)}))`);
2901
+ break;
2902
+ case "above":
2903
+ lines.push(` .body("${jp}", greaterThan(${a.expected}))`);
2904
+ break;
2905
+ }
2906
+ }
2907
+ lines[lines.length - 1] += ";";
2908
+ } else {
2909
+ lines.push(` .statusCode(200);`);
2910
+ }
2179
2911
  lines.push(` }`);
2180
2912
  methods.push(lines.join("\n"));
2181
2913
  }
2914
+ const hasHooks = beforeAllH.length > 0 || afterAllH.length > 0;
2915
+ const fieldDecls = Array.from(sharedVars).map((v) => ` private static String ${v} = "";`).join("\n");
2182
2916
  return `package com.example.api;
2183
2917
 
2184
2918
  import org.junit.jupiter.api.Test;
2185
-
2919
+ ${hasHooks ? "import org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.AfterAll;\n" : ""}
2186
2920
  import static io.restassured.RestAssured.given;
2187
2921
  import static org.hamcrest.Matchers.*;
2188
2922
 
2189
2923
  public class ${className} extends BaseTest {
2924
+ ${fieldDecls ? "\n" + fieldDecls + "\n" : ""}
2190
2925
 
2191
2926
  ${methods.join("\n\n")}
2192
2927
  }
@@ -2244,7 +2979,7 @@ function generateRestAssured(collection, environment) {
2244
2979
  const className = javaClass(name) + "Test";
2245
2980
  files.push({
2246
2981
  path: `src/test/java/com/example/api/${className}.java`,
2247
- content: buildTestClass(name, folder, collection.requests)
2982
+ content: buildTestClass(name, folder, collection)
2248
2983
  });
2249
2984
  }
2250
2985
  for (const sub of folder.folders) {
@@ -2340,6 +3075,10 @@ async function buildDispatcher(proxy, tls) {
2340
3075
  return void 0;
2341
3076
  }
2342
3077
  async function executeOne(req, collectionVars, envVars, globals, localVars, dispatcher, piiMaskPatterns) {
3078
+ if (!req.headers) req.headers = [];
3079
+ if (!req.params) req.params = [];
3080
+ if (!req.body) req.body = { mode: "none" };
3081
+ if (!req.auth) req.auth = { type: "none" };
2343
3082
  const base = {
2344
3083
  requestId: req.id,
2345
3084
  name: req.name,
@@ -2347,13 +3086,14 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2347
3086
  resolvedUrl: "",
2348
3087
  status: "running"
2349
3088
  };
2350
- let vars = requestHandler.mergeVars(envVars, collectionVars, globals, localVars);
3089
+ const dynamicVars = await requestCollection.buildDynamicVars();
3090
+ let vars = requestCollection.mergeVars(envVars, collectionVars, globals, localVars, dynamicVars);
2351
3091
  let updatedEnvVars = { ...envVars };
2352
3092
  let updatedCollectionVars = { ...collectionVars };
2353
3093
  let updatedGlobals = { ...globals };
2354
3094
  let preScriptError;
2355
3095
  if (req.preRequestScript?.trim()) {
2356
- const r = await requestHandler.runScript(requestHandler.interpolate(req.preRequestScript, vars), {
3096
+ const r = await requestCollection.runScript(requestCollection.interpolate(req.preRequestScript, vars), {
2357
3097
  envVars: { ...envVars },
2358
3098
  collectionVars: { ...collectionVars },
2359
3099
  globals: { ...globals },
@@ -2364,11 +3104,11 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2364
3104
  updatedEnvVars = r.updatedEnvVars;
2365
3105
  updatedCollectionVars = r.updatedCollectionVars;
2366
3106
  updatedGlobals = r.updatedGlobals;
2367
- requestHandler.patchGlobals(r.updatedGlobals);
2368
- await requestHandler.persistGlobals();
2369
- vars = requestHandler.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars);
3107
+ requestCollection.patchGlobals(r.updatedGlobals);
3108
+ await requestCollection.persistGlobals();
3109
+ vars = requestCollection.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
2370
3110
  }
2371
- const resolvedUrl = requestHandler.buildUrl(req.url, req.params, vars);
3111
+ const resolvedUrl = requestCollection.buildUrl(req.url, req.params, vars);
2372
3112
  base.resolvedUrl = resolvedUrl;
2373
3113
  const start = Date.now();
2374
3114
  try {
@@ -2377,23 +3117,23 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2377
3117
  const tokenMissing = !req.auth.oauth2CachedToken;
2378
3118
  const tokenExpired = req.auth.oauth2TokenExpiry ? req.auth.oauth2TokenExpiry <= now + 5e3 : true;
2379
3119
  if (tokenMissing || tokenExpired) {
2380
- const result = await requestHandler.fetchOAuth2Token(req.auth, vars);
3120
+ const result = await requestCollection.fetchOAuth2Token(req.auth, vars);
2381
3121
  req.auth.oauth2CachedToken = result.accessToken;
2382
3122
  req.auth.oauth2TokenExpiry = result.expiresAt;
2383
3123
  }
2384
3124
  }
2385
- const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
3125
+ const authHeaders = await requestCollection.buildAuthHeaders(req.auth, vars);
2386
3126
  const headers = new undici.Headers();
2387
3127
  for (const h of req.headers) {
2388
- if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
3128
+ if (h.enabled && h.key) headers.set(requestCollection.interpolate(h.key, vars), requestCollection.interpolate(h.value, vars));
2389
3129
  }
2390
3130
  for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
2391
3131
  let body;
2392
3132
  if (req.body.mode === "json" && req.body.json) {
2393
- body = requestHandler.interpolate(req.body.json, vars);
3133
+ body = requestCollection.interpolate(req.body.json, vars);
2394
3134
  if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
2395
3135
  } else if (req.body.mode === "raw" && req.body.raw) {
2396
- body = requestHandler.interpolate(req.body.raw, vars);
3136
+ body = requestCollection.interpolate(req.body.raw, vars);
2397
3137
  if (!headers.has("content-type")) headers.set("Content-Type", req.body.rawContentType ?? "text/plain");
2398
3138
  }
2399
3139
  const methodHasBody = !["GET", "HEAD"].includes(req.method);
@@ -2405,14 +3145,14 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2405
3145
  });
2406
3146
  let fetchResp;
2407
3147
  if (req.auth.type === "ntlm") {
2408
- await requestHandler.performNtlmRequest(resolvedUrl, req.method, req.auth, vars);
3148
+ await requestCollection.performNtlmRequest(resolvedUrl, req.method, req.auth, vars);
2409
3149
  fetchResp = await doFetch(headers);
2410
3150
  } else if (req.auth.type === "digest") {
2411
3151
  const probeFetch = (url, init) => undici.fetch(url, {
2412
3152
  ...init,
2413
3153
  dispatcher
2414
3154
  });
2415
- const digestHeader = await requestHandler.performDigestAuth(resolvedUrl, req.method, req.auth, vars, probeFetch);
3155
+ const digestHeader = await requestCollection.performDigestAuth(resolvedUrl, req.method, req.auth, vars, probeFetch);
2416
3156
  if (digestHeader) headers.set("Authorization", digestHeader);
2417
3157
  fetchResp = await doFetch(headers);
2418
3158
  } else {
@@ -2424,8 +3164,8 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2424
3164
  fetchResp.headers.forEach((v, k) => {
2425
3165
  rawRespHeaders[k] = v;
2426
3166
  });
2427
- const maskedBody = requestHandler.maskPii(responseBody, piiMaskPatterns);
2428
- const maskedHeaders = requestHandler.maskHeaders(rawRespHeaders, piiMaskPatterns);
3167
+ const maskedBody = requestCollection.maskPii(responseBody, piiMaskPatterns);
3168
+ const maskedHeaders = requestCollection.maskHeaders(rawRespHeaders, piiMaskPatterns);
2429
3169
  const response = {
2430
3170
  status: fetchResp.status,
2431
3171
  statusText: fetchResp.statusText,
@@ -2434,12 +3174,12 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2434
3174
  bodySize: Buffer.byteLength(responseBody, "utf8"),
2435
3175
  durationMs
2436
3176
  };
2437
- const schemaTestResults = requestHandler.buildSchemaTestResults(req.schema, responseBody);
3177
+ const schemaTestResults = requestCollection.buildSchemaTestResults(req.schema, responseBody);
2438
3178
  let testResults = [...schemaTestResults];
2439
3179
  let consoleOutput = [];
2440
3180
  let postScriptError;
2441
3181
  if (req.postRequestScript?.trim()) {
2442
- const r = await requestHandler.runScript(requestHandler.interpolate(req.postRequestScript, vars), {
3182
+ const r = await requestCollection.runScript(requestCollection.interpolate(req.postRequestScript, vars), {
2443
3183
  envVars: updatedEnvVars,
2444
3184
  collectionVars: updatedCollectionVars,
2445
3185
  globals: updatedGlobals,
@@ -2453,14 +3193,14 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2453
3193
  updatedCollectionVars = r.updatedCollectionVars;
2454
3194
  updatedGlobals = r.updatedGlobals;
2455
3195
  localVars = r.updatedLocalVars;
2456
- requestHandler.patchGlobals(r.updatedGlobals);
2457
- await requestHandler.persistGlobals();
3196
+ requestCollection.patchGlobals(r.updatedGlobals);
3197
+ await requestCollection.persistGlobals();
2458
3198
  }
2459
3199
  const allPassed = testResults.every((t) => t.passed);
2460
3200
  const httpFailed = fetchResp.status >= 400;
2461
3201
  const hasTests = testResults.length > 0;
2462
- const status = postScriptError ? "error" : httpFailed ? "failed" : hasTests ? allPassed ? "passed" : "failed" : "skipped";
2463
- if (httpFailed && !testResults.some((t) => !t.passed)) {
3202
+ const status = postScriptError ? "error" : hasTests ? allPassed ? "passed" : "failed" : httpFailed ? "failed" : "skipped";
3203
+ if (httpFailed && testResults.length === 0) {
2464
3204
  testResults = [
2465
3205
  ...testResults,
2466
3206
  {
@@ -2517,8 +3257,8 @@ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
2517
3257
  function registerRunnerHandler(ipc) {
2518
3258
  ipc.handle("runner:start", async (event, payload) => {
2519
3259
  const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
2520
- const envVars = await requestHandler.buildEnvVars(environment);
2521
- const liveGlobals = requestHandler.getGlobals();
3260
+ const envVars = await requestCollection.buildEnvVars(environment);
3261
+ const liveGlobals = requestCollection.getGlobals();
2522
3262
  const globals = { ...payloadGlobals, ...liveGlobals };
2523
3263
  const dispatcher = await buildDispatcher(proxy, tls);
2524
3264
  const summary = { total: items.length, passed: 0, failed: 0, errors: 0, skipped: 0, durationMs: 0 };
@@ -2652,14 +3392,14 @@ function registerOAuth2Handlers(ipc) {
2652
3392
  ipc.handle("oauth2:startFlow", async (_e, auth, vars) => {
2653
3393
  const port = auth.oauth2RedirectPort ?? 9876;
2654
3394
  const redirectUri = `http://localhost:${port}/callback`;
2655
- const authUrl = requestHandler.interpolate(auth.oauth2AuthUrl ?? "", vars);
2656
- const tokenUrl = requestHandler.interpolate(auth.oauth2TokenUrl ?? "", vars);
2657
- const clientId = requestHandler.interpolate(auth.oauth2ClientId ?? "", vars);
3395
+ const authUrl = requestCollection.interpolate(auth.oauth2AuthUrl ?? "", vars);
3396
+ const tokenUrl = requestCollection.interpolate(auth.oauth2TokenUrl ?? "", vars);
3397
+ const clientId = requestCollection.interpolate(auth.oauth2ClientId ?? "", vars);
2658
3398
  let clientSecret = auth.oauth2ClientSecret ?? "";
2659
3399
  if (!clientSecret && auth.oauth2ClientSecretRef) {
2660
- clientSecret = await requestHandler.getSecret(auth.oauth2ClientSecretRef) ?? "";
3400
+ clientSecret = await requestCollection.getSecret(auth.oauth2ClientSecretRef) ?? "";
2661
3401
  }
2662
- clientSecret = requestHandler.interpolate(clientSecret, vars);
3402
+ clientSecret = requestCollection.interpolate(clientSecret, vars);
2663
3403
  if (!authUrl) throw new Error("OAuth 2.0: authUrl is required for authorization_code flow.");
2664
3404
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for authorization_code flow.");
2665
3405
  if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
@@ -2734,13 +3474,13 @@ function registerOAuth2Handlers(ipc) {
2734
3474
  };
2735
3475
  });
2736
3476
  ipc.handle("oauth2:refreshToken", async (_e, auth, vars, refreshToken) => {
2737
- const tokenUrl = requestHandler.interpolate(auth.oauth2TokenUrl ?? "", vars);
2738
- const clientId = requestHandler.interpolate(auth.oauth2ClientId ?? "", vars);
3477
+ const tokenUrl = requestCollection.interpolate(auth.oauth2TokenUrl ?? "", vars);
3478
+ const clientId = requestCollection.interpolate(auth.oauth2ClientId ?? "", vars);
2739
3479
  let clientSecret = auth.oauth2ClientSecret ?? "";
2740
3480
  if (!clientSecret && auth.oauth2ClientSecretRef) {
2741
- clientSecret = await requestHandler.getSecret(auth.oauth2ClientSecretRef) ?? "";
3481
+ clientSecret = await requestCollection.getSecret(auth.oauth2ClientSecretRef) ?? "";
2742
3482
  }
2743
- clientSecret = requestHandler.interpolate(clientSecret, vars);
3483
+ clientSecret = requestCollection.interpolate(clientSecret, vars);
2744
3484
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for refresh.");
2745
3485
  const { fetch: nodeFetch } = await import("undici");
2746
3486
  const params = new URLSearchParams();
@@ -3188,21 +3928,21 @@ function validateConsumerResponse(contract, actualStatus, actualHeaders, bodyTex
3188
3928
  return violations;
3189
3929
  }
3190
3930
  async function executeContract(req, vars) {
3191
- const url = requestHandler.buildUrl(req.url, req.params, vars);
3931
+ const url = requestCollection.buildUrl(req.url, req.params, vars);
3192
3932
  const start = Date.now();
3193
3933
  try {
3194
3934
  const headers = new undici.Headers();
3195
3935
  for (const h of req.headers) {
3196
- if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
3936
+ if (h.enabled && h.key) headers.set(requestCollection.interpolate(h.key, vars), requestCollection.interpolate(h.value, vars));
3197
3937
  }
3198
- const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
3938
+ const authHeaders = await requestCollection.buildAuthHeaders(req.auth, vars);
3199
3939
  for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
3200
3940
  let body;
3201
3941
  if (req.body.mode === "json" && req.body.json) {
3202
- body = requestHandler.interpolate(req.body.json, vars);
3942
+ body = requestCollection.interpolate(req.body.json, vars);
3203
3943
  if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
3204
3944
  } else if (req.body.mode === "raw" && req.body.raw) {
3205
- body = requestHandler.interpolate(req.body.raw, vars);
3945
+ body = requestCollection.interpolate(req.body.raw, vars);
3206
3946
  }
3207
3947
  const resp = await undici.fetch(url, {
3208
3948
  method: req.method,
@@ -3357,7 +4097,7 @@ function validateRequestAgainstSpec(spec, req, envVars, requestBaseUrl) {
3357
4097
  const jsonContent = content["application/json"];
3358
4098
  if (jsonContent?.["schema"]) {
3359
4099
  try {
3360
- const data = JSON.parse(requestHandler.interpolate(req.body.json, vars));
4100
+ const data = JSON.parse(requestCollection.interpolate(req.body.json, vars));
3361
4101
  const schema = resolveSchema(spec, jsonContent["schema"]);
3362
4102
  const validate = ajv.compile(schema);
3363
4103
  if (!validate(data)) {
@@ -3494,18 +4234,18 @@ function getProviderResponseSchema(spec, req, envVars, statusCode, requestBaseUr
3494
4234
  return null;
3495
4235
  }
3496
4236
  async function executeRequest(req, vars) {
3497
- const url = requestHandler.buildUrl(req.url, req.params, vars);
4237
+ const url = requestCollection.buildUrl(req.url, req.params, vars);
3498
4238
  const start = Date.now();
3499
4239
  try {
3500
4240
  const headers = new undici.Headers();
3501
4241
  for (const h of req.headers) {
3502
- if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
4242
+ if (h.enabled && h.key) headers.set(requestCollection.interpolate(h.key, vars), requestCollection.interpolate(h.value, vars));
3503
4243
  }
3504
- const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
4244
+ const authHeaders = await requestCollection.buildAuthHeaders(req.auth, vars);
3505
4245
  for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
3506
4246
  let body;
3507
4247
  if (req.body.mode === "json" && req.body.json) {
3508
- body = requestHandler.interpolate(req.body.json, vars);
4248
+ body = requestCollection.interpolate(req.body.json, vars);
3509
4249
  if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
3510
4250
  }
3511
4251
  const resp = await undici.fetch(url, {
@@ -3531,7 +4271,7 @@ async function runBidirectional(requests, envVars, collectionVars = {}, specUrl,
3531
4271
  (r) => !r.disabled && r.contract && (r.contract.statusCode !== void 0 || r.contract.bodySchema || r.contract.headers?.length)
3532
4272
  );
3533
4273
  const results = await Promise.all(contractRequests.map(async (req) => {
3534
- const url = requestHandler.buildUrl(req.url, req.params, vars);
4274
+ const url = requestCollection.buildUrl(req.url, req.params, vars);
3535
4275
  const violations = [];
3536
4276
  const expectedStatus = req.contract.statusCode ?? 200;
3537
4277
  const consumerSchema = req.contract.bodySchema ? (() => {
@@ -3857,7 +4597,7 @@ function createWindow() {
3857
4597
  });
3858
4598
  if (process.platform === "win32") {
3859
4599
  const version = electron.app.getVersion();
3860
- win.setTitle(`api Spector${version ? ` v${version}` : ""}`);
4600
+ win.setTitle(`API Spector${version ? ` v${version}` : ""}`);
3861
4601
  win.webContents.on("page-title-updated", (e) => e.preventDefault());
3862
4602
  }
3863
4603
  win.webContents.on("before-input-event", (_e, input) => {
@@ -3868,10 +4608,10 @@ function createWindow() {
3868
4608
  }
3869
4609
  electron.app.whenReady().then(async () => {
3870
4610
  if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
3871
- await requestHandler.initSecretStore(electron.app.getPath("userData"));
4611
+ await requestCollection.initSecretStore(electron.app.getPath("userData"));
3872
4612
  registerFileHandlers(electron.ipcMain);
3873
- requestHandler.registerRequestHandler(electron.ipcMain);
3874
- requestHandler.registerSecretHandlers(electron.ipcMain);
4613
+ requestCollection.registerRequestHandler(electron.ipcMain);
4614
+ requestCollection.registerSecretHandlers(electron.ipcMain);
3875
4615
  registerImportHandlers(electron.ipcMain);
3876
4616
  registerGenerateHandlers(electron.ipcMain);
3877
4617
  registerRunnerHandler(electron.ipcMain);