apitestcenter 0.9.0 → 0.10.0

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 (56) hide show
  1. package/dist/THIRD-PARTY-NOTICES-UI.txt +1 -1
  2. package/dist/THIRD-PARTY-NOTICES.txt +1 -1
  3. package/dist/cli.mjs +564 -170
  4. package/dist/ui/404/index.html +1 -1
  5. package/dist/ui/404.html +1 -1
  6. package/dist/ui/__next.__PAGE__.txt +15 -14
  7. package/dist/ui/__next._full.txt +13 -12
  8. package/dist/ui/__next._tree.txt +2 -2
  9. package/dist/ui/_next/static/chunks/{2od90u8fd_sla.js → 0f9cyrqzolhzx.js} +1 -1
  10. package/dist/ui/_next/static/chunks/0imlzn2evcfw6.css +3 -0
  11. package/dist/ui/_next/static/chunks/{13xdai3-mkg7c.js → 1-ogjtk9dipfq.js} +319 -134
  12. package/dist/ui/_next/static/chunks/{2idsynwg5lkk7.js → 115l_disvbdxa.js} +1 -1
  13. package/dist/ui/_next/static/chunks/{36lx6-56-udwb.js → 1b31b8g5_5h-y.js} +1 -1
  14. package/dist/ui/_next/static/chunks/2qchu_7euauai.js +1 -0
  15. package/dist/ui/_next/static/chunks/{3ad2zh-p8_xhv.js → 2sa_fr4vgkkrp.js} +2 -2
  16. package/dist/ui/_next/static/chunks/{02myfb0nlri-z.js → 3jywp_61ylz0z.js} +1 -1
  17. package/dist/ui/_next/static/media/icon.1oy4kb0w-k_2v.png +0 -0
  18. package/dist/ui/_not-found/__next._full.txt +12 -11
  19. package/dist/ui/_not-found/__next._not-found.__PAGE__.txt +17 -16
  20. package/dist/ui/_not-found/__next._tree.txt +2 -2
  21. package/dist/ui/_not-found/index.html +1 -1
  22. package/dist/ui/_not-found/index.txt +12 -11
  23. package/dist/ui/compare/__next._full.txt +13 -12
  24. package/dist/ui/compare/__next._tree.txt +2 -2
  25. package/dist/ui/compare/__next.compare.__PAGE__.txt +18 -17
  26. package/dist/ui/compare/index.html +1 -1
  27. package/dist/ui/compare/index.txt +13 -12
  28. package/dist/ui/history/__next._full.txt +14 -13
  29. package/dist/ui/history/__next._tree.txt +2 -2
  30. package/dist/ui/history/__next.history.__PAGE__.txt +19 -18
  31. package/dist/ui/history/index.html +1 -1
  32. package/dist/ui/history/index.txt +14 -13
  33. package/dist/ui/icon.png +0 -0
  34. package/dist/ui/index.html +1 -1
  35. package/dist/ui/index.txt +13 -12
  36. package/dist/ui/preview/__next._full.txt +14 -13
  37. package/dist/ui/preview/__next._tree.txt +2 -2
  38. package/dist/ui/preview/__next.preview.__PAGE__.txt +19 -18
  39. package/dist/ui/preview/index.html +1 -1
  40. package/dist/ui/preview/index.txt +14 -13
  41. package/dist/ui/suite/__next._full.txt +13 -12
  42. package/dist/ui/suite/__next._tree.txt +2 -2
  43. package/dist/ui/suite/__next.suite.__PAGE__.txt +18 -17
  44. package/dist/ui/suite/index.html +1 -1
  45. package/dist/ui/suite/index.txt +13 -12
  46. package/dist/ui/tests/__next._full.txt +12 -11
  47. package/dist/ui/tests/__next._tree.txt +2 -2
  48. package/dist/ui/tests/__next.tests.__PAGE__.txt +17 -16
  49. package/dist/ui/tests/index.html +1 -1
  50. package/dist/ui/tests/index.txt +12 -11
  51. package/package.json +1 -1
  52. package/dist/ui/_next/static/chunks/2i7s5kuurlkr6.css +0 -3
  53. package/dist/ui/_next/static/chunks/2nxnxp6p-40kg.js +0 -1
  54. /package/dist/ui/_next/static/{C6zMFzDkVqF8IXny_v7JK → 9enzqastnBYCi6FVPT4mz}/_buildManifest.js +0 -0
  55. /package/dist/ui/_next/static/{C6zMFzDkVqF8IXny_v7JK → 9enzqastnBYCi6FVPT4mz}/_clientMiddlewareManifest.js +0 -0
  56. /package/dist/ui/_next/static/{C6zMFzDkVqF8IXny_v7JK → 9enzqastnBYCi6FVPT4mz}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -231,15 +231,15 @@ function normalizeView(raw) {
231
231
  const v = raw;
232
232
  if (typeof v.id !== "string" || !v.id || typeof v.name !== "string") return null;
233
233
  const scope = v.scope === "compare" ? "compare" : "run";
234
- const seen = /* @__PURE__ */ new Set();
234
+ const seen2 = /* @__PURE__ */ new Set();
235
235
  const widgets = [];
236
236
  for (const item of Array.isArray(v.widgets) ? v.widgets : []) {
237
237
  const w = item;
238
238
  const def = METRICS[w.metric];
239
239
  if (!def || scope === "compare" && !def.compare) continue;
240
240
  let id = typeof w.id === "string" && w.id ? w.id : `${def.id}-${widgets.length}`;
241
- while (seen.has(id)) id = `${id}-x`;
242
- seen.add(id);
241
+ while (seen2.has(id)) id = `${id}-x`;
242
+ seen2.add(id);
243
243
  const kind = def.kinds.includes(w.kind) ? w.kind : def.kinds[0];
244
244
  const width = clamp(w.w ?? def.size.w, def.min.w, GRID_COLUMNS);
245
245
  widgets.push({
@@ -781,12 +781,12 @@ function variablesOf(env) {
781
781
  }
782
782
  function normalizeEnvironments(raw) {
783
783
  if (!Array.isArray(raw)) return [];
784
- const seen = /* @__PURE__ */ new Set();
784
+ const seen2 = /* @__PURE__ */ new Set();
785
785
  const out = [];
786
786
  for (const item of raw) {
787
787
  const e = item;
788
- if (typeof e?.id !== "string" || !e.id || seen.has(e.id)) continue;
789
- seen.add(e.id);
788
+ if (typeof e?.id !== "string" || !e.id || seen2.has(e.id)) continue;
789
+ seen2.add(e.id);
790
790
  const keys = /* @__PURE__ */ new Set();
791
791
  const variables = [];
792
792
  for (const v of Array.isArray(e.variables) ? e.variables : []) {
@@ -12334,14 +12334,14 @@ function initializeContext(params) {
12334
12334
  function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
12335
12335
  var _a3;
12336
12336
  const def = schema._zod.def;
12337
- const seen = ctx.seen.get(schema);
12338
- if (seen) {
12339
- seen.count++;
12337
+ const seen2 = ctx.seen.get(schema);
12338
+ if (seen2) {
12339
+ seen2.count++;
12340
12340
  const isCycle = _params.schemaPath.includes(schema);
12341
12341
  if (isCycle) {
12342
- seen.cycle = _params.path;
12342
+ seen2.cycle = _params.path;
12343
12343
  }
12344
- return seen.schema;
12344
+ return seen2.schema;
12345
12345
  }
12346
12346
  const result = { schema: {}, count: 1, cycle: void 0, path: _params.path };
12347
12347
  ctx.seen.set(schema, result);
@@ -12424,12 +12424,12 @@ function extractDefs(ctx, schema) {
12424
12424
  if (entry[1].schema.$ref) {
12425
12425
  return;
12426
12426
  }
12427
- const seen = entry[1];
12427
+ const seen2 = entry[1];
12428
12428
  const { ref, defId } = makeURI(entry);
12429
- seen.def = { ...seen.schema };
12429
+ seen2.def = { ...seen2.schema };
12430
12430
  if (defId)
12431
- seen.defId = defId;
12432
- const schema2 = seen.schema;
12431
+ seen2.defId = defId;
12432
+ const schema2 = seen2.schema;
12433
12433
  for (const key in schema2) {
12434
12434
  delete schema2[key];
12435
12435
  }
@@ -12437,16 +12437,16 @@ function extractDefs(ctx, schema) {
12437
12437
  };
12438
12438
  if (ctx.cycles === "throw") {
12439
12439
  for (const entry of ctx.seen.entries()) {
12440
- const seen = entry[1];
12441
- if (seen.cycle) {
12442
- throw new Error(`Cycle detected: #/${seen.cycle?.join("/")}/<root>
12440
+ const seen2 = entry[1];
12441
+ if (seen2.cycle) {
12442
+ throw new Error(`Cycle detected: #/${seen2.cycle?.join("/")}/<root>
12443
12443
 
12444
12444
  Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`);
12445
12445
  }
12446
12446
  }
12447
12447
  }
12448
12448
  for (const entry of ctx.seen.entries()) {
12449
- const seen = entry[1];
12449
+ const seen2 = entry[1];
12450
12450
  if (schema === entry[0]) {
12451
12451
  extractToDef(entry);
12452
12452
  continue;
@@ -12463,11 +12463,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
12463
12463
  extractToDef(entry);
12464
12464
  continue;
12465
12465
  }
12466
- if (seen.cycle) {
12466
+ if (seen2.cycle) {
12467
12467
  extractToDef(entry);
12468
12468
  continue;
12469
12469
  }
12470
- if (seen.count > 1) {
12470
+ if (seen2.count > 1) {
12471
12471
  if (ctx.reused === "ref") {
12472
12472
  extractToDef(entry);
12473
12473
  continue;
@@ -12480,13 +12480,13 @@ function finalize(ctx, schema) {
12480
12480
  if (!root2)
12481
12481
  throw new Error("Unprocessed schema. This is a bug in Zod.");
12482
12482
  const flattenRef = (zodSchema) => {
12483
- const seen = ctx.seen.get(zodSchema);
12484
- if (seen.ref === null)
12483
+ const seen2 = ctx.seen.get(zodSchema);
12484
+ if (seen2.ref === null)
12485
12485
  return;
12486
- const schema2 = seen.def ?? seen.schema;
12486
+ const schema2 = seen2.def ?? seen2.schema;
12487
12487
  const _cached = { ...schema2 };
12488
- const ref = seen.ref;
12489
- seen.ref = null;
12488
+ const ref = seen2.ref;
12489
+ seen2.ref = null;
12490
12490
  if (ref) {
12491
12491
  flattenRef(ref);
12492
12492
  const refSeen = ctx.seen.get(ref);
@@ -12538,7 +12538,7 @@ function finalize(ctx, schema) {
12538
12538
  ctx.override({
12539
12539
  zodSchema,
12540
12540
  jsonSchema: schema2,
12541
- path: seen.path ?? []
12541
+ path: seen2.path ?? []
12542
12542
  });
12543
12543
  };
12544
12544
  for (const entry of [...ctx.seen.entries()].reverse()) {
@@ -12566,11 +12566,11 @@ function finalize(ctx, schema) {
12566
12566
  delete result.id;
12567
12567
  const defs = ctx.external?.defs ?? {};
12568
12568
  for (const entry of ctx.seen.entries()) {
12569
- const seen = entry[1];
12570
- if (seen.def && seen.defId) {
12571
- if (seen.def.id === seen.defId)
12572
- delete seen.def.id;
12573
- defs[seen.defId] = seen.def;
12569
+ const seen2 = entry[1];
12570
+ if (seen2.def && seen2.defId) {
12571
+ if (seen2.def.id === seen2.defId)
12572
+ delete seen2.def.id;
12573
+ defs[seen2.defId] = seen2.def;
12574
12574
  }
12575
12575
  }
12576
12576
  if (ctx.external) {
@@ -13106,9 +13106,9 @@ var init_json_schema_processors = __esm({
13106
13106
  nullableProcessor = (schema, ctx, json2, params) => {
13107
13107
  const def = schema._zod.def;
13108
13108
  const inner = process2(def.innerType, ctx, params);
13109
- const seen = ctx.seen.get(schema);
13109
+ const seen2 = ctx.seen.get(schema);
13110
13110
  if (ctx.target === "openapi-3.0") {
13111
- seen.ref = def.innerType;
13111
+ seen2.ref = def.innerType;
13112
13112
  json2.nullable = true;
13113
13113
  } else {
13114
13114
  json2.anyOf = [inner, { type: "null" }];
@@ -13117,29 +13117,29 @@ var init_json_schema_processors = __esm({
13117
13117
  nonoptionalProcessor = (schema, ctx, _json, params) => {
13118
13118
  const def = schema._zod.def;
13119
13119
  process2(def.innerType, ctx, params);
13120
- const seen = ctx.seen.get(schema);
13121
- seen.ref = def.innerType;
13120
+ const seen2 = ctx.seen.get(schema);
13121
+ seen2.ref = def.innerType;
13122
13122
  };
13123
13123
  defaultProcessor = (schema, ctx, json2, params) => {
13124
13124
  const def = schema._zod.def;
13125
13125
  process2(def.innerType, ctx, params);
13126
- const seen = ctx.seen.get(schema);
13127
- seen.ref = def.innerType;
13126
+ const seen2 = ctx.seen.get(schema);
13127
+ seen2.ref = def.innerType;
13128
13128
  json2.default = JSON.parse(JSON.stringify(def.defaultValue));
13129
13129
  };
13130
13130
  prefaultProcessor = (schema, ctx, json2, params) => {
13131
13131
  const def = schema._zod.def;
13132
13132
  process2(def.innerType, ctx, params);
13133
- const seen = ctx.seen.get(schema);
13134
- seen.ref = def.innerType;
13133
+ const seen2 = ctx.seen.get(schema);
13134
+ seen2.ref = def.innerType;
13135
13135
  if (ctx.io === "input")
13136
13136
  json2._prefault = JSON.parse(JSON.stringify(def.defaultValue));
13137
13137
  };
13138
13138
  catchProcessor = (schema, ctx, json2, params) => {
13139
13139
  const def = schema._zod.def;
13140
13140
  process2(def.innerType, ctx, params);
13141
- const seen = ctx.seen.get(schema);
13142
- seen.ref = def.innerType;
13141
+ const seen2 = ctx.seen.get(schema);
13142
+ seen2.ref = def.innerType;
13143
13143
  let catchValue;
13144
13144
  try {
13145
13145
  catchValue = def.catchValue(void 0);
@@ -13153,33 +13153,33 @@ var init_json_schema_processors = __esm({
13153
13153
  const inIsTransform = def.in._zod.traits.has("$ZodTransform");
13154
13154
  const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
13155
13155
  process2(innerType, ctx, params);
13156
- const seen = ctx.seen.get(schema);
13157
- seen.ref = innerType;
13156
+ const seen2 = ctx.seen.get(schema);
13157
+ seen2.ref = innerType;
13158
13158
  };
13159
13159
  readonlyProcessor = (schema, ctx, json2, params) => {
13160
13160
  const def = schema._zod.def;
13161
13161
  process2(def.innerType, ctx, params);
13162
- const seen = ctx.seen.get(schema);
13163
- seen.ref = def.innerType;
13162
+ const seen2 = ctx.seen.get(schema);
13163
+ seen2.ref = def.innerType;
13164
13164
  json2.readOnly = true;
13165
13165
  };
13166
13166
  promiseProcessor = (schema, ctx, _json, params) => {
13167
13167
  const def = schema._zod.def;
13168
13168
  process2(def.innerType, ctx, params);
13169
- const seen = ctx.seen.get(schema);
13170
- seen.ref = def.innerType;
13169
+ const seen2 = ctx.seen.get(schema);
13170
+ seen2.ref = def.innerType;
13171
13171
  };
13172
13172
  optionalProcessor = (schema, ctx, _json, params) => {
13173
13173
  const def = schema._zod.def;
13174
13174
  process2(def.innerType, ctx, params);
13175
- const seen = ctx.seen.get(schema);
13176
- seen.ref = def.innerType;
13175
+ const seen2 = ctx.seen.get(schema);
13176
+ seen2.ref = def.innerType;
13177
13177
  };
13178
13178
  lazyProcessor = (schema, ctx, _json, params) => {
13179
13179
  const innerType = schema._zod.innerType;
13180
13180
  process2(innerType, ctx, params);
13181
- const seen = ctx.seen.get(schema);
13182
- seen.ref = innerType;
13181
+ const seen2 = ctx.seen.get(schema);
13182
+ seen2.ref = innerType;
13183
13183
  };
13184
13184
  allProcessors = {
13185
13185
  string: stringProcessor,
@@ -16068,13 +16068,41 @@ function checkLabel(check2) {
16068
16068
  case "status":
16069
16069
  return `status is ${check2.expect.replace(/\s+/g, "")}`;
16070
16070
  case "body":
16071
+ if (check2.op === "is_json") return "body is JSON";
16072
+ if (check2.op === "matches") return `body matches /${clip(check2.value)}/`;
16071
16073
  return `body ${check2.op === "contains" ? "contains" : "does not contain"} "${clip(check2.value)}"`;
16072
- case "json":
16073
- return check2.op === "exists" || check2.op === "not_exists" ? `${jsonPathLabel(check2.path)} ${check2.op === "exists" ? "exists" : "does not exist"}` : `${jsonPathLabel(check2.path)} ${check2.op === "equals" ? "=" : check2.op === "not_equals" ? "\u2260" : "contains"} "${clip(check2.value)}"`;
16074
+ case "json": {
16075
+ const path = jsonPathLabel(check2.path);
16076
+ switch (check2.op) {
16077
+ case "exists":
16078
+ return `${path} exists`;
16079
+ case "not_exists":
16080
+ return `${path} does not exist`;
16081
+ case "greater":
16082
+ return `${path} > ${clip(check2.value)}`;
16083
+ case "less":
16084
+ return `${path} < ${clip(check2.value)}`;
16085
+ case "matches":
16086
+ return `${path} matches /${clip(check2.value)}/`;
16087
+ case "type":
16088
+ return `${path} is ${/^[aeiou]/.test(check2.value) ? "an" : "a"} ${check2.value || "?"}`;
16089
+ case "length":
16090
+ return `length of ${path} ${lengthRule(check2.value).label}`;
16091
+ default:
16092
+ return `${path} ${check2.op === "equals" ? "=" : check2.op === "not_equals" ? "\u2260" : "contains"} "${clip(check2.value)}"`;
16093
+ }
16094
+ }
16074
16095
  case "header":
16075
- return check2.op === "exists" ? `header ${check2.name} exists` : `header ${check2.name} ${check2.op === "equals" ? "=" : "contains"} "${clip(check2.value)}"`;
16096
+ if (check2.op === "exists") return `header ${check2.name} exists`;
16097
+ if (check2.op === "not_exists") return `header ${check2.name} does not exist`;
16098
+ if (check2.op === "matches") return `header ${check2.name} matches /${clip(check2.value)}/`;
16099
+ return `header ${check2.name} ${check2.op === "equals" ? "=" : "contains"} "${clip(check2.value)}"`;
16076
16100
  case "time":
16077
16101
  return `responds within ${check2.maxMs} ms`;
16102
+ case "schema": {
16103
+ const title = schemaTitle(check2.schema);
16104
+ return title ? `body matches schema "${clip(title)}"` : "body matches the JSON schema";
16105
+ }
16078
16106
  }
16079
16107
  }
16080
16108
  function clip(text) {
@@ -16082,26 +16110,86 @@ function clip(text) {
16082
16110
  }
16083
16111
  function jsonPathLabel(path) {
16084
16112
  const trimmed = path.trim().replace(/^\$\.?/, "");
16085
- return `$.${trimmed}`;
16113
+ return trimmed ? `$.${trimmed}` : "$";
16114
+ }
16115
+ function schemaTitle(schema) {
16116
+ try {
16117
+ const parsed = JSON.parse(schema);
16118
+ return typeof parsed?.title === "string" && parsed.title.trim() ? parsed.title.trim() : void 0;
16119
+ } catch {
16120
+ return void 0;
16121
+ }
16122
+ }
16123
+ function lengthRule(value) {
16124
+ const m = /^\s*(>=|<=|!=|>|<|=)?\s*(\d+)\s*$/.exec(value);
16125
+ if (!m) return { label: `is ${value.trim() || "?"}` };
16126
+ const op = m[1] ?? "=";
16127
+ return { label: `${op === "=" ? "=" : op === "!=" ? "\u2260" : op === ">=" ? "\u2265" : op === "<=" ? "\u2264" : op} ${m[2]}` };
16086
16128
  }
16087
16129
  function checksNeedBody(checks) {
16088
- return checks.some((c) => c.type === "body" || c.type === "json");
16130
+ return checks.some((c) => c.type === "body" || c.type === "json" || c.type === "schema");
16089
16131
  }
16090
16132
  function evaluator() {
16091
16133
  compiled ??= new Function(`${CHECK_EVALUATOR_JS}
16092
- return __atcCheck;`)();
16134
+ return { check: __atcCheck, schemaError: __atcSchemaError, jsonGet: __atcJsonGet };`)();
16093
16135
  return compiled;
16094
16136
  }
16137
+ function explain(check2, res) {
16138
+ if (check2.type === "status") return `got ${res.status}`;
16139
+ if (check2.type === "time") return `took ${Math.round(res.durationMs)} ms`;
16140
+ if (check2.type === "header") {
16141
+ const h = res.headers[check2.name.toLowerCase()];
16142
+ return h === void 0 ? "no such header" : `is "${clip(h)}"`;
16143
+ }
16144
+ if (res.body === null) return "no body";
16145
+ if (check2.type === "body") return check2.op === "is_json" ? "not valid JSON" : void 0;
16146
+ let parsed;
16147
+ try {
16148
+ parsed = JSON.parse(res.body);
16149
+ } catch {
16150
+ return "the body is not JSON";
16151
+ }
16152
+ if (check2.type === "schema") {
16153
+ let schema;
16154
+ try {
16155
+ schema = JSON.parse(check2.schema);
16156
+ } catch {
16157
+ return "the schema is not valid JSON";
16158
+ }
16159
+ return evaluator().schemaError(schema, parsed, "", schema, 0) ?? void 0;
16160
+ }
16161
+ if (check2.type === "json") {
16162
+ const got = evaluator().jsonGet(parsed, check2.path);
16163
+ if (!got.found) return check2.op === "not_exists" ? void 0 : "not found";
16164
+ const v = got.value;
16165
+ if (check2.op === "type") return `is ${v === null ? "null" : Array.isArray(v) ? "an array" : typeof v === "object" ? "an object" : `a ${typeof v}`}`;
16166
+ if (check2.op === "length") {
16167
+ const n = typeof v === "string" || Array.isArray(v) ? v.length : v && typeof v === "object" ? Object.keys(v).length : void 0;
16168
+ return n === void 0 ? "has no length" : `length is ${n}`;
16169
+ }
16170
+ if (check2.op === "not_exists") return "it exists";
16171
+ const text = typeof v === "string" ? v : JSON.stringify(v);
16172
+ return `is ${clip(text ?? "")}`;
16173
+ }
16174
+ return void 0;
16175
+ }
16095
16176
  function evaluateChecks(checks, res) {
16096
- const run2 = evaluator();
16177
+ const run2 = evaluator().check;
16097
16178
  return checks.map((check2) => {
16098
16179
  let ok = false;
16099
16180
  try {
16100
- ok = Boolean(run2(check2, res));
16181
+ ok = Boolean(run2({ ...check2 }, res));
16101
16182
  } catch {
16102
16183
  ok = false;
16103
16184
  }
16104
- return { name: checkLabel(check2), ok };
16185
+ if (ok) return { name: checkLabel(check2), ok };
16186
+ let detail;
16187
+ try {
16188
+ detail = explain(check2, res);
16189
+ } catch {
16190
+ detail = void 0;
16191
+ }
16192
+ return detail ? { name: checkLabel(check2), ok, detail } : { name: checkLabel(check2), ok };
16105
16193
  });
16106
16194
  }
16107
16195
  var responseCheckSchema, CHECK_EVALUATOR_JS, compiled;
@@ -16111,20 +16199,28 @@ var init_checks3 = __esm({
16111
16199
  init_zod();
16112
16200
  responseCheckSchema = external_exports.discriminatedUnion("type", [
16113
16201
  external_exports.object({ type: external_exports.literal("status"), expect: external_exports.string().min(1).max(100) }).strict(),
16114
- external_exports.object({ type: external_exports.literal("body"), op: external_exports.enum(["contains", "not_contains"]), value: external_exports.string().min(1).max(2e3) }).strict(),
16202
+ external_exports.object({
16203
+ type: external_exports.literal("body"),
16204
+ op: external_exports.enum(["contains", "not_contains", "matches", "is_json"]),
16205
+ // Not used by is_json.
16206
+ value: external_exports.string().max(2e3).default("")
16207
+ }).strict(),
16115
16208
  external_exports.object({
16116
16209
  type: external_exports.literal("json"),
16117
16210
  path: external_exports.string().min(1).max(300),
16118
- op: external_exports.enum(["exists", "not_exists", "equals", "not_equals", "contains"]),
16211
+ op: external_exports.enum(["exists", "not_exists", "equals", "not_equals", "contains", "greater", "less", "matches", "type", "length"]),
16119
16212
  value: external_exports.string().max(2e3).default("")
16120
16213
  }).strict(),
16121
16214
  external_exports.object({
16122
16215
  type: external_exports.literal("header"),
16123
16216
  name: external_exports.string().min(1).max(200),
16124
- op: external_exports.enum(["exists", "equals", "contains"]),
16217
+ op: external_exports.enum(["exists", "not_exists", "equals", "contains", "matches"]),
16125
16218
  value: external_exports.string().max(2e3).default("")
16126
16219
  }).strict(),
16127
- external_exports.object({ type: external_exports.literal("time"), maxMs: external_exports.number().positive().max(6e5) }).strict()
16220
+ external_exports.object({ type: external_exports.literal("time"), maxMs: external_exports.number().positive().max(6e5) }).strict(),
16221
+ // A JSON Schema (draft 7 subset) the whole body must match.
16222
+ // Kept while still empty (being written); only a JSON object is sent.
16223
+ external_exports.object({ type: external_exports.literal("schema"), schema: external_exports.string().max(2e5) }).strict()
16128
16224
  ]);
16129
16225
  CHECK_EVALUATOR_JS = String.raw`
16130
16226
  function __atcStatusOk(expect, status) {
@@ -16159,28 +16255,167 @@ function __atcAsText(value) {
16159
16255
  if (value === undefined) return "";
16160
16256
  return JSON.stringify(value);
16161
16257
  }
16258
+ function __atcTypeOf(value) {
16259
+ if (value === null) return "null";
16260
+ if (Array.isArray(value)) return "array";
16261
+ return typeof value;
16262
+ }
16263
+ function __atcIsType(value, type) {
16264
+ if (type === "integer") return typeof value === "number" && isFinite(value) && Math.floor(value) === value;
16265
+ if (type === "number") return typeof value === "number" && isFinite(value);
16266
+ return __atcTypeOf(value) === type;
16267
+ }
16268
+ function __atcMatches(pattern, text) {
16269
+ try { return new RegExp(pattern).test(text); } catch (e) { return false; }
16270
+ }
16271
+ function __atcLengthOk(rule, n) {
16272
+ var m = String(rule).match(/^\s*(>=|<=|!=|>|<|=)?\s*(\d+)\s*$/);
16273
+ if (!m) return false;
16274
+ var want = Number(m[2]);
16275
+ var op = m[1] || "=";
16276
+ if (op === ">") return n > want;
16277
+ if (op === "<") return n < want;
16278
+ if (op === ">=") return n >= want;
16279
+ if (op === "<=") return n <= want;
16280
+ if (op === "!=") return n !== want;
16281
+ return n === want;
16282
+ }
16283
+ function __atcPathText(path) { return path === "" ? "$" : "$" + path; }
16284
+ // JSON Schema, the parts APIs use: type (and nullable), enum, const, properties, required,
16285
+ // additionalProperties, items, min/max (Length, Items, imum), pattern, allOf/anyOf/oneOf,
16286
+ // local $ref. Returns the first problem as "$.items[0].id should be number", or null.
16287
+ function __atcSchemaError(schema, value, path, root, depth) {
16288
+ if (schema === true || schema === undefined || schema === null) return null;
16289
+ if (schema === false) return __atcPathText(path) + " should not be there";
16290
+ if (depth > 60) return null;
16291
+ if (typeof schema.$ref === "string") {
16292
+ var ref = schema.$ref.replace(/^#\/?/, "").split("/");
16293
+ var target = root;
16294
+ for (var r = 0; r < ref.length && target; r++) if (ref[r]) target = target[ref[r].replace(/~1/g, "/").replace(/~0/g, "~")];
16295
+ return target ? __atcSchemaError(target, value, path, root, depth + 1) : null;
16296
+ }
16297
+ var where = __atcPathText(path);
16298
+ if (schema.type !== undefined) {
16299
+ var types = Array.isArray(schema.type) ? schema.type : [schema.type];
16300
+ if (schema.nullable === true) types = types.concat(["null"]);
16301
+ var okType = false;
16302
+ for (var t = 0; t < types.length; t++) if (__atcIsType(value, types[t])) okType = true;
16303
+ if (!okType) return where + " should be " + types.join(" or ") + " (it is " + __atcTypeOf(value) + ")";
16304
+ }
16305
+ if (schema.enum && Array.isArray(schema.enum)) {
16306
+ var inEnum = false;
16307
+ for (var e = 0; e < schema.enum.length; e++) if (JSON.stringify(schema.enum[e]) === JSON.stringify(value)) inEnum = true;
16308
+ if (!inEnum) return where + " should be one of " + JSON.stringify(schema.enum);
16309
+ }
16310
+ if (schema.const !== undefined && JSON.stringify(schema.const) !== JSON.stringify(value)) return where + " should be " + JSON.stringify(schema.const);
16311
+ if (typeof value === "string") {
16312
+ if (typeof schema.minLength === "number" && value.length < schema.minLength) return where + " should be at least " + schema.minLength + " characters";
16313
+ if (typeof schema.maxLength === "number" && value.length > schema.maxLength) return where + " should be at most " + schema.maxLength + " characters";
16314
+ if (typeof schema.pattern === "string" && !__atcMatches(schema.pattern, value)) return where + " should match /" + schema.pattern + "/";
16315
+ }
16316
+ if (typeof value === "number") {
16317
+ if (typeof schema.minimum === "number" && value < schema.minimum) return where + " should be at least " + schema.minimum;
16318
+ if (typeof schema.maximum === "number" && value > schema.maximum) return where + " should be at most " + schema.maximum;
16319
+ if (typeof schema.exclusiveMinimum === "number" && value <= schema.exclusiveMinimum) return where + " should be more than " + schema.exclusiveMinimum;
16320
+ if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) return where + " should be less than " + schema.exclusiveMaximum;
16321
+ }
16322
+ if (Array.isArray(value)) {
16323
+ if (typeof schema.minItems === "number" && value.length < schema.minItems) return where + " should have at least " + schema.minItems + " items";
16324
+ if (typeof schema.maxItems === "number" && value.length > schema.maxItems) return where + " should have at most " + schema.maxItems + " items";
16325
+ if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
16326
+ for (var i = 0; i < value.length; i++) {
16327
+ var itemErr = __atcSchemaError(schema.items, value[i], path + "[" + i + "]", root, depth + 1);
16328
+ if (itemErr) return itemErr;
16329
+ }
16330
+ }
16331
+ }
16332
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
16333
+ var req = Array.isArray(schema.required) ? schema.required : [];
16334
+ for (var q = 0; q < req.length; q++) if (!Object.prototype.hasOwnProperty.call(value, req[q])) return where + " should have " + req[q];
16335
+ var props = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
16336
+ for (var key in value) {
16337
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
16338
+ var sub = path + (/^[A-Za-z_$][\w$]*$/.test(key) ? "." + key : "[" + JSON.stringify(key) + "]");
16339
+ if (Object.prototype.hasOwnProperty.call(props, key)) {
16340
+ var propErr = __atcSchemaError(props[key], value[key], sub, root, depth + 1);
16341
+ if (propErr) return propErr;
16342
+ } else if (schema.additionalProperties === false) {
16343
+ return __atcPathText(sub) + " is not expected";
16344
+ } else if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
16345
+ var addErr = __atcSchemaError(schema.additionalProperties, value[key], sub, root, depth + 1);
16346
+ if (addErr) return addErr;
16347
+ }
16348
+ }
16349
+ }
16350
+ if (Array.isArray(schema.allOf)) {
16351
+ for (var a = 0; a < schema.allOf.length; a++) {
16352
+ var allErr = __atcSchemaError(schema.allOf[a], value, path, root, depth + 1);
16353
+ if (allErr) return allErr;
16354
+ }
16355
+ }
16356
+ var some = schema.anyOf || schema.oneOf;
16357
+ if (Array.isArray(some) && some.length) {
16358
+ var first = null;
16359
+ var matched = 0;
16360
+ for (var s = 0; s < some.length; s++) {
16361
+ var someErr = __atcSchemaError(some[s], value, path, root, depth + 1);
16362
+ if (!someErr) matched++;
16363
+ else if (!first) first = someErr;
16364
+ }
16365
+ if (!matched) return first;
16366
+ if (schema.oneOf && matched > 1) return where + " matches more than one of oneOf";
16367
+ }
16368
+ return null;
16369
+ }
16370
+ function __atcParsedSchema(check) {
16371
+ if (check.__parsed === undefined) {
16372
+ try { check.__parsed = JSON.parse(check.schema); } catch (e) { check.__parsed = null; }
16373
+ }
16374
+ return check.__parsed;
16375
+ }
16162
16376
  function __atcCheck(check, res) {
16163
16377
  if (check.type === "status") return __atcStatusOk(check.expect, res.status);
16164
16378
  if (check.type === "time") return res.durationMs <= check.maxMs;
16165
16379
  if (check.type === "header") {
16166
16380
  var h = res.headers[String(check.name).toLowerCase()];
16167
16381
  if (check.op === "exists") return h !== undefined;
16382
+ if (check.op === "not_exists") return h === undefined;
16168
16383
  if (h === undefined) return false;
16384
+ if (check.op === "matches") return __atcMatches(check.value, h);
16169
16385
  return check.op === "equals" ? h === check.value : h.indexOf(check.value) >= 0;
16170
16386
  }
16171
16387
  if (res.body === null || res.body === undefined) return false;
16172
16388
  if (check.type === "body") {
16389
+ if (check.op === "is_json") { try { JSON.parse(res.body); return true; } catch (e) { return false; } }
16390
+ if (check.op === "matches") return __atcMatches(check.value, res.body);
16173
16391
  var has = res.body.indexOf(check.value) >= 0;
16174
16392
  return check.op === "contains" ? has : !has;
16175
16393
  }
16394
+ var parsed;
16395
+ try { parsed = JSON.parse(res.body); } catch (e) { return false; }
16396
+ if (check.type === "schema") {
16397
+ var schema = __atcParsedSchema(check);
16398
+ return schema !== null && __atcSchemaError(schema, parsed, "", schema, 0) === null;
16399
+ }
16176
16400
  if (check.type === "json") {
16177
- var parsed;
16178
- try { parsed = JSON.parse(res.body); } catch (e) { return false; }
16179
16401
  var got = __atcJsonGet(parsed, check.path);
16180
16402
  if (check.op === "exists") return got.found;
16181
16403
  if (check.op === "not_exists") return !got.found;
16182
16404
  if (!got.found) return check.op === "not_equals";
16405
+ if (check.op === "type") return __atcIsType(got.value, String(check.value).trim().toLowerCase());
16406
+ if (check.op === "length") {
16407
+ var v = got.value;
16408
+ var n = typeof v === "string" || Array.isArray(v) ? v.length : v !== null && typeof v === "object" ? Object.keys(v).length : -1;
16409
+ return n >= 0 && __atcLengthOk(check.value, n);
16410
+ }
16411
+ if (check.op === "greater" || check.op === "less") {
16412
+ var num = Number(got.value);
16413
+ var lim = Number(check.value);
16414
+ if (got.value === null || got.value === "" || typeof got.value === "boolean" || !isFinite(num) || String(check.value).trim() === "" || !isFinite(lim)) return false;
16415
+ return check.op === "greater" ? num > lim : num < lim;
16416
+ }
16183
16417
  var text = __atcAsText(got.value);
16418
+ if (check.op === "matches") return __atcMatches(check.value, text);
16184
16419
  if (check.op === "equals") return text === check.value;
16185
16420
  if (check.op === "not_equals") return text !== check.value;
16186
16421
  return text.indexOf(check.value) >= 0;
@@ -16604,31 +16839,32 @@ var init_definition = __esm({
16604
16839
  });
16605
16840
 
16606
16841
  // ../shared/src/chain.ts
16607
- function fillText(text, vars, mode = "raw") {
16842
+ function fillText(text, vars, mode = "raw", builtins = true) {
16608
16843
  fillFn ??= new Function(`${FILL_JS}
16609
16844
  return __atcFill;`)();
16610
- return fillFn(text, vars, mode);
16845
+ return fillFn(text, vars, mode, builtins);
16611
16846
  }
16612
- function fillDeep(value, vars) {
16613
- if (typeof value === "string") return fillText(value, vars, "raw");
16614
- if (Array.isArray(value)) return value.map((v) => fillDeep(v, vars));
16847
+ function fillDeep(value, vars, builtins) {
16848
+ if (typeof value === "string") return fillText(value, vars, "raw", builtins);
16849
+ if (Array.isArray(value)) return value.map((v) => fillDeep(v, vars, builtins));
16615
16850
  if (value && typeof value === "object") {
16616
- return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, fillDeep(v, vars)]));
16851
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, fillDeep(v, vars, builtins)]));
16617
16852
  }
16618
16853
  return value;
16619
16854
  }
16620
- function fillLab(lab, vars) {
16621
- if (!Object.keys(vars).length) return lab;
16622
- const raw = (s) => fillText(s, vars, "raw");
16855
+ function fillLab(lab, vars, { builtins = true } = {}) {
16856
+ if (!Object.keys(vars).length && !builtins) return lab;
16857
+ const raw = (s) => fillText(s, vars, "raw", builtins);
16858
+ const deep = (v) => fillDeep(v, vars, builtins);
16623
16859
  const { request: request3, auth } = lab;
16624
16860
  const body = request3.body;
16625
- const filledBody = body.mode === "json" ? { ...body, json: fillDeep(body.json, vars) } : body.mode === "text" ? { ...body, text: raw(body.text) } : body.mode === "form" ? { ...body, fields: body.fields.map((f) => ({ key: raw(f.key), value: raw(f.value) })) } : body.mode === "raw" ? { ...body, raw: raw(body.raw) } : body;
16861
+ const filledBody = body.mode === "json" ? { ...body, json: deep(body.json) } : body.mode === "text" ? { ...body, text: raw(body.text) } : body.mode === "form" ? { ...body, fields: body.fields.map((f) => ({ key: raw(f.key), value: raw(f.value) })) } : body.mode === "raw" ? { ...body, raw: raw(body.raw) } : body;
16626
16862
  const filledAuth = auth.mode === "bearer" ? { ...auth, token: raw(auth.token) } : auth.mode === "basic" ? { ...auth, username: raw(auth.username), password: raw(auth.password) } : auth.mode === "api_key" ? { ...auth, name: raw(auth.name), value: raw(auth.value) } : auth;
16627
16863
  return {
16628
16864
  ...lab,
16629
16865
  request: {
16630
16866
  ...request3,
16631
- url: fillText(request3.url, vars, "url"),
16867
+ url: fillText(request3.url, vars, "url", builtins),
16632
16868
  query: request3.query.map((q) => ({ key: raw(q.key), value: raw(q.value) })),
16633
16869
  headers: request3.headers.map((h) => ({ key: h.key, value: raw(h.value) })),
16634
16870
  body: filledBody
@@ -16680,12 +16916,58 @@ var init_chain = __esm({
16680
16916
  init_checks3();
16681
16917
  init_definition();
16682
16918
  FILL_JS = String.raw`
16683
- function __atcFill(text, vars, mode) {
16919
+ var __ATC_FIRST = ["Alex", "Maria", "Sam", "Priya", "Omar", "Lena", "Kofi", "Yuki", "Diego", "Emma", "Noah", "Aisha", "Ivan", "Sofia", "Liam", "Mei"];
16920
+ var __ATC_LAST = ["Smith", "Garcia", "Kim", "Patel", "Muller", "Silva", "Nguyen", "Okafor", "Rossi", "Cohen", "Tanaka", "Novak", "Brown", "Khan", "Lopez", "Berg"];
16921
+ function __atcRand(n) { return Math.floor(Math.random() * n); }
16922
+ function __atcRandText(len, chars) {
16923
+ var out = "";
16924
+ for (var i = 0; i < len; i++) out += chars.charAt(__atcRand(chars.length));
16925
+ return out;
16926
+ }
16927
+ function __atcBuiltin(name, arg) {
16928
+ var args = arg === undefined || String(arg).trim() === "" ? [] : String(arg).split(",");
16929
+ for (var i = 0; i < args.length; i++) args[i] = Number(String(args[i]).trim());
16930
+ switch (name.slice(1).toLowerCase()) {
16931
+ case "uuid": case "guid": case "randomuuid":
16932
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { var r = __atcRand(16); return (c === "x" ? r : (r & 3) | 8).toString(16); });
16933
+ case "timestamp": return String(Math.floor(Date.now() / 1000));
16934
+ case "timestampms": return String(Date.now());
16935
+ case "isotimestamp": return new Date().toISOString();
16936
+ case "isodate": return new Date().toISOString().slice(0, 10);
16937
+ case "randomint": {
16938
+ var lo = args.length > 1 ? args[0] : 0;
16939
+ var hi = args.length > 1 ? args[1] : args.length ? args[0] : 1000;
16940
+ if (!isFinite(lo) || !isFinite(hi)) return undefined;
16941
+ var min = Math.ceil(Math.min(lo, hi)), max = Math.floor(Math.max(lo, hi));
16942
+ return String(min + __atcRand(max - min + 1));
16943
+ }
16944
+ case "randomstring": {
16945
+ var len = args.length && isFinite(args[0]) ? Math.max(1, Math.min(1000, Math.floor(args[0]))) : 8;
16946
+ return __atcRandText(len, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
16947
+ }
16948
+ case "randomemail": return "user." + __atcRandText(8, "abcdefghijklmnopqrstuvwxyz0123456789") + "@example.com";
16949
+ case "randomfirstname": return __ATC_FIRST[__atcRand(__ATC_FIRST.length)];
16950
+ case "randomlastname": return __ATC_LAST[__atcRand(__ATC_LAST.length)];
16951
+ case "randomfullname": return __ATC_FIRST[__atcRand(__ATC_FIRST.length)] + " " + __ATC_LAST[__atcRand(__ATC_LAST.length)];
16952
+ case "randomboolean": return Math.random() < 0.5 ? "true" : "false";
16953
+ }
16954
+ return undefined;
16955
+ }
16956
+ function __atcFill(text, vars, mode, builtins) {
16684
16957
  if (typeof text !== "string") return text;
16685
- return text.replace(/\{\{\s*([A-Za-z_][\w.-]*)\s*\}\}|%7B%7B([A-Za-z_][\w.-]*)%7D%7D/gi, function (whole, a, b) {
16958
+ return text.replace(/\{\{\s*(\$?[A-Za-z_][\w.-]*)(?:\(([^(){}]*)\))?\s*\}\}|%7B%7B(\$?[A-Za-z_][\w.-]*)(?:\(([^(){}]*)\))?%7D%7D/gi, function (whole, a, aArg, b, bArg) {
16686
16959
  var name = a || b;
16687
- if (!Object.prototype.hasOwnProperty.call(vars, name)) return whole;
16688
- var v = String(vars[name]);
16960
+ var arg = a ? aArg : bArg;
16961
+ var v;
16962
+ if (name.charAt(0) === "$") {
16963
+ if (builtins === false) return whole;
16964
+ if (b && arg !== undefined) { try { arg = decodeURIComponent(arg); } catch (e) {} }
16965
+ v = __atcBuiltin(name, arg);
16966
+ if (v === undefined) return whole;
16967
+ } else {
16968
+ if (arg !== undefined || !Object.prototype.hasOwnProperty.call(vars, name)) return whole;
16969
+ v = String(vars[name]);
16970
+ }
16689
16971
  if (mode === "url") return encodeURIComponent(v);
16690
16972
  if (mode === "json") return JSON.stringify(v).slice(1, -1);
16691
16973
  return v;
@@ -16697,6 +16979,32 @@ function __atcFill(text, vars, mode) {
16697
16979
  }
16698
16980
  });
16699
16981
 
16982
+ // ../shared/src/codegen.ts
16983
+ function parseSetCookie(header) {
16984
+ const [pair, ...attrs] = header.split(";");
16985
+ const eq = pair?.indexOf("=") ?? -1;
16986
+ if (!pair || eq <= 0) return null;
16987
+ const cookie = { name: pair.slice(0, eq).trim(), value: pair.slice(eq + 1).trim(), httpOnly: false, secure: false };
16988
+ for (const attr of attrs) {
16989
+ const i = attr.indexOf("=");
16990
+ const key = (i < 0 ? attr : attr.slice(0, i)).trim().toLowerCase();
16991
+ const value = i < 0 ? "" : attr.slice(i + 1).trim();
16992
+ if (key === "domain") cookie.domain = value;
16993
+ else if (key === "path") cookie.path = value;
16994
+ else if (key === "expires") cookie.expires = value;
16995
+ else if (key === "max-age" && /^-?\d+$/.test(value)) cookie.maxAge = Number(value);
16996
+ else if (key === "httponly") cookie.httpOnly = true;
16997
+ else if (key === "secure") cookie.secure = true;
16998
+ else if (key === "samesite") cookie.sameSite = value;
16999
+ }
17000
+ return cookie;
17001
+ }
17002
+ var init_codegen = __esm({
17003
+ "../shared/src/codegen.ts"() {
17004
+ "use strict";
17005
+ }
17006
+ });
17007
+
16700
17008
  // ../../node_modules/.pnpm/yaml@2.9.1/node_modules/yaml/dist/nodes/identity.js
16701
17009
  var require_identity = __commonJS({
16702
17010
  "../../node_modules/.pnpm/yaml@2.9.1/node_modules/yaml/dist/nodes/identity.js"(exports) {
@@ -24120,7 +24428,9 @@ function defaultRequestDraft() {
24120
24428
  maxRedirects: 5,
24121
24429
  connectionReuse: true,
24122
24430
  useCookies: true,
24123
- tlsSkip: false,
24431
+ // On for new requests, so test servers with their own certificates just work. Saved requests
24432
+ // keep what they have; one saved without the setting checks certificates (as before).
24433
+ tlsSkip: true,
24124
24434
  checks: [],
24125
24435
  before: null,
24126
24436
  testData: null,
@@ -24307,6 +24617,8 @@ function requestParts(draft) {
24307
24617
  } catch (err) {
24308
24618
  const bare = draftRuntimeNames(draft).find((n) => new RegExp(`(^|[^"])\\{\\{\\s*${n.replace(/[.-]/g, "\\$&")}\\s*\\}\\}(?!")`).test(draft.bodyText));
24309
24619
  if (bare) throw new Error(`Put quotes around {{${bare}}} in the JSON body \u2014 values from test data and run-first steps are filled in as text`);
24620
+ const builtin = /(^|[^"])\{\{\s*(\$[A-Za-z]+)/.exec(draft.bodyText)?.[2];
24621
+ if (builtin) throw new Error(`Put quotes around {{${builtin}}} in the JSON body \u2014 built-in values are filled in as text`);
24310
24622
  throw new Error(`Body is not valid JSON: ${err instanceof Error ? err.message : "parse error"}`);
24311
24623
  }
24312
24624
  } else if (hasBody(draft.method) && draft.bodyMode === "graphql") {
@@ -24348,12 +24660,21 @@ function requestParts(draft) {
24348
24660
  }
24349
24661
  };
24350
24662
  }
24663
+ function isJsonObject(text) {
24664
+ try {
24665
+ const v = JSON.parse(text);
24666
+ return Boolean(v) && typeof v === "object" && !Array.isArray(v);
24667
+ } catch {
24668
+ return false;
24669
+ }
24670
+ }
24351
24671
  function usableChecks(checks) {
24352
24672
  return checks.filter((c) => {
24353
24673
  if (c.type === "status") return c.expect.trim().length > 0;
24354
- if (c.type === "body") return c.value.length > 0;
24674
+ if (c.type === "body") return c.op === "is_json" || c.value.length > 0;
24355
24675
  if (c.type === "json") return c.path.trim().length > 0;
24356
24676
  if (c.type === "header") return c.name.trim().length > 0;
24677
+ if (c.type === "schema") return isJsonObject(c.schema);
24357
24678
  return c.maxMs > 0;
24358
24679
  });
24359
24680
  }
@@ -24630,10 +24951,10 @@ function sustainedFrom(buckets) {
24630
24951
  function percentileFromHist(hist, p) {
24631
24952
  const total = hist.reduce((a, n) => a + n, 0);
24632
24953
  if (!total) return null;
24633
- let seen = 0;
24954
+ let seen2 = 0;
24634
24955
  for (let i = 0; i < hist.length; i++) {
24635
- seen += hist[i];
24636
- if (seen / total >= p) {
24956
+ seen2 += hist[i];
24957
+ if (seen2 / total >= p) {
24637
24958
  const edge = LATENCY_BIN_EDGES_MS[i];
24638
24959
  return Number.isFinite(edge) ? edge : LATENCY_BIN_EDGES_MS[LATENCY_BIN_EDGES_MS.length - 2];
24639
24960
  }
@@ -25163,7 +25484,7 @@ function stepOutcome(r) {
25163
25484
  if (!failed.length) return { state: "passed" };
25164
25485
  return {
25165
25486
  state: "failed",
25166
- message: `${failed.length} of ${r.checks.length} check${r.checks.length === 1 ? "" : "s"} failed: ${failed.map((c) => c.name).join("; ")}`
25487
+ message: `${failed.length} of ${r.checks.length} check${r.checks.length === 1 ? "" : "s"} failed: ${failed.map((c) => c.detail ? `${c.name} (${c.detail})` : c.name).join("; ")}`
25167
25488
  };
25168
25489
  }
25169
25490
  if (r.status >= 200 && r.status < 400) return { state: "passed" };
@@ -25186,16 +25507,16 @@ function suitePassed(run2) {
25186
25507
  }
25187
25508
  function orderSuite(items, plan) {
25188
25509
  const byId = new Map(items.map((i) => [i.id, i]));
25189
- const seen = /* @__PURE__ */ new Set();
25510
+ const seen2 = /* @__PURE__ */ new Set();
25190
25511
  const out = [];
25191
25512
  for (const id of plan?.order ?? []) {
25192
25513
  const item = byId.get(id);
25193
- if (item && !seen.has(id)) {
25514
+ if (item && !seen2.has(id)) {
25194
25515
  out.push(item);
25195
- seen.add(id);
25516
+ seen2.add(id);
25196
25517
  }
25197
25518
  }
25198
- for (const item of items) if (!seen.has(item.id)) out.push(item);
25519
+ for (const item of items) if (!seen2.has(item.id)) out.push(item);
25199
25520
  const skip = new Set(plan?.skip ?? []);
25200
25521
  return out.map((item) => ({ item, skipped: skip.has(item.id) }));
25201
25522
  }
@@ -26660,6 +26981,7 @@ var init_src = __esm({
26660
26981
  init_dashboard();
26661
26982
  init_workspace();
26662
26983
  init_chain();
26984
+ init_codegen();
26663
26985
  init_import();
26664
26986
  init_email_report();
26665
26987
  init_junit();
@@ -26772,7 +27094,7 @@ token, password, or key.
26772
27094
  async scan() {
26773
27095
  await this.ensure();
26774
27096
  let changed = false;
26775
- const seen = /* @__PURE__ */ new Set();
27097
+ const seen2 = /* @__PURE__ */ new Set();
26776
27098
  for (const folder of ["runs", "compares"]) {
26777
27099
  let names = [];
26778
27100
  try {
@@ -26782,7 +27104,7 @@ token, password, or key.
26782
27104
  }
26783
27105
  for (const name of names) {
26784
27106
  const key = `${folder}/${name}`;
26785
- seen.add(key);
27107
+ seen2.add(key);
26786
27108
  let info;
26787
27109
  try {
26788
27110
  info = await stat(join(this.folder(folder), name));
@@ -26796,7 +27118,7 @@ token, password, or key.
26796
27118
  }
26797
27119
  }
26798
27120
  for (const key of [...this.entries.keys()]) {
26799
- if (!seen.has(key)) {
27121
+ if (!seen2.has(key)) {
26800
27122
  this.entries.delete(key);
26801
27123
  changed = true;
26802
27124
  }
@@ -27183,6 +27505,11 @@ function generateK6Script(run2, extra2 = {}) {
27183
27505
  ATC_NAME: run2.name,
27184
27506
  ATC_CHECKS: JSON.stringify(run2.request.checks.map((check2) => ({ label: checkLabel(check2), check: check2 })))
27185
27507
  };
27508
+ if (run2.request.checks.some((c) => c.type === "schema")) {
27509
+ files["checks.json"] = env.ATC_CHECKS;
27510
+ env.ATC_CHECKS = "";
27511
+ env.ATC_CHECKS_FILE = "1";
27512
+ }
27186
27513
  if (extra2.tlsAuth?.length) env.ATC_TLS_AUTH = JSON.stringify(extra2.tlsAuth);
27187
27514
  if (extra2.jwt) {
27188
27515
  const { tokens, ...rest } = extra2.jwt;
@@ -27213,15 +27540,14 @@ function generateK6Script(run2, extra2 = {}) {
27213
27540
  env.ATC_AUTH_USER = run2.auth.mode === "ntlm" && run2.auth.domain ? `${run2.auth.domain}\\${run2.auth.username}` : run2.auth.username;
27214
27541
  env.ATC_AUTH_PASS = run2.auth.password;
27215
27542
  }
27543
+ const bodyMode = run2.request.body.mode;
27544
+ env.ATC_BODY_FILL = bodyMode === "json" ? "json" : bodyMode === "form" ? "url" : "raw";
27216
27545
  if (data) {
27217
27546
  files["data.json"] = JSON.stringify(data.rows);
27218
27547
  env.ATC_DATA_COLUMNS = JSON.stringify(data.columns);
27219
27548
  env.ATC_DATA_ORDER = data.order;
27220
- const mode = run2.request.body.mode;
27221
- env.ATC_BODY_FILL = mode === "json" ? "json" : mode === "form" ? "url" : "raw";
27222
27549
  }
27223
27550
  const dataBlock = data ? `
27224
- ${FILL_JS}
27225
27551
  const ROWS = new SharedArray("rows", function () { return JSON.parse(open("./data.json")); });
27226
27552
  const COLUMNS = JSON.parse(__ENV.ATC_DATA_COLUMNS);
27227
27553
  function __atcRow() {
@@ -27245,12 +27571,12 @@ import { SharedArray } from "k6/data";
27245
27571
  export const options = Object.assign(${optionsBlock(run2)}, __ENV.ATC_TLS_AUTH ? { tlsAuth: JSON.parse(__ENV.ATC_TLS_AUTH) } : {});
27246
27572
 
27247
27573
  ${CHECK_EVALUATOR_JS}
27248
- ${dataBlock}${run2.auth.mode === "aws_sigv4" ? `
27574
+ ${FILL_JS}${dataBlock}${run2.auth.mode === "aws_sigv4" ? `
27249
27575
  const AWS = JSON.parse(__ENV.ATC_AWS);
27250
27576
  ${SIGV4_JS}` : ""}${run2.auth.mode === "oauth1" ? `
27251
27577
  const OAUTH1 = JSON.parse(__ENV.ATC_OAUTH1);
27252
27578
  ${OAUTH1_JS}` : ""}
27253
- const CHECKS = JSON.parse(__ENV.ATC_CHECKS || "[]");
27579
+ const CHECKS = __ENV.ATC_CHECKS_FILE ? JSON.parse(open("./checks.json")) : JSON.parse(__ENV.ATC_CHECKS || "[]");
27254
27580
  // JWT: tokens signed by the app for the whole run; each is used for its stretch of time.
27255
27581
  const JWT = __ENV.ATC_JWT ? JSON.parse(__ENV.ATC_JWT) : null;
27256
27582
  const JWTS = JWT ? JSON.parse(open("./jwt.json")) : [];
@@ -27278,7 +27604,9 @@ function __atcFreshToken() {
27278
27604
  }
27279
27605
 
27280
27606
  export default function () {
27281
- ${data ? 'const vars = __atcRow();\n const f = function (text, mode) { return __atcFill(text, vars, mode || "raw"); };' : "const f = function (text) { return text; };"}
27607
+ // Test-data values, and built-in values ({{$uuid}}, \u2026) made anew for every request.
27608
+ const vars = ${data ? "__atcRow()" : "{}"};
27609
+ const f = function (text, mode) { return __atcFill(text, vars, mode || "raw"); };
27282
27610
  const headers = JSON.parse(__ENV.ATC_HEADERS || "{}");
27283
27611
  for (const key in headers) headers[key] = f(headers[key]);
27284
27612
  if (__ENV.ATC_COOKIE) headers.Cookie = __ENV.ATC_COOKIE;
@@ -45374,13 +45702,13 @@ function toPem(m) {
45374
45702
  }
45375
45703
  async function k6TlsAuth(urls) {
45376
45704
  const out = [];
45377
- const seen = /* @__PURE__ */ new Set();
45705
+ const seen2 = /* @__PURE__ */ new Set();
45378
45706
  for (const target of urls) {
45379
45707
  const m = await certificateFor(target);
45380
45708
  if (!m) continue;
45381
45709
  const domain2 = new URL(target).hostname;
45382
- if (seen.has(domain2)) continue;
45383
- seen.add(domain2);
45710
+ if (seen2.has(domain2)) continue;
45711
+ seen2.add(domain2);
45384
45712
  const pem = toPem(m);
45385
45713
  out.push({ domains: [domain2], cert: pem.cert, key: pem.key, ...pem.password ? { password: pem.password } : {} });
45386
45714
  }
@@ -45485,7 +45813,7 @@ function defaultPath(url2) {
45485
45813
  if (!p.startsWith("/") || p.lastIndexOf("/") === 0) return "/";
45486
45814
  return p.slice(0, p.lastIndexOf("/"));
45487
45815
  }
45488
- function parseSetCookie(header, url2, now = Date.now()) {
45816
+ function parseSetCookie2(header, url2, now = Date.now()) {
45489
45817
  const [pair, ...attrs] = header.split(";");
45490
45818
  const eq = pair.indexOf("=");
45491
45819
  if (eq <= 0) return null;
@@ -45536,7 +45864,7 @@ async function storeCookies(target, setCookie) {
45536
45864
  const now = Date.now();
45537
45865
  let next = [...await load()];
45538
45866
  for (const header of Array.isArray(setCookie) ? setCookie : [setCookie]) {
45539
- const c = parseSetCookie(header, url2, now);
45867
+ const c = parseSetCookie2(header, url2, now);
45540
45868
  if (!c) continue;
45541
45869
  next = next.filter((o) => !(o.name === c.name && o.domain === c.domain && o.path === c.path));
45542
45870
  if (c.expires === null || c.expires > now) next.push(c);
@@ -69245,7 +69573,7 @@ var require_parse = __commonJS({
69245
69573
  var { maxNameValuePairSize, maxAttributeValueSize } = require_constants4();
69246
69574
  var { isCTLExcludingHtab } = require_util5();
69247
69575
  var assert2 = __require("node:assert");
69248
- function parseSetCookie2(header) {
69576
+ function parseSetCookie3(header) {
69249
69577
  if (isCTLExcludingHtab(header)) {
69250
69578
  return null;
69251
69579
  }
@@ -69369,7 +69697,7 @@ var require_parse = __commonJS({
69369
69697
  return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList);
69370
69698
  }
69371
69699
  module.exports = {
69372
- parseSetCookie: parseSetCookie2,
69700
+ parseSetCookie: parseSetCookie3,
69373
69701
  parseUnparsedAttributes
69374
69702
  };
69375
69703
  }
@@ -69379,7 +69707,7 @@ var require_parse = __commonJS({
69379
69707
  var require_cookies = __commonJS({
69380
69708
  "../../node_modules/.pnpm/undici@8.10.0/node_modules/undici/lib/web/cookies/index.js"(exports, module) {
69381
69709
  "use strict";
69382
- var { parseSetCookie: parseSetCookie2 } = require_parse();
69710
+ var { parseSetCookie: parseSetCookie3 } = require_parse();
69383
69711
  var { stringify } = require_util5();
69384
69712
  var { webidl } = require_webidl();
69385
69713
  var { Headers } = require_headers();
@@ -69418,11 +69746,11 @@ var require_cookies = __commonJS({
69418
69746
  if (!cookies) {
69419
69747
  return [];
69420
69748
  }
69421
- return cookies.map((pair) => parseSetCookie2(pair));
69749
+ return cookies.map((pair) => parseSetCookie3(pair));
69422
69750
  }
69423
69751
  function parseCookie(cookie) {
69424
69752
  cookie = webidl.converters.DOMString(cookie);
69425
- return parseSetCookie2(cookie);
69753
+ return parseSetCookie3(cookie);
69426
69754
  }
69427
69755
  function setCookie(headers, cookie) {
69428
69756
  webidl.argumentLengthCheck(arguments, 2, "setCookie");
@@ -73150,7 +73478,7 @@ function parseLab(input) {
73150
73478
  return labRequestSchema.parse(input);
73151
73479
  }
73152
73480
  async function runSetupStep(setup) {
73153
- const sent = await sendOnce(setup);
73481
+ const sent = await sendOnce(fillLab(setup, {}));
73154
73482
  const vars = {};
73155
73483
  const values = [];
73156
73484
  let problem = sent.result.error ? `could not reach it: ${sent.result.error}` : void 0;
@@ -73219,6 +73547,12 @@ function takeValues(rules, sent) {
73219
73547
  });
73220
73548
  }
73221
73549
  async function sendOnce(lab) {
73550
+ const out = await sendOnceAuthed(lab);
73551
+ const note = AUTH_NOTES[lab.auth.mode];
73552
+ if (note && out.result.sent) out.result.sent.authNote = note;
73553
+ return out;
73554
+ }
73555
+ async function sendOnceAuthed(lab) {
73222
73556
  if (lab.auth.mode === "digest" || lab.auth.mode === "ntlm") return sendChallenged(lab, lab.auth);
73223
73557
  if (lab.auth.mode === "jwt") {
73224
73558
  const { mode: _mode, ...config3 } = lab.auth;
@@ -73296,6 +73630,7 @@ async function sendRaw(lab, extra2 = {}) {
73296
73630
  headers[pair.key] = pair.value;
73297
73631
  }
73298
73632
  const secretNames = applyAuth(headers, url2, lab.auth);
73633
+ const secretQuery = lab.auth.mode === "api_key" && lab.auth.in === "query" ? [lab.auth.name] : [];
73299
73634
  if (extra2.authorization) {
73300
73635
  headers.authorization = extra2.authorization;
73301
73636
  secretNames.push("authorization");
@@ -73312,7 +73647,10 @@ async function sendRaw(lab, extra2 = {}) {
73312
73647
  if (signed.authorization) {
73313
73648
  headers.authorization = signed.authorization;
73314
73649
  secretNames.push("authorization");
73315
- } else target = new URL(signed.url);
73650
+ } else {
73651
+ target = new URL(signed.url);
73652
+ secretQuery.push("oauth_signature", "oauth_token");
73653
+ }
73316
73654
  }
73317
73655
  if (lab.auth.mode === "aws_sigv4") {
73318
73656
  const { mode: _mode, ...creds } = lab.auth;
@@ -73326,6 +73664,8 @@ async function sendRaw(lab, extra2 = {}) {
73326
73664
  const agent = extra2.dispatcher ?? new import_undici2.Agent({ connect: await tlsConnect(url2, options.tlsSkipVerify) });
73327
73665
  const dispatcher = extra2.dispatcher ?? (useJar ? agent : agent.compose(import_undici2.interceptors.redirect({ maxRedirections: options.maxRedirects })));
73328
73666
  const started = performance.now();
73667
+ let sent;
73668
+ const cookies = [];
73329
73669
  try {
73330
73670
  let current = target;
73331
73671
  let method = lab.request.method;
@@ -73337,9 +73677,16 @@ async function sendRaw(lab, extra2 = {}) {
73337
73677
  const jar2 = await cookieHeader(current);
73338
73678
  if (jar2) sendHeaders.cookie = jar2;
73339
73679
  }
73680
+ if (!sent) sent = describeSent(method, current, sendHeaders, body, [...secretNames, ...ALWAYS_SECRET], secretQuery, options.tlsSkipVerify);
73681
+ else (sent.redirects ??= []).push(`${method} ${current.toString()}`);
73340
73682
  response = await (0, import_undici2.request)(current, { method, headers: sendHeaders, body, headersTimeout: timeout, bodyTimeout: timeout, dispatcher });
73683
+ const setCookie = response.headers["set-cookie"];
73684
+ for (const line of typeof setCookie === "string" ? [setCookie] : setCookie ?? []) {
73685
+ const parsed = parseSetCookie(line);
73686
+ if (parsed) cookies.push(parsed);
73687
+ }
73341
73688
  if (!useJar) break;
73342
- await storeCookies(current, response.headers["set-cookie"]);
73689
+ await storeCookies(current, setCookie);
73343
73690
  const location = response.headers.location;
73344
73691
  if (![301, 302, 303, 307, 308].includes(response.statusCode) || typeof location !== "string" || hops >= options.maxRedirects) break;
73345
73692
  await response.body.dump();
@@ -73354,8 +73701,11 @@ async function sendRaw(lab, extra2 = {}) {
73354
73701
  }
73355
73702
  current = next;
73356
73703
  }
73357
- const raw = await response.body.text();
73704
+ const bytes2 = Buffer.from(await response.body.arrayBuffer());
73705
+ const raw = bytes2.toString("utf8");
73358
73706
  const durationMs = Math.round((performance.now() - started) * 10) / 10;
73707
+ const responseType = String(response.headers["content-type"] ?? "").toLowerCase();
73708
+ const bodyBase64 = /^image\/(png|jpe?g|gif|webp|bmp|svg\+xml|x-icon|vnd\.microsoft\.icon|avif)/.test(responseType) && bytes2.length <= IMAGE_LIMIT ? bytes2.toString("base64") : void 0;
73359
73709
  const truncated = raw.length > BODY_LIMIT;
73360
73710
  const headerBag = {};
73361
73711
  for (const [key, value] of Object.entries(response.headers)) {
@@ -73376,7 +73726,10 @@ async function sendRaw(lab, extra2 = {}) {
73376
73726
  truncated,
73377
73727
  durationMs,
73378
73728
  url: url2.toString(),
73379
- checks
73729
+ checks,
73730
+ sent,
73731
+ ...cookies.length ? { cookies } : {},
73732
+ ...bodyBase64 ? { bodyBase64 } : {}
73380
73733
  }
73381
73734
  };
73382
73735
  } catch (err) {
@@ -73393,14 +73746,29 @@ async function sendRaw(lab, extra2 = {}) {
73393
73746
  truncated: false,
73394
73747
  durationMs,
73395
73748
  url: url2.toString(),
73396
- error: err instanceof Error ? err.message : String(err)
73749
+ error: err instanceof Error ? err.message : String(err),
73750
+ sent
73397
73751
  }
73398
73752
  };
73399
73753
  } finally {
73400
73754
  if (!extra2.dispatcher) await dispatcher.close();
73401
73755
  }
73402
73756
  }
73403
- var import_undici2, BODY_LIMIT;
73757
+ function describeSent(method, url2, headers, body, secretHeaders, secretQuery, insecure) {
73758
+ const list = [["host", url2.host], ...Object.entries(headers)];
73759
+ if (body !== void 0) list.push(["content-length", String(Buffer.byteLength(body))]);
73760
+ return {
73761
+ method,
73762
+ url: url2.toString(),
73763
+ headers: list,
73764
+ ...body !== void 0 ? { body: body.length > BODY_LIMIT ? body.slice(0, BODY_LIMIT) : body, bodyTruncated: body.length > BODY_LIMIT || void 0 } : {},
73765
+ secretHeaders: [...new Set(secretHeaders.map((h) => h.toLowerCase()))],
73766
+ secretQuery,
73767
+ // Only https has a certificate to skip.
73768
+ insecure: insecure && url2.protocol === "https:" || void 0
73769
+ };
73770
+ }
73771
+ var import_undici2, BODY_LIMIT, IMAGE_LIMIT, ALWAYS_SECRET, AUTH_NOTES;
73404
73772
  var init_probe = __esm({
73405
73773
  "../../apps/engine/src/probe.ts"() {
73406
73774
  "use strict";
@@ -73414,6 +73782,16 @@ var init_probe = __esm({
73414
73782
  init_cookies();
73415
73783
  init_oauth();
73416
73784
  BODY_LIMIT = 64 * 1024;
73785
+ IMAGE_LIMIT = 5 * 1024 * 1024;
73786
+ ALWAYS_SECRET = ["authorization", "proxy-authorization", "cookie", "x-api-key", "api-key", "x-amz-security-token"];
73787
+ AUTH_NOTES = {
73788
+ oauth2: "OAuth 2.0 \u2014 the app got the token and sent it as a Bearer token",
73789
+ oauth1: "OAuth 1.0 \u2014 signed for this one request (nonce and timestamp)",
73790
+ aws_sigv4: "AWS Signature \u2014 signed for this one request",
73791
+ jwt: "JWT \u2014 signed by the app for this request",
73792
+ digest: "Digest \u2014 answers the server's 401 challenge; this is the second request",
73793
+ ntlm: "NTLM \u2014 signs in over one connection; this is the last of three requests"
73794
+ };
73417
73795
  }
73418
73796
  });
73419
73797
 
@@ -73454,10 +73832,10 @@ var init_latency_histogram = __esm({
73454
73832
  percentile(p) {
73455
73833
  if (this.count === 0) return 0;
73456
73834
  const rank = Math.ceil(p / 100 * this.count);
73457
- let seen = 0;
73835
+ let seen2 = 0;
73458
73836
  for (const bin of [...this.bins.keys()].sort((a, b) => a - b)) {
73459
- seen += this.bins.get(bin);
73460
- if (seen >= rank) {
73837
+ seen2 += this.bins.get(bin);
73838
+ if (seen2 >= rank) {
73461
73839
  const mid = FLOOR_MS * Math.exp((bin + 0.5) * GROWTH);
73462
73840
  return Math.min(this.max, Math.max(this.min, mid));
73463
73841
  }
@@ -73968,7 +74346,7 @@ async function startRun(lab, repoRoot2) {
73968
74346
  err.statusCode = 422;
73969
74347
  throw err;
73970
74348
  }
73971
- filled = fillLab(lab, step.vars);
74349
+ filled = fillLab(lab, step.vars, { builtins: false });
73972
74350
  }
73973
74351
  const id = crypto.randomUUID();
73974
74352
  const dir2 = await mkdtemp(join5(tmpdir2(), "atc-run-"));
@@ -74318,7 +74696,7 @@ function explainMissing(message, index, savers, steps) {
74318
74696
  });
74319
74697
  return hints.length ? hints.join("; ") : message;
74320
74698
  }
74321
- async function runStep(step, workspace, vars, envName, setups, explain = (m) => m) {
74699
+ async function runStep(step, workspace, vars, envName, setups, explain2 = (m) => m) {
74322
74700
  const saved = await workspace.getRequest(step.id);
74323
74701
  if (!saved) throw new StepProblem("This request is no longer saved");
74324
74702
  if (saved.secretsMissing?.length) {
@@ -74328,7 +74706,7 @@ async function runStep(step, workspace, vars, envName, setups, explain = (m) =>
74328
74706
  try {
74329
74707
  draft = await resolveSaved(workspace, saved, vars, envName);
74330
74708
  } catch (err) {
74331
- throw new StepProblem(explain(err instanceof Error ? err.message.replace(/ — pick one next to the URL$/, " \u2014 pick an environment for this run") : String(err)));
74709
+ throw new StepProblem(explain2(err instanceof Error ? err.message.replace(/ — pick one next to the URL$/, " \u2014 pick an environment for this run") : String(err)));
74332
74710
  }
74333
74711
  const payload = buildPayload(draft, toLoadConfig(DEFAULT_LOAD), saved.name);
74334
74712
  let runtime = {};
@@ -74354,23 +74732,23 @@ async function runStep(step, workspace, vars, envName, setups, explain = (m) =>
74354
74732
  runtime = done.vars;
74355
74733
  }
74356
74734
  if (payload.data) runtime = { ...runtime, ...firstRowVars(payload.data) };
74357
- const probe = await runProbe({ ...fillLab({ request: payload.request, auth: payload.auth, options: payload.options }, runtime), save: payload.save });
74358
- const outcome = stepOutcome(probe);
74735
+ const probe2 = await runProbe({ ...fillLab({ request: payload.request, auth: payload.auth, options: payload.options }, runtime), save: payload.save });
74736
+ const outcome = stepOutcome(probe2);
74359
74737
  step.state = outcome.state;
74360
74738
  step.message = outcome.message;
74361
- if (probe.saved?.length) {
74362
- step.saved = probe.saved.map((v) => ({ name: v.name, found: v.found, preview: v.preview }));
74363
- for (const v of probe.saved) if (v.found && v.value !== void 0) vars[v.name] = v.value;
74364
- const missing = probe.saved.filter((v) => !v.found);
74739
+ if (probe2.saved?.length) {
74740
+ step.saved = probe2.saved.map((v) => ({ name: v.name, found: v.found, preview: v.preview }));
74741
+ for (const v of probe2.saved) if (v.found && v.value !== void 0) vars[v.name] = v.value;
74742
+ const missing = probe2.saved.filter((v) => !v.found);
74365
74743
  if (missing.length && step.state === "passed") {
74366
74744
  step.state = "failed";
74367
74745
  step.message = missing.map((v) => `{{${v.name}}} was not found in the answer (looked for ${v.from})`).join("; ");
74368
74746
  }
74369
74747
  }
74370
- step.status = probe.status || void 0;
74371
- step.durationMs = probe.durationMs;
74372
- step.checks = probe.checks;
74373
- if (outcome.state !== "passed" && probe.body) step.bodyPreview = probe.body.slice(0, BODY_PREVIEW_CHARS);
74748
+ step.status = probe2.status || void 0;
74749
+ step.durationMs = probe2.durationMs;
74750
+ step.checks = probe2.checks;
74751
+ if (outcome.state !== "passed" && probe2.body) step.bodyPreview = probe2.body.slice(0, BODY_PREVIEW_CHARS);
74374
74752
  }
74375
74753
  function registerSuiteRoutes(api, workspace, plans, builtins) {
74376
74754
  api.get("/suites/plan", async (request3) => {
@@ -81457,32 +81835,32 @@ var require_err_helpers = __commonJS({
81457
81835
  return isErrorLike(cause) ? cause : void 0;
81458
81836
  }
81459
81837
  };
81460
- var _stackWithCauses = (err, seen) => {
81838
+ var _stackWithCauses = (err, seen2) => {
81461
81839
  if (!isErrorLike(err)) return "";
81462
81840
  const stack = err.stack || "";
81463
- if (seen.has(err)) {
81841
+ if (seen2.has(err)) {
81464
81842
  return stack + "\ncauses have become circular...";
81465
81843
  }
81466
81844
  const cause = getErrorCause(err);
81467
81845
  if (cause) {
81468
- seen.add(err);
81469
- return stack + "\ncaused by: " + _stackWithCauses(cause, seen);
81846
+ seen2.add(err);
81847
+ return stack + "\ncaused by: " + _stackWithCauses(cause, seen2);
81470
81848
  } else {
81471
81849
  return stack;
81472
81850
  }
81473
81851
  };
81474
81852
  var stackWithCauses = (err) => _stackWithCauses(err, /* @__PURE__ */ new Set());
81475
- var _messageWithCauses = (err, seen, skip) => {
81853
+ var _messageWithCauses = (err, seen2, skip) => {
81476
81854
  if (!isErrorLike(err)) return "";
81477
81855
  const message = skip ? "" : err.message || "";
81478
- if (seen.has(err)) {
81856
+ if (seen2.has(err)) {
81479
81857
  return message + ": ...";
81480
81858
  }
81481
81859
  const cause = getErrorCause(err);
81482
81860
  if (cause) {
81483
- seen.add(err);
81861
+ seen2.add(err);
81484
81862
  const skipIfVErrorStyleCause = typeof err.cause === "function";
81485
- return message + (skipIfVErrorStyleCause ? "" : ": ") + _messageWithCauses(cause, seen, skipIfVErrorStyleCause);
81863
+ return message + (skipIfVErrorStyleCause ? "" : ": ") + _messageWithCauses(cause, seen2, skipIfVErrorStyleCause);
81486
81864
  } else {
81487
81865
  return message;
81488
81866
  }
@@ -81501,7 +81879,7 @@ var require_err_helpers = __commonJS({
81501
81879
  var require_err_proto = __commonJS({
81502
81880
  "../../node_modules/.pnpm/pino-std-serializers@7.1.0/node_modules/pino-std-serializers/lib/err-proto.js"(exports, module) {
81503
81881
  "use strict";
81504
- var seen = /* @__PURE__ */ Symbol("circular-ref-tag");
81882
+ var seen2 = /* @__PURE__ */ Symbol("circular-ref-tag");
81505
81883
  var rawSymbol = /* @__PURE__ */ Symbol("pino-raw-err-ref");
81506
81884
  var pinoErrProto = Object.create({}, {
81507
81885
  type: {
@@ -81541,7 +81919,7 @@ var require_err_proto = __commonJS({
81541
81919
  module.exports = {
81542
81920
  pinoErrProto,
81543
81921
  pinoErrorSymbols: {
81544
- seen,
81922
+ seen: seen2,
81545
81923
  rawSymbol
81546
81924
  }
81547
81925
  };
@@ -81555,13 +81933,13 @@ var require_err = __commonJS({
81555
81933
  module.exports = errSerializer;
81556
81934
  var { messageWithCauses, stackWithCauses, isErrorLike } = require_err_helpers();
81557
81935
  var { pinoErrProto, pinoErrorSymbols } = require_err_proto();
81558
- var { seen } = pinoErrorSymbols;
81936
+ var { seen: seen2 } = pinoErrorSymbols;
81559
81937
  var { toString } = Object.prototype;
81560
81938
  function errSerializer(err) {
81561
81939
  if (!isErrorLike(err)) {
81562
81940
  return err;
81563
81941
  }
81564
- err[seen] = void 0;
81942
+ err[seen2] = void 0;
81565
81943
  const _err = Object.create(pinoErrProto);
81566
81944
  _err.type = toString.call(err.constructor) === "[object Function]" ? err.constructor.name : err.name;
81567
81945
  _err.message = messageWithCauses(err);
@@ -81573,7 +81951,7 @@ var require_err = __commonJS({
81573
81951
  if (_err[key] === void 0) {
81574
81952
  const val = err[key];
81575
81953
  if (isErrorLike(val)) {
81576
- if (key !== "cause" && !Object.prototype.hasOwnProperty.call(val, seen)) {
81954
+ if (key !== "cause" && !Object.prototype.hasOwnProperty.call(val, seen2)) {
81577
81955
  _err[key] = errSerializer(val);
81578
81956
  }
81579
81957
  } else {
@@ -81581,7 +81959,7 @@ var require_err = __commonJS({
81581
81959
  }
81582
81960
  }
81583
81961
  }
81584
- delete err[seen];
81962
+ delete err[seen2];
81585
81963
  _err.raw = err;
81586
81964
  return _err;
81587
81965
  }
@@ -81595,13 +81973,13 @@ var require_err_with_cause = __commonJS({
81595
81973
  module.exports = errWithCauseSerializer;
81596
81974
  var { isErrorLike } = require_err_helpers();
81597
81975
  var { pinoErrProto, pinoErrorSymbols } = require_err_proto();
81598
- var { seen } = pinoErrorSymbols;
81976
+ var { seen: seen2 } = pinoErrorSymbols;
81599
81977
  var { toString } = Object.prototype;
81600
81978
  function errWithCauseSerializer(err) {
81601
81979
  if (!isErrorLike(err)) {
81602
81980
  return err;
81603
81981
  }
81604
- err[seen] = void 0;
81982
+ err[seen2] = void 0;
81605
81983
  const _err = Object.create(pinoErrProto);
81606
81984
  _err.type = toString.call(err.constructor) === "[object Function]" ? err.constructor.name : err.name;
81607
81985
  _err.message = err.message;
@@ -81609,14 +81987,14 @@ var require_err_with_cause = __commonJS({
81609
81987
  if (Array.isArray(err.errors)) {
81610
81988
  _err.aggregateErrors = err.errors.map((err2) => errWithCauseSerializer(err2));
81611
81989
  }
81612
- if (isErrorLike(err.cause) && !Object.prototype.hasOwnProperty.call(err.cause, seen)) {
81990
+ if (isErrorLike(err.cause) && !Object.prototype.hasOwnProperty.call(err.cause, seen2)) {
81613
81991
  _err.cause = errWithCauseSerializer(err.cause);
81614
81992
  }
81615
81993
  for (const key in err) {
81616
81994
  if (_err[key] === void 0) {
81617
81995
  const val = err[key];
81618
81996
  if (isErrorLike(val)) {
81619
- if (!Object.prototype.hasOwnProperty.call(val, seen)) {
81997
+ if (!Object.prototype.hasOwnProperty.call(val, seen2)) {
81620
81998
  _err[key] = errWithCauseSerializer(val);
81621
81999
  }
81622
82000
  } else {
@@ -81624,7 +82002,7 @@ var require_err_with_cause = __commonJS({
81624
82002
  }
81625
82003
  }
81626
82004
  }
81627
- delete err[seen];
82005
+ delete err[seen2];
81628
82006
  _err.raw = err;
81629
82007
  return _err;
81630
82008
  }
@@ -108779,7 +109157,7 @@ var require_dist5 = __commonJS({
108779
109157
  exports.stringifyCookie = stringifyCookie;
108780
109158
  exports.stringifySetCookie = stringifySetCookie;
108781
109159
  exports.serialize = stringifySetCookie;
108782
- exports.parseSetCookie = parseSetCookie2;
109160
+ exports.parseSetCookie = parseSetCookie3;
108783
109161
  exports.stringifySetCookie = stringifySetCookie;
108784
109162
  exports.serialize = stringifySetCookie;
108785
109163
  var cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/;
@@ -108916,7 +109294,7 @@ var require_dist5 = __commonJS({
108916
109294
  }
108917
109295
  return str2;
108918
109296
  }
108919
- function parseSetCookie2(str2, options) {
109297
+ function parseSetCookie3(str2, options) {
108920
109298
  const dec = options?.decode || decode4;
108921
109299
  const len = str2.length;
108922
109300
  const endIdx = endIndex(str2, 0, len);
@@ -112029,7 +112407,7 @@ import http from "node:http";
112029
112407
  import https from "node:https";
112030
112408
  function run(cmd, args) {
112031
112409
  return new Promise((resolve5) => {
112032
- execFile2(cmd, args, { timeout: 4e3, windowsHide: true, maxBuffer: 8 * 1024 * 1024 }, (err, stdout) => resolve5(err && !stdout ? "" : String(stdout)));
112410
+ execFile2(cmd, args, { timeout: 2e4, windowsHide: true, maxBuffer: 8 * 1024 * 1024 }, (err, stdout) => resolve5(err && !stdout ? "" : String(stdout)));
112033
112411
  });
112034
112412
  }
112035
112413
  function local(address) {
@@ -112098,11 +112476,10 @@ function parseSs(text) {
112098
112476
  async function listPorts() {
112099
112477
  if (process.platform === "win32") {
112100
112478
  const tasks = run("tasklist", ["/FO", "CSV", "/NH"]);
112101
- const [v4, v6] = await Promise.all([run("netstat", ["-ano", "-p", "TCP"]), run("netstat", ["-ano", "-p", "TCPv6"])]);
112102
- const found2 = parseNetstat(v4);
112103
- for (const [port2, l] of parseNetstat(v6)) if (!found2.has(port2)) found2.set(port2, l);
112479
+ const found2 = parseNetstat(await run("netstat", ["-ano"]));
112104
112480
  const named = tasks.then((text) => {
112105
112481
  const names = parseTasklist(text);
112482
+ if (!names.size) return;
112106
112483
  for (const l of found2.values()) {
112107
112484
  const t = names.get(l.pid);
112108
112485
  l.name = t?.name;
@@ -112126,20 +112503,34 @@ function ask(scheme, host2, port2, verify, timeoutMs) {
112126
112503
  resolve5("yes");
112127
112504
  }
112128
112505
  );
112129
- req.on("timeout", () => req.destroy(new Error("timeout")));
112506
+ req.on("timeout", () => {
112507
+ resolve5("slow");
112508
+ req.destroy(new Error("timeout"));
112509
+ });
112130
112510
  req.on("error", (err) => resolve5(scheme === "https" && verify && /CERT|SELF_SIGNED|UNABLE_TO|SIGNATURE|HOSTNAME|ALTNAME/i.test(err.code ?? err.message) ? "cert" : "no"));
112131
112511
  req.end();
112132
112512
  });
112133
112513
  }
112134
112514
  async function identify(l, timeoutMs) {
112515
+ const before = seen.get(l.port);
112516
+ const server = await probe(l, timeoutMs);
112517
+ if (server && server !== "slow") {
112518
+ seen.set(l.port, { pid: l.pid, server });
112519
+ return server;
112520
+ }
112521
+ if (server === "slow" && before && before.pid === l.pid) return before.server;
112522
+ seen.delete(l.port);
112523
+ return null;
112524
+ }
112525
+ async function probe(l, timeoutMs) {
112135
112526
  const host2 = l.ipv4 ? "127.0.0.1" : "::1";
112136
112527
  const [plain, tls] = await Promise.all([ask("http", host2, l.port, true, timeoutMs), ask("https", host2, l.port, true, timeoutMs)]);
112137
112528
  if (plain === "yes") return { port: l.port, scheme: "http", name: l.name };
112138
112529
  if (tls === "yes") return { port: l.port, scheme: "https", name: l.name };
112139
112530
  if (tls === "cert" && await ask("https", host2, l.port, false, timeoutMs) === "yes") return { port: l.port, scheme: "https", name: l.name, selfSigned: true };
112140
- return null;
112531
+ return plain === "slow" || tls === "slow" ? "slow" : null;
112141
112532
  }
112142
- async function findLocalServers(skip = [], timeoutMs = 600) {
112533
+ async function findLocalServers(skip = [], timeoutMs = 1500) {
112143
112534
  const { ports, named } = await listPorts();
112144
112535
  const [answers] = await Promise.all([
112145
112536
  Promise.all(ports.filter((l) => !skip.includes(l.port) && !NOT_HTTP.has(l.port)).map(async (l) => ({ l, server: await identify(l, timeoutMs) }))),
@@ -112147,12 +112538,13 @@ async function findLocalServers(skip = [], timeoutMs = 600) {
112147
112538
  ]);
112148
112539
  return answers.filter(({ l, server }) => server && !l.service && !SYSTEM.has((l.name ?? "").toLowerCase())).map(({ l, server }) => ({ ...server, name: l.name })).sort((a, b) => a.port - b.port);
112149
112540
  }
112150
- var SYSTEM, NOT_HTTP;
112541
+ var SYSTEM, NOT_HTTP, seen;
112151
112542
  var init_local_servers = __esm({
112152
112543
  "../../apps/engine/src/local-servers.ts"() {
112153
112544
  "use strict";
112154
112545
  SYSTEM = /* @__PURE__ */ new Set(["system", "svchost", "lsass", "wininit", "services", "spoolsv", "launchd", "systemd", "systemd-resolved", "cupsd", "sshd", "rpcbind"]);
112155
112546
  NOT_HTTP = /* @__PURE__ */ new Set([22, 25, 53, 135, 139, 445, 1433, 1521, 3306, 3389, 5432, 5672, 6379, 9042, 11211, 27017]);
112547
+ seen = /* @__PURE__ */ new Map();
112156
112548
  }
112157
112549
  });
112158
112550
 
@@ -112255,9 +112647,11 @@ async function routes(api) {
112255
112647
  }
112256
112648
  const key = skip.join(",");
112257
112649
  if (!localCache || localCache.key !== key || Date.now() - localCache.at > 2e3) {
112258
- localCache = { at: Date.now(), key, servers: await findLocalServers(skip) };
112650
+ const entry = { at: Number.POSITIVE_INFINITY, key, servers: findLocalServers(skip) };
112651
+ void entry.servers.finally(() => entry.at = Date.now());
112652
+ localCache = entry;
112259
112653
  }
112260
- return { servers: localCache.servers };
112654
+ return { servers: await localCache.servers };
112261
112655
  });
112262
112656
  registerSuiteRoutes(api, workspace, suitePlans, (port2) => ({ practiceApi: `http://127.0.0.1:${port2}${api.prefix}/demo-api` }));
112263
112657
  onRunFinished(async (report, error51) => {
@@ -112448,7 +112842,7 @@ init_cookies();
112448
112842
  init_workspace_store();
112449
112843
  var repoRoot = resolve4(dirname3(fileURLToPath2(import.meta.url)), "../../..");
112450
112844
  var EXIT = { passed: 0, failed: 1, error: 2 };
112451
- var VERSION = "0.9.0";
112845
+ var VERSION = "0.10.0";
112452
112846
  var HELP = `APITestCenter CLI ${VERSION} \u2014 https://apitestcenter.com/docs/cli-and-ci.html
112453
112847
 
112454
112848
  Usage (apitestcenter, or the short name atc once installed)
@@ -112675,10 +113069,10 @@ Environments (${index.environments.length})`);
112675
113069
  }
112676
113070
  if (payload.data) log(`data ${payload.data.fileName} \xB7 ${payload.data.rows.length} rows \xB7 ${payload.data.order}`);
112677
113071
  if (!values["no-check"]) {
112678
- const probe = await runProbe({ request: payload.request, auth: payload.auth, options: payload.options, setup: payload.setup, data: payload.data });
112679
- const failedChecks = probe.checks?.filter((c) => !c.ok) ?? [];
112680
- log(`check ${probe.status || "ERR"} in ${probe.durationMs} ms${probe.checks ? ` \xB7 checks ${probe.checks.length - failedChecks.length}/${probe.checks.length}` : ""}`);
112681
- if (!probe.ok || probe.status < 200 || probe.status >= 300) fail(`check request returned ${probe.status || probe.error}; load did not start (use --no-check to force)`);
113072
+ const probe2 = await runProbe({ request: payload.request, auth: payload.auth, options: payload.options, setup: payload.setup, data: payload.data });
113073
+ const failedChecks = probe2.checks?.filter((c) => !c.ok) ?? [];
113074
+ log(`check ${probe2.status || "ERR"} in ${probe2.durationMs} ms${probe2.checks ? ` \xB7 checks ${probe2.checks.length - failedChecks.length}/${probe2.checks.length}` : ""}`);
113075
+ if (!probe2.ok || probe2.status < 200 || probe2.status >= 300) fail(`check request returned ${probe2.status || probe2.error}; load did not start (use --no-check to force)`);
112682
113076
  }
112683
113077
  const history2 = values.save ? new HistoryStore(dataDir) : null;
112684
113078
  if (history2) onRunFinished(async (report2, error51) => void await history2.saveRun(report2, { source: "run", error: error51 }));