@schwabyio/gta 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.
- package/README.md +2 -0
- package/dist/SPEC.md +182 -15
- package/dist/chunks/{chunk-VRAFYQNE.js → chunk-MZSHHPAQ.js} +860 -337
- package/dist/gta.js +45 -14
- package/dist/worker.js +1 -1
- package/package.json +1 -1
|
@@ -54452,8 +54452,8 @@ var MultipartPartSchema = external_exports.union(
|
|
|
54452
54452
|
file: relativePath("a multipart file"),
|
|
54453
54453
|
/** Absent: from the file's extension, else application/octet-stream. */
|
|
54454
54454
|
contentType: external_exports.string().min(1).optional(),
|
|
54455
|
-
/** The name sent for it; absent, the file's own. */
|
|
54456
|
-
filename: external_exports.string().
|
|
54455
|
+
/** The name sent for it; absent, the file's own. `''`, as a browser sends no file chosen. */
|
|
54456
|
+
filename: external_exports.string().optional()
|
|
54457
54457
|
})
|
|
54458
54458
|
],
|
|
54459
54459
|
{
|
|
@@ -54539,6 +54539,16 @@ var StepSchema = external_exports.looseObject({
|
|
|
54539
54539
|
tags: TagsSchema.optional(),
|
|
54540
54540
|
/** Feature flags this step needs; unmet, it is skipped (SPEC.md §2.9). */
|
|
54541
54541
|
flags: FlagConditionsSchema.optional(),
|
|
54542
|
+
/**
|
|
54543
|
+
* Send the request once for each item of a list, read as `{{item}}` and
|
|
54544
|
+
* `item` in code: `'{{roots}}'`, a variable holding a JSON array (SPEC.md §2.1).
|
|
54545
|
+
*/
|
|
54546
|
+
forEach: external_exports.string().min(1).optional(),
|
|
54547
|
+
/**
|
|
54548
|
+
* In a request set: the use step's own `tests` check this step's response,
|
|
54549
|
+
* rather than the set's last (SPEC.md §2.5). At most one step has it.
|
|
54550
|
+
*/
|
|
54551
|
+
useTests: external_exports.literal(true).optional(),
|
|
54542
54552
|
docs: external_exports.string().optional(),
|
|
54543
54553
|
headers: HeadersSchema.optional(),
|
|
54544
54554
|
body: BodySchema.optional(),
|
|
@@ -54558,6 +54568,13 @@ var StepSchema = external_exports.looseObject({
|
|
|
54558
54568
|
}
|
|
54559
54569
|
const methods = HTTP_METHODS.filter((m) => m in step);
|
|
54560
54570
|
if ("use" in step) {
|
|
54571
|
+
if ("forEach" in step) {
|
|
54572
|
+
ctx.addIssue({
|
|
54573
|
+
code: "custom",
|
|
54574
|
+
path: ["forEach"],
|
|
54575
|
+
message: "forEach repeats one request, so a use: step cannot have it (SPEC.md \xA72.1)"
|
|
54576
|
+
});
|
|
54577
|
+
}
|
|
54561
54578
|
const own2 = [...methods, ...USE_FORBIDDEN.filter((key) => key in step)];
|
|
54562
54579
|
for (const key of own2) {
|
|
54563
54580
|
ctx.addIssue({
|
|
@@ -54606,6 +54623,8 @@ var STEP_KEYS = /* @__PURE__ */ new Set([
|
|
|
54606
54623
|
"tests",
|
|
54607
54624
|
"tags",
|
|
54608
54625
|
"flags",
|
|
54626
|
+
"forEach",
|
|
54627
|
+
"useTests",
|
|
54609
54628
|
"base",
|
|
54610
54629
|
"docs"
|
|
54611
54630
|
]);
|
|
@@ -54688,8 +54707,49 @@ var CollectionSchema = external_exports.strictObject({
|
|
|
54688
54707
|
* and as `params.name` in code.
|
|
54689
54708
|
*/
|
|
54690
54709
|
params: external_exports.record(external_exports.string(), ParamSpecSchema).optional(),
|
|
54691
|
-
|
|
54710
|
+
/**
|
|
54711
|
+
* Steps run once before `steps`, and before every row of a data file:
|
|
54712
|
+
* what they set lasts the whole run (SPEC.md §2.10).
|
|
54713
|
+
*/
|
|
54714
|
+
setup: external_exports.array(StepSchema).optional(),
|
|
54715
|
+
steps: external_exports.array(StepSchema).default([]),
|
|
54716
|
+
/** Steps run once after the rest, even when they failed (SPEC.md §2.10). */
|
|
54717
|
+
teardown: external_exports.array(StepSchema).optional()
|
|
54692
54718
|
}).superRefine((collection, ctx) => {
|
|
54719
|
+
for (const stage of STAGES) {
|
|
54720
|
+
if (!collection[stage]) continue;
|
|
54721
|
+
if (collection.params) {
|
|
54722
|
+
ctx.addIssue({
|
|
54723
|
+
code: "custom",
|
|
54724
|
+
path: [stage],
|
|
54725
|
+
message: `a request set runs inside another collection, so it has no ${stage} (SPEC.md \xA72.10)`
|
|
54726
|
+
});
|
|
54727
|
+
}
|
|
54728
|
+
collection[stage].forEach((step, index) => {
|
|
54729
|
+
if (step.tags) {
|
|
54730
|
+
ctx.addIssue({
|
|
54731
|
+
code: "custom",
|
|
54732
|
+
path: [stage, index, "tags"],
|
|
54733
|
+
message: `a ${stage} step runs whenever its collection does, so it has no tags (SPEC.md \xA72.10)`
|
|
54734
|
+
});
|
|
54735
|
+
}
|
|
54736
|
+
});
|
|
54737
|
+
}
|
|
54738
|
+
const marked = collection.steps.flatMap((step, index) => step.useTests ? [index] : []);
|
|
54739
|
+
if (marked.length > 0 && !collection.params) {
|
|
54740
|
+
ctx.addIssue({
|
|
54741
|
+
code: "custom",
|
|
54742
|
+
path: ["steps", marked[0], "useTests"],
|
|
54743
|
+
message: "useTests marks the request set step whose response a use step's tests check; this collection has no params, so it is not a request set (SPEC.md \xA72.5)"
|
|
54744
|
+
});
|
|
54745
|
+
}
|
|
54746
|
+
if (marked.length > 1) {
|
|
54747
|
+
ctx.addIssue({
|
|
54748
|
+
code: "custom",
|
|
54749
|
+
path: ["steps", marked[1], "useTests"],
|
|
54750
|
+
message: `only one step can have useTests; step ${marked[0] + 1} has it already (SPEC.md \xA72.5)`
|
|
54751
|
+
});
|
|
54752
|
+
}
|
|
54693
54753
|
if (collection.params) {
|
|
54694
54754
|
collection.steps.forEach((step, index) => {
|
|
54695
54755
|
if (isUseStep(step)) {
|
|
@@ -54719,6 +54779,8 @@ var CollectionSchema = external_exports.strictObject({
|
|
|
54719
54779
|
}
|
|
54720
54780
|
});
|
|
54721
54781
|
});
|
|
54782
|
+
var STAGES = ["setup", "teardown"];
|
|
54783
|
+
var STEP_LISTS = ["setup", "steps", "teardown"];
|
|
54722
54784
|
var EnvironmentVarSchema = external_exports.union([
|
|
54723
54785
|
VarValueSchema,
|
|
54724
54786
|
external_exports.strictObject({
|
|
@@ -54996,25 +55058,32 @@ function decodeEntities(text) {
|
|
|
54996
55058
|
}
|
|
54997
55059
|
|
|
54998
55060
|
// ../../packages/core/src/model/path.ts
|
|
55061
|
+
var SEGMENT = (
|
|
55062
|
+
// eslint-disable-next-line no-control-regex
|
|
55063
|
+
/\[("(?:[^"\\\u0000-\u001f]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*")\]|([^.[\]]+)|\[(\d*)\]/g
|
|
55064
|
+
);
|
|
54999
55065
|
function parsePath(path12) {
|
|
55000
55066
|
const segments = [];
|
|
55001
|
-
const
|
|
55002
|
-
|
|
55003
|
-
if (match[
|
|
55004
|
-
else if (match[
|
|
55005
|
-
else segments.push({ kind: "index", index: Number(match[
|
|
55067
|
+
for (const match of path12.matchAll(SEGMENT)) {
|
|
55068
|
+
if (match[1] !== void 0) segments.push({ kind: "key", key: JSON.parse(match[1]) });
|
|
55069
|
+
else if (match[2] !== void 0) segments.push({ kind: "key", key: match[2] });
|
|
55070
|
+
else if (match[3] === "") segments.push({ kind: "each" });
|
|
55071
|
+
else segments.push({ kind: "index", index: Number(match[3]) });
|
|
55006
55072
|
}
|
|
55007
55073
|
return segments;
|
|
55008
55074
|
}
|
|
55009
55075
|
function formatPath(segments) {
|
|
55010
55076
|
let out = "";
|
|
55011
55077
|
for (const segment of segments) {
|
|
55012
|
-
if (segment.kind === "key")
|
|
55013
|
-
|
|
55078
|
+
if (segment.kind === "key") {
|
|
55079
|
+
if (!plainKey(segment.key)) out += `[${JSON.stringify(segment.key)}]`;
|
|
55080
|
+
else out += out === "" ? segment.key : `.${segment.key}`;
|
|
55081
|
+
} else if (segment.kind === "index") out += `[${segment.index}]`;
|
|
55014
55082
|
else out += "[]";
|
|
55015
55083
|
}
|
|
55016
55084
|
return out;
|
|
55017
55085
|
}
|
|
55086
|
+
var plainKey = (key) => key !== "" && !/[.[\]]/.test(key);
|
|
55018
55087
|
var isIndexKey = (key) => /^\d+$/.test(key);
|
|
55019
55088
|
function resolvePath(root, pattern) {
|
|
55020
55089
|
let frontier = [{ path: [], value: root }];
|
|
@@ -55156,7 +55225,7 @@ var RunErrorSchema = external_exports.object({
|
|
|
55156
55225
|
* Which stage failed, so the UI can say where without guessing. `body` is a
|
|
55157
55226
|
* file the body names that could not be read (SPEC.md §2.2).
|
|
55158
55227
|
*/
|
|
55159
|
-
phase: external_exports.enum(["use", "flags", "interpolate", "body", "pre-request", "http", "tests"]),
|
|
55228
|
+
phase: external_exports.enum(["use", "flags", "forEach", "interpolate", "body", "pre-request", "http", "tests"]),
|
|
55160
55229
|
message: external_exports.string(),
|
|
55161
55230
|
code: external_exports.string().optional(),
|
|
55162
55231
|
stack: external_exports.string().optional(),
|
|
@@ -55188,16 +55257,30 @@ var RunResultSchema = external_exports.object({
|
|
|
55188
55257
|
logs: external_exports.array(LogEntrySchema).optional(),
|
|
55189
55258
|
error: RunErrorSchema.nullable(),
|
|
55190
55259
|
status: RunStatusSchema,
|
|
55191
|
-
/**
|
|
55260
|
+
/**
|
|
55261
|
+
* Why a `skipped` step did not run: a feature flag it needs (SPEC.md §2.9),
|
|
55262
|
+
* `gta.skip` or `gta.skipRest` (§5), or an empty `forEach` list.
|
|
55263
|
+
*/
|
|
55192
55264
|
skipped: external_exports.object({ reason: external_exports.string() }).optional(),
|
|
55193
55265
|
/** Wall-clock milliseconds for the whole item, scripts included. */
|
|
55194
55266
|
durationMs: external_exports.number(),
|
|
55195
55267
|
/**
|
|
55196
|
-
* For a request run by a use step:
|
|
55197
|
-
*
|
|
55268
|
+
* For a request run by a use step: the set as `use:` names it, the use
|
|
55269
|
+
* step's own `name` when it has one, and which step of the set it was
|
|
55270
|
+
* (0-based) of how many. Absent for a collection's own request.
|
|
55198
55271
|
*/
|
|
55199
|
-
use: external_exports.object({ set: external_exports.string(), child: external_exports.number(), of: external_exports.number() }).optional()
|
|
55200
|
-
|
|
55272
|
+
use: external_exports.object({ set: external_exports.string(), name: external_exports.string().optional(), child: external_exports.number(), of: external_exports.number() }).optional(),
|
|
55273
|
+
/** For a `setup` or `teardown` step: which list it is in (SPEC.md §2.10). Absent for `steps`. */
|
|
55274
|
+
stage: external_exports.enum(["setup", "teardown"]).optional(),
|
|
55275
|
+
/** For a step with `forEach`: which item this request was for, from 0, of how many. */
|
|
55276
|
+
forEach: external_exports.object({ index: external_exports.number(), of: external_exports.number(), item: external_exports.string() }).optional()
|
|
55277
|
+
});
|
|
55278
|
+
function resultName(result) {
|
|
55279
|
+
const use = result.use;
|
|
55280
|
+
const name = !use?.name ? result.item.name : use.of === 1 ? use.name : `${use.name} \u203A ${result.item.name}`;
|
|
55281
|
+
const each = result.forEach ? ` (item ${result.forEach.index + 1} of ${result.forEach.of})` : "";
|
|
55282
|
+
return `${result.stage ? `${result.stage} \u203A ` : ""}${name}${each}`;
|
|
55283
|
+
}
|
|
55201
55284
|
var CollectionRunSummarySchema = external_exports.object({
|
|
55202
55285
|
total: external_exports.number(),
|
|
55203
55286
|
passed: external_exports.number(),
|
|
@@ -55578,8 +55661,10 @@ var STEP_KEY_ORDER = [
|
|
|
55578
55661
|
"use",
|
|
55579
55662
|
"with",
|
|
55580
55663
|
...HTTP_METHODS,
|
|
55664
|
+
"forEach",
|
|
55581
55665
|
"tags",
|
|
55582
55666
|
"flags",
|
|
55667
|
+
"useTests",
|
|
55583
55668
|
"base",
|
|
55584
55669
|
"docs",
|
|
55585
55670
|
"headers",
|
|
@@ -55631,116 +55716,36 @@ var runtime = defaultRuntime();
|
|
|
55631
55716
|
// ../../packages/core/src/git/diff.ts
|
|
55632
55717
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
55633
55718
|
|
|
55634
|
-
// ../../packages/core/src/workspace/collection.ts
|
|
55635
|
-
import fs4 from "node:fs/promises";
|
|
55636
|
-
import path4 from "node:path";
|
|
55637
|
-
|
|
55638
|
-
// ../../packages/core/src/workspace/ids.ts
|
|
55639
|
-
import path from "node:path";
|
|
55640
|
-
var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
55641
|
-
var idOfFile = (file2) => path.basename(file2, DOC_EXTENSION);
|
|
55642
|
-
function idProblem(doc, file2) {
|
|
55643
|
-
const expected = idOfFile(file2);
|
|
55644
|
-
if (!ID_PATTERN.test(expected)) {
|
|
55645
|
-
return `${expected}${DOC_EXTENSION}: a collection's file name is its id, and an id is letters, digits and - _ . (SPEC.md \xA72)`;
|
|
55646
|
-
}
|
|
55647
|
-
const unusable = nameProblem(path.basename(file2));
|
|
55648
|
-
if (unusable) return `${path.basename(file2)}: ${unusable} (SPEC.md \xA71.2)`;
|
|
55649
|
-
if (doc.id === void 0) {
|
|
55650
|
-
return `id: missing \u2014 a collection file says its id, its file name: id: ${expected} (SPEC.md \xA72)`;
|
|
55651
|
-
}
|
|
55652
|
-
if (doc.id !== expected) {
|
|
55653
|
-
return `id: ${doc.id} does not match the file name, ${expected}${DOC_EXTENSION} \u2014 they must be the same (SPEC.md \xA72)`;
|
|
55654
|
-
}
|
|
55655
|
-
return null;
|
|
55656
|
-
}
|
|
55657
|
-
function duplicateIds(files, home) {
|
|
55658
|
-
const byId = /* @__PURE__ */ new Map();
|
|
55659
|
-
for (const file2 of files) {
|
|
55660
|
-
const key = idOfFile(file2).toLowerCase();
|
|
55661
|
-
byId.set(key, [...byId.get(key) ?? [], file2]);
|
|
55662
|
-
}
|
|
55663
|
-
const problems = /* @__PURE__ */ new Map();
|
|
55664
|
-
for (const group of byId.values()) {
|
|
55665
|
-
if (group.length < 2) continue;
|
|
55666
|
-
for (const file2 of group) {
|
|
55667
|
-
const others = group.filter((other) => other !== file2).map((other) => `${path.basename(home)}/${relativePosix(home, other)}`);
|
|
55668
|
-
problems.set(
|
|
55669
|
-
file2,
|
|
55670
|
-
`id: ${idOfFile(file2)} is also the id of ${others.join(", ")} \u2014 an id is unique in ${path.basename(home)}/, ignoring case (SPEC.md \xA72)`
|
|
55671
|
-
);
|
|
55672
|
-
}
|
|
55673
|
-
}
|
|
55674
|
-
return problems;
|
|
55675
|
-
}
|
|
55676
|
-
|
|
55677
|
-
// ../../packages/core/src/workspace/dataFile.ts
|
|
55678
|
-
import fs2 from "node:fs/promises";
|
|
55679
|
-
import path2 from "node:path";
|
|
55680
|
-
var MAX_BYTES = 10 * 1024 * 1024;
|
|
55681
|
-
async function findDataFile(collectionFile) {
|
|
55682
|
-
const base2 = collectionFile.endsWith(DOC_EXTENSION) ? collectionFile.slice(0, -DOC_EXTENSION.length) : collectionFile;
|
|
55683
|
-
for (const extension of [".csv", ".json"]) {
|
|
55684
|
-
try {
|
|
55685
|
-
const candidate = `${base2}${extension}`;
|
|
55686
|
-
const names = await fs2.readdir(path2.dirname(candidate));
|
|
55687
|
-
if (names.includes(path2.basename(candidate))) return candidate;
|
|
55688
|
-
} catch {
|
|
55689
|
-
return null;
|
|
55690
|
-
}
|
|
55691
|
-
}
|
|
55692
|
-
return null;
|
|
55693
|
-
}
|
|
55694
|
-
async function readDataTable(file2) {
|
|
55695
|
-
const stat = await fs2.stat(file2);
|
|
55696
|
-
if (stat.size > MAX_BYTES) {
|
|
55697
|
-
throw new DataFileError(`${path2.basename(file2)}: larger than ${MAX_BYTES / 1024 / 1024} MB`);
|
|
55698
|
-
}
|
|
55699
|
-
const source = await fs2.readFile(file2, "utf8");
|
|
55700
|
-
return { source, table: parseDataTable(source, path2.basename(file2), dataKindOf(file2)) };
|
|
55701
|
-
}
|
|
55702
|
-
var rowsOf = (table) => table.rows.map((values) => ({ values, label: rowLabel(values) }));
|
|
55703
|
-
async function readDataFile(file2) {
|
|
55704
|
-
const { table } = await readDataTable(file2);
|
|
55705
|
-
const problem = dataTableProblems(table)[0];
|
|
55706
|
-
if (problem) throw new DataFileError(`${path2.basename(file2)}: ${problem}`);
|
|
55707
|
-
return { path: file2, rows: rowsOf(table) };
|
|
55708
|
-
}
|
|
55709
|
-
async function loadDataFile(collectionFile) {
|
|
55710
|
-
const file2 = await findDataFile(collectionFile);
|
|
55711
|
-
return file2 ? readDataFile(file2) : null;
|
|
55712
|
-
}
|
|
55713
|
-
|
|
55714
55719
|
// ../../packages/core/src/workspace/project.ts
|
|
55715
|
-
import
|
|
55716
|
-
import
|
|
55720
|
+
import fs2 from "node:fs/promises";
|
|
55721
|
+
import path from "node:path";
|
|
55717
55722
|
var HOMES = /* @__PURE__ */ new Set([COLLECTIONS_DIR, REQUESTS_DIR, ENDPOINTS_DIR, BASES_DIR]);
|
|
55718
55723
|
function projectRootOf(collectionFile) {
|
|
55719
|
-
const directory =
|
|
55720
|
-
if (HOMES.has(
|
|
55721
|
-
const parent =
|
|
55722
|
-
if (HOMES.has(
|
|
55724
|
+
const directory = path.dirname(path.resolve(collectionFile));
|
|
55725
|
+
if (HOMES.has(path.basename(directory))) return path.dirname(directory);
|
|
55726
|
+
const parent = path.dirname(directory);
|
|
55727
|
+
if (HOMES.has(path.basename(parent))) return path.dirname(parent);
|
|
55723
55728
|
return null;
|
|
55724
55729
|
}
|
|
55725
55730
|
function directoryOf(collectionFile) {
|
|
55726
|
-
const directory =
|
|
55727
|
-
return HOMES.has(
|
|
55731
|
+
const directory = path.dirname(path.resolve(collectionFile));
|
|
55732
|
+
return HOMES.has(path.basename(directory)) ? null : path.basename(directory);
|
|
55728
55733
|
}
|
|
55729
55734
|
var isHidden = (name) => name.startsWith(".") || IGNORED_DIRECTORIES.has(name);
|
|
55730
55735
|
var isCollectionFile = (name) => name.endsWith(DOC_EXTENSION) && !name.startsWith(".");
|
|
55731
55736
|
async function discoverProject(root) {
|
|
55732
|
-
const collectionsDir =
|
|
55737
|
+
const collectionsDir = path.join(root, COLLECTIONS_DIR);
|
|
55733
55738
|
const layout = { root, collectionsDir, files: [], directories: [], problems: [] };
|
|
55734
55739
|
const entries2 = await readDir(collectionsDir);
|
|
55735
55740
|
for (const entry of entries2) {
|
|
55736
55741
|
if (entry.isFile() && isCollectionFile(entry.name)) {
|
|
55737
|
-
layout.files.push(
|
|
55742
|
+
layout.files.push(path.join(collectionsDir, entry.name));
|
|
55738
55743
|
} else if (entry.isDirectory() && !isHidden(entry.name)) {
|
|
55739
55744
|
layout.directories.push(entry.name);
|
|
55740
|
-
const directory =
|
|
55745
|
+
const directory = path.join(collectionsDir, entry.name);
|
|
55741
55746
|
for (const inner of await readDir(directory)) {
|
|
55742
55747
|
if (inner.isFile() && isCollectionFile(inner.name)) {
|
|
55743
|
-
layout.files.push(
|
|
55748
|
+
layout.files.push(path.join(directory, inner.name));
|
|
55744
55749
|
} else if (inner.isDirectory() && !isHidden(inner.name)) {
|
|
55745
55750
|
layout.problems.push({
|
|
55746
55751
|
path: `${entry.name}/${inner.name}`,
|
|
@@ -55780,14 +55785,14 @@ async function portabilityProblems(root) {
|
|
|
55780
55785
|
}
|
|
55781
55786
|
}
|
|
55782
55787
|
for (const home of [COLLECTIONS_DIR, REQUESTS_DIR, ENDPOINTS_DIR, BASES_DIR]) {
|
|
55783
|
-
const directories = (await readDir(
|
|
55788
|
+
const directories = (await readDir(path.join(root, home))).filter((entry) => entry.isDirectory() && !isHidden(entry.name)).map((entry) => entry.name);
|
|
55784
55789
|
problems.push(...portableNameProblems(home, directories));
|
|
55785
55790
|
}
|
|
55786
55791
|
for (const [home, extension] of [
|
|
55787
55792
|
[ENVIRONMENTS_DIR, DOC_EXTENSION],
|
|
55788
55793
|
[CHECKS_DIR, ".js"]
|
|
55789
55794
|
]) {
|
|
55790
|
-
const files = (await readDir(
|
|
55795
|
+
const files = (await readDir(path.join(root, home))).filter((entry) => entry.isFile() && entry.name.endsWith(extension)).map((entry) => entry.name);
|
|
55791
55796
|
problems.push(...portableNameProblems(home, files));
|
|
55792
55797
|
}
|
|
55793
55798
|
return problems;
|
|
@@ -55856,7 +55861,7 @@ async function readProject(root) {
|
|
|
55856
55861
|
async function readProjectFile(root) {
|
|
55857
55862
|
let source;
|
|
55858
55863
|
try {
|
|
55859
|
-
source = await
|
|
55864
|
+
source = await fs2.readFile(path.join(root, PROJECT_FILE), "utf8");
|
|
55860
55865
|
} catch {
|
|
55861
55866
|
return { doc: null, source: null, problem: null };
|
|
55862
55867
|
}
|
|
@@ -55874,18 +55879,18 @@ async function readProjectFile(root) {
|
|
|
55874
55879
|
}
|
|
55875
55880
|
}
|
|
55876
55881
|
async function projectEnvironments(root, global) {
|
|
55877
|
-
const own2 = await readEnvironments(
|
|
55878
|
-
const shared = global ? await readEnvironments(
|
|
55882
|
+
const own2 = await readEnvironments(path.join(root, ENVIRONMENTS_DIR), "project");
|
|
55883
|
+
const shared = global ? await readEnvironments(path.join(global.root, ENVIRONMENTS_DIR), "global") : [];
|
|
55879
55884
|
return [...own2, ...shared];
|
|
55880
55885
|
}
|
|
55881
55886
|
async function readEnvironments(directory, source = "project") {
|
|
55882
55887
|
const environments = [];
|
|
55883
55888
|
for (const entry of await readDir(directory)) {
|
|
55884
55889
|
if (!entry.isFile() || !entry.name.endsWith(DOC_EXTENSION)) continue;
|
|
55885
|
-
const file2 =
|
|
55886
|
-
let name =
|
|
55890
|
+
const file2 = path.join(directory, entry.name);
|
|
55891
|
+
let name = path.basename(entry.name, DOC_EXTENSION);
|
|
55887
55892
|
try {
|
|
55888
|
-
name = parseEnvironment(await
|
|
55893
|
+
name = parseEnvironment(await fs2.readFile(file2, "utf8"), file2).data.name ?? name;
|
|
55889
55894
|
} catch {
|
|
55890
55895
|
}
|
|
55891
55896
|
environments.push({ name, path: file2, source });
|
|
@@ -55894,12 +55899,92 @@ async function readEnvironments(directory, source = "project") {
|
|
|
55894
55899
|
}
|
|
55895
55900
|
async function readDir(directory) {
|
|
55896
55901
|
try {
|
|
55897
|
-
return await
|
|
55902
|
+
return await fs2.readdir(directory, { withFileTypes: true });
|
|
55898
55903
|
} catch {
|
|
55899
55904
|
return [];
|
|
55900
55905
|
}
|
|
55901
55906
|
}
|
|
55902
55907
|
|
|
55908
|
+
// ../../packages/core/src/workspace/collection.ts
|
|
55909
|
+
import fs4 from "node:fs/promises";
|
|
55910
|
+
import path4 from "node:path";
|
|
55911
|
+
|
|
55912
|
+
// ../../packages/core/src/workspace/ids.ts
|
|
55913
|
+
import path2 from "node:path";
|
|
55914
|
+
var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
55915
|
+
var idOfFile = (file2) => path2.basename(file2, DOC_EXTENSION);
|
|
55916
|
+
function idProblem(doc, file2) {
|
|
55917
|
+
const expected = idOfFile(file2);
|
|
55918
|
+
if (!ID_PATTERN.test(expected)) {
|
|
55919
|
+
return `${expected}${DOC_EXTENSION}: a collection's file name is its id, and an id is letters, digits and - _ . (SPEC.md \xA72)`;
|
|
55920
|
+
}
|
|
55921
|
+
const unusable = nameProblem(path2.basename(file2));
|
|
55922
|
+
if (unusable) return `${path2.basename(file2)}: ${unusable} (SPEC.md \xA71.2)`;
|
|
55923
|
+
if (doc.id === void 0) {
|
|
55924
|
+
return `id: missing \u2014 a collection file says its id, its file name: id: ${expected} (SPEC.md \xA72)`;
|
|
55925
|
+
}
|
|
55926
|
+
if (doc.id !== expected) {
|
|
55927
|
+
return `id: ${doc.id} does not match the file name, ${expected}${DOC_EXTENSION} \u2014 they must be the same (SPEC.md \xA72)`;
|
|
55928
|
+
}
|
|
55929
|
+
return null;
|
|
55930
|
+
}
|
|
55931
|
+
function duplicateIds(files, home) {
|
|
55932
|
+
const byId = /* @__PURE__ */ new Map();
|
|
55933
|
+
for (const file2 of files) {
|
|
55934
|
+
const key = idOfFile(file2).toLowerCase();
|
|
55935
|
+
byId.set(key, [...byId.get(key) ?? [], file2]);
|
|
55936
|
+
}
|
|
55937
|
+
const problems = /* @__PURE__ */ new Map();
|
|
55938
|
+
for (const group of byId.values()) {
|
|
55939
|
+
if (group.length < 2) continue;
|
|
55940
|
+
for (const file2 of group) {
|
|
55941
|
+
const others = group.filter((other) => other !== file2).map((other) => `${path2.basename(home)}/${relativePosix(home, other)}`);
|
|
55942
|
+
problems.set(
|
|
55943
|
+
file2,
|
|
55944
|
+
`id: ${idOfFile(file2)} is also the id of ${others.join(", ")} \u2014 an id is unique in ${path2.basename(home)}/, ignoring case (SPEC.md \xA72)`
|
|
55945
|
+
);
|
|
55946
|
+
}
|
|
55947
|
+
}
|
|
55948
|
+
return problems;
|
|
55949
|
+
}
|
|
55950
|
+
|
|
55951
|
+
// ../../packages/core/src/workspace/dataFile.ts
|
|
55952
|
+
import fs3 from "node:fs/promises";
|
|
55953
|
+
import path3 from "node:path";
|
|
55954
|
+
var MAX_BYTES = 10 * 1024 * 1024;
|
|
55955
|
+
async function findDataFile(collectionFile) {
|
|
55956
|
+
const base2 = collectionFile.endsWith(DOC_EXTENSION) ? collectionFile.slice(0, -DOC_EXTENSION.length) : collectionFile;
|
|
55957
|
+
for (const extension of [".csv", ".json"]) {
|
|
55958
|
+
try {
|
|
55959
|
+
const candidate = `${base2}${extension}`;
|
|
55960
|
+
const names = await fs3.readdir(path3.dirname(candidate));
|
|
55961
|
+
if (names.includes(path3.basename(candidate))) return candidate;
|
|
55962
|
+
} catch {
|
|
55963
|
+
return null;
|
|
55964
|
+
}
|
|
55965
|
+
}
|
|
55966
|
+
return null;
|
|
55967
|
+
}
|
|
55968
|
+
async function readDataTable(file2) {
|
|
55969
|
+
const stat = await fs3.stat(file2);
|
|
55970
|
+
if (stat.size > MAX_BYTES) {
|
|
55971
|
+
throw new DataFileError(`${path3.basename(file2)}: larger than ${MAX_BYTES / 1024 / 1024} MB`);
|
|
55972
|
+
}
|
|
55973
|
+
const source = await fs3.readFile(file2, "utf8");
|
|
55974
|
+
return { source, table: parseDataTable(source, path3.basename(file2), dataKindOf(file2)) };
|
|
55975
|
+
}
|
|
55976
|
+
var rowsOf = (table) => table.rows.map((values) => ({ values, label: rowLabel(values) }));
|
|
55977
|
+
async function readDataFile(file2) {
|
|
55978
|
+
const { table } = await readDataTable(file2);
|
|
55979
|
+
const problem = dataTableProblems(table)[0];
|
|
55980
|
+
if (problem) throw new DataFileError(`${path3.basename(file2)}: ${problem}`);
|
|
55981
|
+
return { path: file2, rows: rowsOf(table) };
|
|
55982
|
+
}
|
|
55983
|
+
async function loadDataFile(collectionFile) {
|
|
55984
|
+
const file2 = await findDataFile(collectionFile);
|
|
55985
|
+
return file2 ? readDataFile(file2) : null;
|
|
55986
|
+
}
|
|
55987
|
+
|
|
55903
55988
|
// ../../packages/core/src/workspace/collection.ts
|
|
55904
55989
|
async function loadCollection(file2, root) {
|
|
55905
55990
|
const absolute = path4.resolve(file2);
|
|
@@ -56223,6 +56308,7 @@ async function loadEndpoints(root, global) {
|
|
|
56223
56308
|
}
|
|
56224
56309
|
function baseProblem(doc) {
|
|
56225
56310
|
if (doc.steps.length > 0) return "a base collection has no steps of its own";
|
|
56311
|
+
if (doc.setup || doc.teardown) return "a base collection has no setup or teardown";
|
|
56226
56312
|
if (doc.params) return "a base collection has no params";
|
|
56227
56313
|
if (doc.extends) return "a base collection cannot extend another (SPEC.md \xA72.7)";
|
|
56228
56314
|
return void 0;
|
|
@@ -56451,6 +56537,14 @@ var VariableScope = class {
|
|
|
56451
56537
|
* `gta.flag`; null when none are known, so any flag named is unknown.
|
|
56452
56538
|
*/
|
|
56453
56539
|
flags = null;
|
|
56540
|
+
/**
|
|
56541
|
+
* Values that last the whole collection run, across its data rows and into
|
|
56542
|
+
* teardown (SPEC.md §2.10): where `gta.set(…, { scope: 'run' })` writes.
|
|
56543
|
+
* Null outside a collection run, where there is nothing to outlast.
|
|
56544
|
+
*/
|
|
56545
|
+
runValues = null;
|
|
56546
|
+
/** In setup and teardown, every value set lasts the run. */
|
|
56547
|
+
runWide = false;
|
|
56454
56548
|
values = /* @__PURE__ */ new Map();
|
|
56455
56549
|
origins = /* @__PURE__ */ new Map();
|
|
56456
56550
|
secrets = /* @__PURE__ */ new Set();
|
|
@@ -56494,6 +56588,12 @@ var VariableScope = class {
|
|
|
56494
56588
|
set(name, value, source = "runtime") {
|
|
56495
56589
|
this.values.set(name, value);
|
|
56496
56590
|
this.origins.set(name, source);
|
|
56591
|
+
if (this.runWide) this.runValues?.set(name, value);
|
|
56592
|
+
}
|
|
56593
|
+
/** `set`, and keep the value for every row after this one and for teardown. */
|
|
56594
|
+
setForRun(name, value, source = "runtime") {
|
|
56595
|
+
this.set(name, value, source);
|
|
56596
|
+
this.runValues?.set(name, value);
|
|
56497
56597
|
}
|
|
56498
56598
|
names() {
|
|
56499
56599
|
return [...this.values.keys()].sort();
|
|
@@ -56503,35 +56603,49 @@ var VariableScope = class {
|
|
|
56503
56603
|
return Object.fromEntries(this.values);
|
|
56504
56604
|
}
|
|
56505
56605
|
};
|
|
56506
|
-
var
|
|
56507
|
-
constructor(run,
|
|
56606
|
+
var OverlayScope = class extends VariableScope {
|
|
56607
|
+
constructor(run, own2, origin) {
|
|
56508
56608
|
super();
|
|
56509
56609
|
this.run = run;
|
|
56510
|
-
this.
|
|
56610
|
+
this.own = own2;
|
|
56611
|
+
this.origin = origin;
|
|
56511
56612
|
this.flags = run.flags;
|
|
56512
56613
|
}
|
|
56513
56614
|
run;
|
|
56514
|
-
|
|
56615
|
+
own;
|
|
56616
|
+
origin;
|
|
56515
56617
|
has(name) {
|
|
56516
|
-
return this.
|
|
56618
|
+
return this.own.has(name) || this.run.has(name);
|
|
56517
56619
|
}
|
|
56518
56620
|
get(name) {
|
|
56519
|
-
return this.
|
|
56621
|
+
return this.own.has(name) ? this.own.get(name) : this.run.get(name);
|
|
56520
56622
|
}
|
|
56521
56623
|
isSecret(name) {
|
|
56522
|
-
return !this.
|
|
56624
|
+
return !this.own.has(name) && this.run.isSecret(name);
|
|
56523
56625
|
}
|
|
56524
56626
|
originOf(name) {
|
|
56525
|
-
return this.
|
|
56627
|
+
return this.own.has(name) ? this.origin : this.run.originOf(name);
|
|
56526
56628
|
}
|
|
56527
56629
|
set(name, value, source = "runtime") {
|
|
56528
56630
|
this.run.set(name, value, source);
|
|
56529
56631
|
}
|
|
56632
|
+
setForRun(name, value, source = "runtime") {
|
|
56633
|
+
this.run.setForRun(name, value, source);
|
|
56634
|
+
}
|
|
56530
56635
|
names() {
|
|
56531
|
-
return [.../* @__PURE__ */ new Set([...this.
|
|
56636
|
+
return [.../* @__PURE__ */ new Set([...this.own.keys(), ...this.run.names()])].sort();
|
|
56532
56637
|
}
|
|
56533
56638
|
all() {
|
|
56534
|
-
return { ...this.run.all(), ...Object.fromEntries(this.
|
|
56639
|
+
return { ...this.run.all(), ...Object.fromEntries(this.own) };
|
|
56640
|
+
}
|
|
56641
|
+
};
|
|
56642
|
+
var ParamsScope = class extends OverlayScope {
|
|
56643
|
+
constructor(run, params) {
|
|
56644
|
+
super(
|
|
56645
|
+
run,
|
|
56646
|
+
new Map(Object.entries(params).map(([name, value]) => [`params.${name}`, value])),
|
|
56647
|
+
"params"
|
|
56648
|
+
);
|
|
56535
56649
|
}
|
|
56536
56650
|
};
|
|
56537
56651
|
var BUILT_INS = {
|
|
@@ -56545,19 +56659,19 @@ var BUILT_INS = {
|
|
|
56545
56659
|
var REFERENCE = /\{\{\s*([^{}\s]+)\s*\}\}/g;
|
|
56546
56660
|
var SOLE_REFERENCE = /^\{\{\s*([^{}\s]+)\s*\}\}$/;
|
|
56547
56661
|
var MAX_DEPTH = 16;
|
|
56548
|
-
function interpolate(value, scope) {
|
|
56662
|
+
function interpolate(value, scope, options = {}) {
|
|
56549
56663
|
const sole = SOLE_REFERENCE.exec(value);
|
|
56550
56664
|
if (sole?.[1]) {
|
|
56551
56665
|
const resolved = resolveName(sole[1], scope, []);
|
|
56552
|
-
return typeof resolved === "string" ? resolveString(resolved, scope, [sole[1]]) : resolved;
|
|
56666
|
+
return typeof resolved === "string" ? resolveString(resolved, scope, [sole[1]], options) : resolved;
|
|
56553
56667
|
}
|
|
56554
|
-
return resolveString(value, scope, []);
|
|
56668
|
+
return resolveString(value, scope, [], options);
|
|
56555
56669
|
}
|
|
56556
|
-
function interpolateToString(value, scope) {
|
|
56557
|
-
const result = interpolate(value, scope);
|
|
56558
|
-
return result === null || result === void 0 ? "" : String(result);
|
|
56670
|
+
function interpolateToString(value, scope, options = {}) {
|
|
56671
|
+
const result = interpolate(value, scope, options);
|
|
56672
|
+
return result === null || result === void 0 ? options.nullAs ?? "" : String(result);
|
|
56559
56673
|
}
|
|
56560
|
-
function resolveString(text, scope, seen) {
|
|
56674
|
+
function resolveString(text, scope, seen, options) {
|
|
56561
56675
|
if (seen.length > MAX_DEPTH) {
|
|
56562
56676
|
throw new InterpolationError(
|
|
56563
56677
|
`Variable nesting is too deep (${seen.join(" -> ")}); this is probably a loop`
|
|
@@ -56571,11 +56685,12 @@ function resolveString(text, scope, seen) {
|
|
|
56571
56685
|
);
|
|
56572
56686
|
}
|
|
56573
56687
|
const resolved = resolveName(name, scope, seen);
|
|
56574
|
-
if (resolved === null || resolved === void 0) return "";
|
|
56575
|
-
return typeof resolved === "string" ? resolveString(resolved, scope, [...seen, name]) : String(resolved);
|
|
56688
|
+
if (resolved === null || resolved === void 0) return options.nullAs ?? "";
|
|
56689
|
+
return typeof resolved === "string" ? resolveString(resolved, scope, [...seen, name], options) : String(resolved);
|
|
56576
56690
|
});
|
|
56577
56691
|
}
|
|
56578
56692
|
function resolveName(name, scope, seen) {
|
|
56693
|
+
if (name.length > 1 && name.startsWith("@")) return resolveNamed(name, scope, seen);
|
|
56579
56694
|
const builtIn = BUILT_INS[name];
|
|
56580
56695
|
if (builtIn) return builtIn();
|
|
56581
56696
|
if (!scope.has(name)) {
|
|
@@ -56592,6 +56707,31 @@ function resolveName(name, scope, seen) {
|
|
|
56592
56707
|
}
|
|
56593
56708
|
return value;
|
|
56594
56709
|
}
|
|
56710
|
+
function resolveNamed(name, scope, seen) {
|
|
56711
|
+
const holder = name.slice(1);
|
|
56712
|
+
const through = [...seen, name];
|
|
56713
|
+
const held = resolveName(holder, scope, through);
|
|
56714
|
+
const target = typeof held === "string" ? resolveString(held, scope, [...through, holder], {}) : held;
|
|
56715
|
+
if (typeof target !== "string" || target.trim() === "") {
|
|
56716
|
+
throw new InterpolationError(
|
|
56717
|
+
`{{${name}}} reads the variable ${holder} names, but ${holder} is ${JSON.stringify(target)}, not a variable name`,
|
|
56718
|
+
holder
|
|
56719
|
+
);
|
|
56720
|
+
}
|
|
56721
|
+
if (through.includes(target)) {
|
|
56722
|
+
throw new InterpolationError(
|
|
56723
|
+
`Variable "${target}" refers to itself: ${[...through, target].join(" -> ")}`,
|
|
56724
|
+
target
|
|
56725
|
+
);
|
|
56726
|
+
}
|
|
56727
|
+
if (!BUILT_INS[target] && !target.startsWith("@") && !scope.has(target)) {
|
|
56728
|
+
throw new InterpolationError(
|
|
56729
|
+
`Variable "${target}", which {{${name}}} names, is not defined in this environment.`,
|
|
56730
|
+
target
|
|
56731
|
+
);
|
|
56732
|
+
}
|
|
56733
|
+
return resolveName(target, scope, through);
|
|
56734
|
+
}
|
|
56595
56735
|
|
|
56596
56736
|
// ../../packages/core/src/vars/resolve.ts
|
|
56597
56737
|
import fs7 from "node:fs/promises";
|
|
@@ -56623,9 +56763,14 @@ async function buildScope(context) {
|
|
|
56623
56763
|
if (context.environmentName) {
|
|
56624
56764
|
layers.push(...await environmentLayers(context, context.environmentName));
|
|
56625
56765
|
}
|
|
56766
|
+
if (context.run) layers.push({ source: "run", vars: Object.fromEntries(context.run.values) });
|
|
56626
56767
|
const row = context.dataRow !== void 0 ? context.dataRow : await firstRow(context.collectionPath);
|
|
56627
56768
|
if (row) layers.push(row);
|
|
56628
56769
|
const scope = new VariableScope(layers, context.env ?? process.env);
|
|
56770
|
+
if (context.run) {
|
|
56771
|
+
scope.runValues = context.run.values;
|
|
56772
|
+
scope.runWide = context.run.wide === true;
|
|
56773
|
+
}
|
|
56629
56774
|
scope.flags = context.flags !== void 0 ? context.flags : context.environmentName ? (await environmentFlags(root, context.environmentName, context.environments)).values : null;
|
|
56630
56775
|
return scope;
|
|
56631
56776
|
}
|
|
@@ -57130,7 +57275,11 @@ async function prepareRequest(written, body, scope, root) {
|
|
|
57130
57275
|
};
|
|
57131
57276
|
}
|
|
57132
57277
|
return {
|
|
57133
|
-
request: {
|
|
57278
|
+
request: {
|
|
57279
|
+
...request,
|
|
57280
|
+
// In a JSON body a null is JSON's own, not a gap that leaves it malformed.
|
|
57281
|
+
body: written.body === null ? null : body?.json !== void 0 ? interpolateToString(written.body, scope, { nullAs: "null" }) : resolve(written.body)
|
|
57282
|
+
},
|
|
57134
57283
|
payload: null
|
|
57135
57284
|
};
|
|
57136
57285
|
}
|
|
@@ -57160,9 +57309,20 @@ async function readBodyFile(root, path12, what) {
|
|
|
57160
57309
|
|
|
57161
57310
|
// ../../packages/core/src/run/runRequest.ts
|
|
57162
57311
|
import assert3 from "node:assert/strict";
|
|
57163
|
-
import
|
|
57312
|
+
import path9 from "node:path";
|
|
57164
57313
|
|
|
57165
57314
|
// ../../packages/core/src/assert/evaluate.ts
|
|
57315
|
+
function locate(root, segments, nullAlong) {
|
|
57316
|
+
const located = resolvePath(root, segments);
|
|
57317
|
+
if (located.length > 0 || !nullAlong || segments.some((s) => s.kind === "each")) return located;
|
|
57318
|
+
for (let end = 1; end < segments.length; end++) {
|
|
57319
|
+
const [reached] = resolvePath(root, segments.slice(0, end));
|
|
57320
|
+
if (!reached) return [];
|
|
57321
|
+
if (reached.value === null) return [reached];
|
|
57322
|
+
}
|
|
57323
|
+
return [];
|
|
57324
|
+
}
|
|
57325
|
+
var isNullCheck = (wanted) => wanted.equals === null && Object.keys(wanted).length === 1;
|
|
57166
57326
|
function checkScalarTarget(target, label, wanted, found, context) {
|
|
57167
57327
|
const base2 = { target, expected: describe3(wanted) };
|
|
57168
57328
|
const name = `${label} ${describe3(wanted, { brief: true })}`;
|
|
@@ -57207,7 +57367,7 @@ function checkBody(path12, segments, wanted, located, context) {
|
|
|
57207
57367
|
const covered = [];
|
|
57208
57368
|
let failure2 = null;
|
|
57209
57369
|
for (const { path: at, value } of located) {
|
|
57210
|
-
const check2 = checkBodyValue(wanted, value, context);
|
|
57370
|
+
const check2 = checkBodyValue(wanted, value, context, context.claimed?.(formatPath(at)));
|
|
57211
57371
|
covered.push(...check2.covered.map((c) => ({ ...c, pattern: [...at, ...c.pattern] })));
|
|
57212
57372
|
if (check2.failure && !failure2) {
|
|
57213
57373
|
failure2 = fanOut ? `At ${formatPath(at)}: ${check2.failure}` : check2.failure;
|
|
@@ -57219,7 +57379,7 @@ function checkBody(path12, segments, wanted, located, context) {
|
|
|
57219
57379
|
covered
|
|
57220
57380
|
};
|
|
57221
57381
|
}
|
|
57222
|
-
function checkBodyValue(wanted, value, context) {
|
|
57382
|
+
function checkBodyValue(wanted, value, context, claimed = /* @__PURE__ */ new Set()) {
|
|
57223
57383
|
const failures = [];
|
|
57224
57384
|
let whole = !(value !== null && typeof value === "object");
|
|
57225
57385
|
const covered = [];
|
|
@@ -57258,7 +57418,7 @@ function checkBodyValue(wanted, value, context) {
|
|
|
57258
57418
|
const used = /* @__PURE__ */ new Set();
|
|
57259
57419
|
for (const expected of wanted.unordered) {
|
|
57260
57420
|
const matches = value.map((item, index) => ({ index, match: matchItem(item, expected, context) })).filter((candidate) => candidate.match !== null);
|
|
57261
|
-
const chosen = matches.find((m) => !used.has(m.index)) ?? matches[0];
|
|
57421
|
+
const chosen = matches.find((m) => !used.has(m.index) && !claimed.has(m.index)) ?? matches.find((m) => !used.has(m.index)) ?? matches[0];
|
|
57262
57422
|
if (!chosen) {
|
|
57263
57423
|
missing.push(expected);
|
|
57264
57424
|
continue;
|
|
@@ -57269,6 +57429,7 @@ function checkBodyValue(wanted, value, context) {
|
|
|
57269
57429
|
covered.push({ pattern: [{ kind: "index", index: chosen.index }, ...c.pattern] });
|
|
57270
57430
|
}
|
|
57271
57431
|
}
|
|
57432
|
+
for (const index of used) claimed.add(index);
|
|
57272
57433
|
if (missing.length > 0)
|
|
57273
57434
|
failures.push(`Missing from the array: ${missing.map(show).join(", ")}`);
|
|
57274
57435
|
}
|
|
@@ -57382,7 +57543,11 @@ function matchItem(item, expected, context) {
|
|
|
57382
57543
|
const captures = [];
|
|
57383
57544
|
for (const [key, want] of entries2) {
|
|
57384
57545
|
const segments = parsePath(key);
|
|
57385
|
-
const located =
|
|
57546
|
+
const located = locate(
|
|
57547
|
+
item,
|
|
57548
|
+
segments,
|
|
57549
|
+
isMatcherObject(want) ? isNullCheck(want) : want === null
|
|
57550
|
+
);
|
|
57386
57551
|
if (isMatcherObject(want)) {
|
|
57387
57552
|
if (want.absent) {
|
|
57388
57553
|
if (located.length > 0) return null;
|
|
@@ -57538,6 +57703,10 @@ var CheckSession = class {
|
|
|
57538
57703
|
/** While true, checks recorded are defaults: an endpoint base's (SPEC.md §2.6). */
|
|
57539
57704
|
recordingDefaults = false;
|
|
57540
57705
|
defaults = /* @__PURE__ */ new Set();
|
|
57706
|
+
/** Checks whose meaning waits on strict validation: what each would be with it on. */
|
|
57707
|
+
byStrictness = [];
|
|
57708
|
+
/** Items of each array unordered checks matched, by the array's path. */
|
|
57709
|
+
claims = /* @__PURE__ */ new Map();
|
|
57541
57710
|
/** Turn strict validation on or off. The last word wins. */
|
|
57542
57711
|
setStrict(strict) {
|
|
57543
57712
|
this.strict = strict;
|
|
@@ -57553,6 +57722,7 @@ var CheckSession = class {
|
|
|
57553
57722
|
if (properties.length === 0) return;
|
|
57554
57723
|
this.sortKeys = [...properties, ...this.sortKeys];
|
|
57555
57724
|
this.sorted = void 0;
|
|
57725
|
+
this.claims.clear();
|
|
57556
57726
|
}
|
|
57557
57727
|
status(matcher) {
|
|
57558
57728
|
return this.record(
|
|
@@ -57571,10 +57741,36 @@ var CheckSession = class {
|
|
|
57571
57741
|
const body = this.body_();
|
|
57572
57742
|
if (body === BODY_UNAVAILABLE) return null;
|
|
57573
57743
|
const segments = parsePath(path12);
|
|
57574
|
-
const
|
|
57744
|
+
const located = locate(body, segments, isNullCheck(matcher));
|
|
57745
|
+
const result = checkBody(path12, segments, matcher, located, {
|
|
57746
|
+
...this.context,
|
|
57747
|
+
claimed: (arrayPath) => this.claimsOn(arrayPath)
|
|
57748
|
+
});
|
|
57575
57749
|
this.covered.push(...result.covered);
|
|
57576
57750
|
return this.record(result.assertion);
|
|
57577
57751
|
}
|
|
57752
|
+
/**
|
|
57753
|
+
* A body check that means one thing under strict validation and another
|
|
57754
|
+
* without, as xtest's lone `notThisExpectedValue` entry did (SPEC.md §3).
|
|
57755
|
+
* Both are checked now, against the body as it is now, and `finish()` keeps
|
|
57756
|
+
* the one the step's last word on strict validation asks for.
|
|
57757
|
+
*/
|
|
57758
|
+
bodyByStrictness(path12, matchers) {
|
|
57759
|
+
const body = this.body_();
|
|
57760
|
+
if (body === BODY_UNAVAILABLE) return null;
|
|
57761
|
+
const segments = parsePath(path12);
|
|
57762
|
+
const located = locate(body, segments, false);
|
|
57763
|
+
const strict = checkBody(path12, segments, matchers.strict, located, this.context);
|
|
57764
|
+
const lenient = checkBody(path12, segments, matchers.lenient, located, this.context);
|
|
57765
|
+
const assertion = this.record(lenient.assertion);
|
|
57766
|
+
this.byStrictness.push({ assertion, strict });
|
|
57767
|
+
return assertion;
|
|
57768
|
+
}
|
|
57769
|
+
claimsOn(arrayPath) {
|
|
57770
|
+
let claimed = this.claims.get(arrayPath);
|
|
57771
|
+
if (!claimed) this.claims.set(arrayPath, claimed = /* @__PURE__ */ new Set());
|
|
57772
|
+
return claimed;
|
|
57773
|
+
}
|
|
57578
57774
|
/** Account for a path under strict validation without asserting anything. */
|
|
57579
57775
|
ignore(path12) {
|
|
57580
57776
|
this.covered.push({ pattern: parsePath(path12) });
|
|
@@ -57614,9 +57810,17 @@ var CheckSession = class {
|
|
|
57614
57810
|
}
|
|
57615
57811
|
}
|
|
57616
57812
|
finish() {
|
|
57813
|
+
if (this.strict) {
|
|
57814
|
+
for (const { assertion, strict } of this.byStrictness) {
|
|
57815
|
+
for (const key of Object.keys(assertion)) delete assertion[key];
|
|
57816
|
+
Object.assign(assertion, strict.assertion);
|
|
57817
|
+
this.covered.push(...strict.covered);
|
|
57818
|
+
}
|
|
57819
|
+
}
|
|
57617
57820
|
this.replaceDefaults();
|
|
57618
57821
|
if (this.strict) {
|
|
57619
|
-
const
|
|
57822
|
+
const readable = !["binary", "html"].includes(this.context.response.bodyKind);
|
|
57823
|
+
const body = readable ? this.body_() : {};
|
|
57620
57824
|
if (body !== BODY_UNAVAILABLE) {
|
|
57621
57825
|
const unasserted = findUnasserted(body, this.covered);
|
|
57622
57826
|
this.assertions.push({
|
|
@@ -57781,13 +57985,30 @@ function variableName(value) {
|
|
|
57781
57985
|
}
|
|
57782
57986
|
return value;
|
|
57783
57987
|
}
|
|
57988
|
+
function pathOf2(path12, what) {
|
|
57989
|
+
if (typeof path12 === "string") return path12;
|
|
57990
|
+
if (!Array.isArray(path12)) {
|
|
57991
|
+
throw new GtaUsageError(
|
|
57992
|
+
`${what} is a string, such as "user.name", or a list of keys, such as ["user", "name"]; got ${show(path12)}`
|
|
57993
|
+
);
|
|
57994
|
+
}
|
|
57995
|
+
if (path12.length === 0) throw new GtaUsageError(`${what} is an empty list; it needs a key`);
|
|
57996
|
+
return formatPath(
|
|
57997
|
+
path12.map((key) => {
|
|
57998
|
+
if (typeof key === "string") return { kind: "key", key };
|
|
57999
|
+
if (typeof key === "number") return { kind: "key", key: String(key) };
|
|
58000
|
+
throw new GtaUsageError(`${what}'s keys are strings or numbers; got ${show(key)}`);
|
|
58001
|
+
})
|
|
58002
|
+
);
|
|
58003
|
+
}
|
|
57784
58004
|
var isValidationEntry = (value) => value !== null && typeof value === "object" && "pathToProperty" in value;
|
|
57785
58005
|
function itemFrom(list, valueKey) {
|
|
57786
58006
|
const item = {};
|
|
57787
58007
|
for (const entry of list) {
|
|
57788
|
-
const path12 = entry.pathToProperty;
|
|
58008
|
+
const path12 = pathOf2(entry.pathToProperty, "pathToProperty");
|
|
57789
58009
|
if (valueKey === "compareValue") {
|
|
57790
|
-
|
|
58010
|
+
const value = entry.compareValue;
|
|
58011
|
+
item[path12] = isRegExp(value) ? { matches: value } : value;
|
|
57791
58012
|
continue;
|
|
57792
58013
|
}
|
|
57793
58014
|
const matcher = xtestMatcher(valueKey in entry, entry[valueKey], entry.specialHandling);
|
|
@@ -57795,12 +58016,22 @@ function itemFrom(list, valueKey) {
|
|
|
57795
58016
|
}
|
|
57796
58017
|
return item;
|
|
57797
58018
|
}
|
|
58019
|
+
function loneNotThisValue(list) {
|
|
58020
|
+
const [entry] = list;
|
|
58021
|
+
if (list.length !== 1 || !isValidationEntry(entry)) return null;
|
|
58022
|
+
if (entry.specialHandling !== "notThisExpectedValue" || !("expectedValue" in entry)) return null;
|
|
58023
|
+
const value = entry.expectedValue;
|
|
58024
|
+
const path12 = pathOf2(entry.pathToProperty, "pathToProperty");
|
|
58025
|
+
return { unorderedNot: [{ [path12]: isRegExp(value) ? { matches: value } : value }] };
|
|
58026
|
+
}
|
|
57798
58027
|
function toList(validationList) {
|
|
57799
58028
|
if (!Array.isArray(validationList)) {
|
|
57800
58029
|
throw new GtaUsageError(`validationList must be an array, got ${show(validationList)}`);
|
|
57801
58030
|
}
|
|
57802
58031
|
return validationList;
|
|
57803
58032
|
}
|
|
58033
|
+
var newStepControl = () => ({ skip: null, rest: null });
|
|
58034
|
+
var reasonOf = (reason, fallback) => reason === void 0 || reason === null || String(reason).trim() === "" ? fallback : String(reason);
|
|
57804
58035
|
function guarded(session, label, fn) {
|
|
57805
58036
|
return (...args) => {
|
|
57806
58037
|
try {
|
|
@@ -57811,11 +58042,26 @@ function guarded(session, label, fn) {
|
|
|
57811
58042
|
}
|
|
57812
58043
|
};
|
|
57813
58044
|
}
|
|
57814
|
-
function testsGta({
|
|
57815
|
-
|
|
58045
|
+
function testsGta({
|
|
58046
|
+
session,
|
|
58047
|
+
scope,
|
|
58048
|
+
pending,
|
|
58049
|
+
warn = () => {
|
|
58050
|
+
},
|
|
58051
|
+
control = newStepControl()
|
|
58052
|
+
}) {
|
|
57816
58053
|
const shared = sharedGta(scope);
|
|
57817
58054
|
return {
|
|
57818
58055
|
...shared,
|
|
58056
|
+
skip() {
|
|
58057
|
+
throw new Error(
|
|
58058
|
+
"gta.skip() stops a request before it is sent, so it belongs in before.script; gta.skipRest() skips the steps after this one"
|
|
58059
|
+
);
|
|
58060
|
+
},
|
|
58061
|
+
/** Run none of the steps after this one in this row; the next row, and teardown, still run. */
|
|
58062
|
+
skipRest(reason) {
|
|
58063
|
+
control.rest ??= reasonOf(reason, "gta.skipRest() in an earlier step");
|
|
58064
|
+
},
|
|
57819
58065
|
expectResponseStatusCodeToBe: guarded(
|
|
57820
58066
|
session,
|
|
57821
58067
|
"expectResponseStatusCodeToBe",
|
|
@@ -57837,39 +58083,62 @@ function testsGta({ session, scope, pending, warn = () => {
|
|
|
57837
58083
|
"expectResponseBodyToHaveProperty",
|
|
57838
58084
|
(...args) => {
|
|
57839
58085
|
const [path12, value, specialHandling] = args;
|
|
57840
|
-
session.body(
|
|
58086
|
+
session.body(
|
|
58087
|
+
pathOf2(path12, "jsonPathToProperty"),
|
|
58088
|
+
xtestMatcher(args.length > 1, value, specialHandling)
|
|
58089
|
+
);
|
|
57841
58090
|
}
|
|
57842
58091
|
),
|
|
57843
58092
|
expectResponseBodyToHaveUnorderedArray: guarded(
|
|
57844
58093
|
session,
|
|
57845
58094
|
"expectResponseBodyToHaveUnorderedArray",
|
|
57846
58095
|
(jsonPathToArray, validationList) => {
|
|
58096
|
+
const path12 = pathOf2(jsonPathToArray, "jsonPathToArray");
|
|
57847
58097
|
const list = toList(validationList);
|
|
57848
58098
|
const unordered = list.every(isValidationEntry) ? [itemFrom(list, "expectedValue")] : list;
|
|
57849
|
-
|
|
58099
|
+
const lone = loneNotThisValue(list);
|
|
58100
|
+
if (lone) session.bodyByStrictness(path12, { strict: { unordered }, lenient: lone });
|
|
58101
|
+
else session.body(path12, { unordered });
|
|
57850
58102
|
}
|
|
57851
58103
|
),
|
|
57852
58104
|
expectResponseBodyToHaveUnorderedArrayNotThisItem: guarded(
|
|
57853
58105
|
session,
|
|
57854
58106
|
"expectResponseBodyToHaveUnorderedArrayNotThisItem",
|
|
57855
58107
|
(jsonPathToArray, validationList) => {
|
|
58108
|
+
const path12 = pathOf2(jsonPathToArray, "jsonPathToArray");
|
|
57856
58109
|
const list = toList(validationList);
|
|
57857
58110
|
const unorderedNot = list.every(isValidationEntry) ? [itemFrom(list, "compareValue")] : list;
|
|
57858
|
-
session.body(
|
|
58111
|
+
session.body(path12, { unorderedNot });
|
|
58112
|
+
}
|
|
58113
|
+
),
|
|
58114
|
+
ignoreResponseBodyProperty: guarded(
|
|
58115
|
+
session,
|
|
58116
|
+
"ignoreResponseBodyProperty",
|
|
58117
|
+
(jsonPathToProperty) => {
|
|
58118
|
+
session.ignore(pathOf2(jsonPathToProperty, "jsonPathToProperty"));
|
|
58119
|
+
}
|
|
58120
|
+
),
|
|
58121
|
+
ignoreResponseBodyArrayObjectProperty: guarded(
|
|
58122
|
+
session,
|
|
58123
|
+
"ignoreResponseBodyArrayObjectProperty",
|
|
58124
|
+
(jsonPathToArray, jsonPathOfObjectProperty) => {
|
|
58125
|
+
const array2 = parsePath(pathOf2(jsonPathToArray, "jsonPathToArray"));
|
|
58126
|
+
const property = parsePath(pathOf2(jsonPathOfObjectProperty, "jsonPathOfObjectProperty"));
|
|
58127
|
+
session.ignore(formatPath([...array2, { kind: "each" }, ...property]));
|
|
57859
58128
|
}
|
|
57860
58129
|
),
|
|
57861
|
-
ignoreResponseBodyProperty(jsonPathToProperty) {
|
|
57862
|
-
session.ignore(jsonPathToProperty);
|
|
57863
|
-
},
|
|
57864
|
-
ignoreResponseBodyArrayObjectProperty(jsonPathToArray, jsonPathOfObjectProperty) {
|
|
57865
|
-
session.ignore(`${jsonPathToArray}[].${jsonPathOfObjectProperty}`);
|
|
57866
|
-
},
|
|
57867
58130
|
sortResponseBodyArrays(propertyName) {
|
|
57868
|
-
|
|
58131
|
+
const named = typeof propertyName === "string" && propertyName !== "" || Array.isArray(propertyName) && propertyName.length > 0;
|
|
58132
|
+
if (!named) {
|
|
57869
58133
|
warn("gta.sortResponseBodyArrays() needs a property name to sort by; nothing was sorted");
|
|
57870
58134
|
return;
|
|
57871
58135
|
}
|
|
57872
|
-
|
|
58136
|
+
try {
|
|
58137
|
+
session.sortBy([pathOf2(propertyName, "propertyName")]);
|
|
58138
|
+
} catch (cause) {
|
|
58139
|
+
if (!(cause instanceof GtaUsageError)) throw cause;
|
|
58140
|
+
warn(`gta.sortResponseBodyArrays(): ${cause.message}; nothing was sorted`);
|
|
58141
|
+
}
|
|
57873
58142
|
},
|
|
57874
58143
|
/** Strict validation for this step. Replaces `startXTest`'s second argument. */
|
|
57875
58144
|
useStrictValidation(enabled = true) {
|
|
@@ -57908,12 +58177,25 @@ function testsGta({ session, scope, pending, warn = () => {
|
|
|
57908
58177
|
}
|
|
57909
58178
|
};
|
|
57910
58179
|
}
|
|
57911
|
-
function preRequestGta(scope) {
|
|
58180
|
+
function preRequestGta(scope, control = newStepControl()) {
|
|
57912
58181
|
const notHere = (name) => () => {
|
|
57913
58182
|
throw new Error(`gta.${name}() checks a response, so it belongs in tests, not before.script`);
|
|
57914
58183
|
};
|
|
57915
58184
|
return {
|
|
57916
58185
|
...sharedGta(scope),
|
|
58186
|
+
/**
|
|
58187
|
+
* Send nothing for this step, and report it skipped with the reason. The
|
|
58188
|
+
* script runs to its end; no later `before.script` does.
|
|
58189
|
+
*/
|
|
58190
|
+
skip(reason) {
|
|
58191
|
+
control.skip ??= reasonOf(reason, "gta.skip() in before.script");
|
|
58192
|
+
},
|
|
58193
|
+
/** `skip`, and run none of the steps after this one in this row either. */
|
|
58194
|
+
skipRest(reason) {
|
|
58195
|
+
const given = reasonOf(reason, "gta.skipRest() in before.script");
|
|
58196
|
+
control.skip ??= given;
|
|
58197
|
+
control.rest ??= given;
|
|
58198
|
+
},
|
|
57917
58199
|
expectResponseStatusCodeToBe: notHere("expectResponseStatusCodeToBe"),
|
|
57918
58200
|
expectResponseToHaveHeader: notHere("expectResponseToHaveHeader"),
|
|
57919
58201
|
expectResponseBodyToHaveProperty: notHere("expectResponseBodyToHaveProperty"),
|
|
@@ -57926,9 +58208,18 @@ function sharedGta(scope) {
|
|
|
57926
58208
|
get(name) {
|
|
57927
58209
|
return scope.get(name);
|
|
57928
58210
|
},
|
|
57929
|
-
/**
|
|
57930
|
-
|
|
57931
|
-
|
|
58211
|
+
/**
|
|
58212
|
+
* Set a variable for the rest of this run, like a capture. With
|
|
58213
|
+
* `{ scope: 'run' }` it lasts past this data row too, into every row after
|
|
58214
|
+
* it and teardown (SPEC.md §2.10).
|
|
58215
|
+
*/
|
|
58216
|
+
set(name, value, options) {
|
|
58217
|
+
const lasting = options?.scope;
|
|
58218
|
+
if (lasting !== void 0 && lasting !== "run") {
|
|
58219
|
+
throw new Error(`gta.set(): scope is 'run' or left out, got ${show(lasting)}`);
|
|
58220
|
+
}
|
|
58221
|
+
if (lasting === "run") scope.setForRun(String(name), toVarValue2(value), "script");
|
|
58222
|
+
else scope.set(String(name), toVarValue2(value), "script");
|
|
57932
58223
|
},
|
|
57933
58224
|
/**
|
|
57934
58225
|
* A feature flag's value in this run (SPEC.md §2.9) — to check something
|
|
@@ -58165,7 +58456,127 @@ function captureConsole(logs, phase) {
|
|
|
58165
58456
|
return { log: write("log"), info: write("info"), warn: write("warn"), error: write("error") };
|
|
58166
58457
|
}
|
|
58167
58458
|
|
|
58459
|
+
// ../../packages/core/src/run/plan.ts
|
|
58460
|
+
import path8 from "node:path";
|
|
58461
|
+
async function planSteps(collection, collectionPath) {
|
|
58462
|
+
const planned = [];
|
|
58463
|
+
let project;
|
|
58464
|
+
for (const [index, step] of collection.steps.entries()) {
|
|
58465
|
+
if (!isUseStep(step)) {
|
|
58466
|
+
planned.push({ kind: "request", index, step });
|
|
58467
|
+
continue;
|
|
58468
|
+
}
|
|
58469
|
+
try {
|
|
58470
|
+
if (!collectionPath)
|
|
58471
|
+
throw new Error(`use: ${step.use} \u2014 save the collection in a project first`);
|
|
58472
|
+
project ??= await projectOf(collectionPath);
|
|
58473
|
+
const set2 = await resolveRequestSet(project.root, project.global, step.use);
|
|
58474
|
+
checkWith(step.use, set2.doc, step.with ?? {});
|
|
58475
|
+
if (set2.doc.steps.length === 0) throw new Error(`use: ${step.use} \u2014 the set has no steps`);
|
|
58476
|
+
const planSet = {
|
|
58477
|
+
name: step.use,
|
|
58478
|
+
useName: step.name?.trim() ? step.name : void 0,
|
|
58479
|
+
path: set2.path,
|
|
58480
|
+
doc: set2.doc,
|
|
58481
|
+
with: step.with ?? {},
|
|
58482
|
+
useTests: step.tests
|
|
58483
|
+
};
|
|
58484
|
+
set2.doc.steps.forEach((child, number4) => {
|
|
58485
|
+
planned.push({ kind: "request", index, step: child, set: planSet, child: number4 });
|
|
58486
|
+
});
|
|
58487
|
+
} catch (cause) {
|
|
58488
|
+
planned.push({
|
|
58489
|
+
kind: "broken",
|
|
58490
|
+
index,
|
|
58491
|
+
step,
|
|
58492
|
+
error: { phase: "use", message: cause instanceof Error ? cause.message : String(cause) }
|
|
58493
|
+
});
|
|
58494
|
+
}
|
|
58495
|
+
}
|
|
58496
|
+
return planned;
|
|
58497
|
+
}
|
|
58498
|
+
async function projectOf(collectionPath) {
|
|
58499
|
+
const root = projectRootOf(collectionPath) ?? path8.dirname(collectionPath);
|
|
58500
|
+
const { global } = await readProject(root);
|
|
58501
|
+
return { root, global };
|
|
58502
|
+
}
|
|
58503
|
+
function checkWith(name, set2, given) {
|
|
58504
|
+
const params = set2.params ?? {};
|
|
58505
|
+
const unknown2 = Object.keys(given).filter((key) => !(key in params));
|
|
58506
|
+
if (unknown2.length > 0) {
|
|
58507
|
+
const takes = Object.keys(params).join(", ") || "none";
|
|
58508
|
+
throw new Error(`use: ${name} \u2014 it takes no ${unknown2.join(", ")} (it takes: ${takes})`);
|
|
58509
|
+
}
|
|
58510
|
+
const missing = Object.entries(params).filter(([key, spec]) => readParam(spec).required && !(key in given)).map(([key]) => key);
|
|
58511
|
+
if (missing.length > 0) {
|
|
58512
|
+
throw new Error(`use: ${name} \u2014 needs ${missing.join(", ")} in with:`);
|
|
58513
|
+
}
|
|
58514
|
+
}
|
|
58515
|
+
function resolveParams(specs, given, scope) {
|
|
58516
|
+
const params = {};
|
|
58517
|
+
const defaults = /* @__PURE__ */ new Map();
|
|
58518
|
+
for (const [key, spec] of Object.entries(specs ?? {})) {
|
|
58519
|
+
const value = given[key];
|
|
58520
|
+
if (value !== void 0)
|
|
58521
|
+
params[key] = typeof value === "string" ? interpolate(value, scope) : value;
|
|
58522
|
+
else {
|
|
58523
|
+
const fallback = readParam(spec).default;
|
|
58524
|
+
if (fallback !== void 0) defaults.set(key, fallback);
|
|
58525
|
+
}
|
|
58526
|
+
}
|
|
58527
|
+
const resolving = [];
|
|
58528
|
+
const resolve = (key) => {
|
|
58529
|
+
const fallback = defaults.get(key);
|
|
58530
|
+
if (fallback === void 0 || Object.hasOwn(params, key)) return;
|
|
58531
|
+
if (resolving.includes(key)) {
|
|
58532
|
+
const chain = [...resolving, key].map((name) => `params.${name}`);
|
|
58533
|
+
throw new InterpolationError(
|
|
58534
|
+
`params.${key} refers to itself: ${chain.join(" -> ")}`,
|
|
58535
|
+
`params.${key}`
|
|
58536
|
+
);
|
|
58537
|
+
}
|
|
58538
|
+
if (typeof fallback !== "string") {
|
|
58539
|
+
params[key] = fallback;
|
|
58540
|
+
return;
|
|
58541
|
+
}
|
|
58542
|
+
resolving.push(key);
|
|
58543
|
+
for (const [, name] of fallback.matchAll(PARAM_REFERENCE)) resolve(name);
|
|
58544
|
+
resolving.pop();
|
|
58545
|
+
try {
|
|
58546
|
+
params[key] = interpolate(fallback, new ParamsScope(scope, params));
|
|
58547
|
+
} catch (cause) {
|
|
58548
|
+
if (!(cause instanceof InterpolationError)) throw cause;
|
|
58549
|
+
throw new InterpolationError(`params.${key}'s default: ${cause.message}`, cause.variable);
|
|
58550
|
+
}
|
|
58551
|
+
};
|
|
58552
|
+
for (const key of defaults.keys()) resolve(key);
|
|
58553
|
+
return params;
|
|
58554
|
+
}
|
|
58555
|
+
var PARAM_REFERENCE = /\{\{\s*params\.([^{}\s]+)\s*\}\}/g;
|
|
58556
|
+
async function referenceProblems(collection, collectionPath) {
|
|
58557
|
+
const problems = [];
|
|
58558
|
+
if (collection.extends) {
|
|
58559
|
+
try {
|
|
58560
|
+
const { root, global } = await projectOf(collectionPath);
|
|
58561
|
+
await resolveBase(root, global, collection.extends);
|
|
58562
|
+
} catch (cause) {
|
|
58563
|
+
problems.push({ step: null, message: cause.message });
|
|
58564
|
+
}
|
|
58565
|
+
}
|
|
58566
|
+
for (const list of STEP_LISTS) {
|
|
58567
|
+
const steps = collection[list];
|
|
58568
|
+
if (!steps) continue;
|
|
58569
|
+
for (const planned of await planSteps({ ...collection, steps }, collectionPath)) {
|
|
58570
|
+
if (planned.kind !== "broken") continue;
|
|
58571
|
+
const stage = list === "steps" ? {} : { stage: list };
|
|
58572
|
+
problems.push({ step: planned.index, ...stage, message: planned.error.message });
|
|
58573
|
+
}
|
|
58574
|
+
}
|
|
58575
|
+
return problems;
|
|
58576
|
+
}
|
|
58577
|
+
|
|
58168
58578
|
// ../../packages/core/src/run/runRequest.ts
|
|
58579
|
+
var itemText = (value) => value === null || ["string", "number", "boolean"].includes(typeof value) ? value : JSON.stringify(value) ?? String(value);
|
|
58169
58580
|
function folded(layers, collection) {
|
|
58170
58581
|
let headers;
|
|
58171
58582
|
let settings = {};
|
|
@@ -58205,16 +58616,9 @@ function endpointValues(endpoint, step, scope) {
|
|
|
58205
58616
|
}
|
|
58206
58617
|
return values;
|
|
58207
58618
|
}
|
|
58208
|
-
function defaultsOf(params) {
|
|
58209
|
-
const defaults = {};
|
|
58210
|
-
for (const [key, spec] of Object.entries(params)) {
|
|
58211
|
-
const value = readParam(spec).default;
|
|
58212
|
-
if (value !== void 0) defaults[key] = value;
|
|
58213
|
-
}
|
|
58214
|
-
return defaults;
|
|
58215
|
-
}
|
|
58216
58619
|
async function runRequest(input2) {
|
|
58217
|
-
const { step, itemPath = null, signal, context, scope: provided, set: set2 } = input2;
|
|
58620
|
+
const { step, itemPath = null, signal, context, scope: provided, set: set2, item: each } = input2;
|
|
58621
|
+
const control = input2.control ?? newStepControl();
|
|
58218
58622
|
if (isUseStep(step)) {
|
|
58219
58623
|
return {
|
|
58220
58624
|
item: { path: itemPath, name: stepLabel(step), seq: null },
|
|
@@ -58226,7 +58630,6 @@ async function runRequest(input2) {
|
|
|
58226
58630
|
durationMs: 0
|
|
58227
58631
|
};
|
|
58228
58632
|
}
|
|
58229
|
-
const params = set2?.params ?? input2.params ?? (input2.collection?.params ? defaultsOf(input2.collection.params) : void 0);
|
|
58230
58633
|
const library = context && needsLibrary(input2) ? await loadLibrary(context.collectionPath) : null;
|
|
58231
58634
|
const checks = input2.checks ?? library?.checks;
|
|
58232
58635
|
const endpoints = input2.endpoints ?? library?.endpoints ?? [];
|
|
@@ -58264,9 +58667,18 @@ async function runRequest(input2) {
|
|
|
58264
58667
|
const logs = [];
|
|
58265
58668
|
const withLogs = () => ({
|
|
58266
58669
|
...logs.length > 0 ? { logs } : {},
|
|
58267
|
-
...
|
|
58670
|
+
...each ? { forEach: { index: each.index, of: each.of, item: String(itemText(each.value)) } } : {},
|
|
58671
|
+
...set2 ? {
|
|
58672
|
+
use: {
|
|
58673
|
+
set: set2.name,
|
|
58674
|
+
...set2.useName ? { name: set2.useName } : {},
|
|
58675
|
+
child: set2.child,
|
|
58676
|
+
of: set2.of
|
|
58677
|
+
}
|
|
58678
|
+
} : {}
|
|
58268
58679
|
});
|
|
58269
|
-
const
|
|
58680
|
+
const marked = set2 ? set2.doc.steps.findIndex((s) => s.useTests === true) : -1;
|
|
58681
|
+
const useChecksThis = set2 ? set2.child === (marked >= 0 ? marked : set2.of - 1) : false;
|
|
58270
58682
|
const layers = [];
|
|
58271
58683
|
if (endpoint) {
|
|
58272
58684
|
layers.push({
|
|
@@ -58286,7 +58698,8 @@ async function runRequest(input2) {
|
|
|
58286
58698
|
layers.push({ script: "collection", label: "collection", ...partsOf2(input2.collection) });
|
|
58287
58699
|
if (set2) layers.push({ script: "set", label: "set", ...partsOf2(set2.doc) });
|
|
58288
58700
|
layers.push({ script: "step", label: "", before: step.before, tests: step.tests });
|
|
58289
|
-
if (
|
|
58701
|
+
if (useChecksThis && set2?.useTests)
|
|
58702
|
+
layers.push({ script: "use", label: "use", tests: set2.useTests });
|
|
58290
58703
|
const collection = folded(layers, input2.collection);
|
|
58291
58704
|
const failed = (error62) => ({
|
|
58292
58705
|
...failedBeforeSending(step, collection, item, error62, startedHr, logs),
|
|
@@ -58294,40 +58707,64 @@ async function runRequest(input2) {
|
|
|
58294
58707
|
});
|
|
58295
58708
|
let spec;
|
|
58296
58709
|
let scope;
|
|
58710
|
+
let params;
|
|
58297
58711
|
try {
|
|
58298
58712
|
const run = provided ?? (context ? await buildScope(context) : new VariableScope());
|
|
58299
|
-
|
|
58713
|
+
params = set2?.params ?? input2.params ?? (input2.collection?.params ? resolveParams(input2.collection.params, {}, run) : void 0);
|
|
58714
|
+
const own2 = params ? new ParamsScope(run, params) : run;
|
|
58715
|
+
scope = each ? new OverlayScope(own2, /* @__PURE__ */ new Map([["item", itemText(each.value)]]), "forEach") : own2;
|
|
58300
58716
|
} catch (cause) {
|
|
58301
58717
|
return failed(interpolateError(cause));
|
|
58302
58718
|
}
|
|
58303
58719
|
const shared = (adopt) => ({
|
|
58304
58720
|
assert: assert3,
|
|
58305
58721
|
...params ? { params: adopt(params) } : {},
|
|
58722
|
+
...each ? { item: adopt(each.value) } : {},
|
|
58306
58723
|
...endpoint ? { endpoint: adopt(endpointValues(endpoint, step, scope)) } : {}
|
|
58307
58724
|
});
|
|
58308
58725
|
const filenameOf = (layer, kind) => layer.label === "" ? kind : `${layer.label} ${kind}`;
|
|
58726
|
+
let edited = null;
|
|
58309
58727
|
for (const layer of layers) {
|
|
58310
58728
|
const before = layer.before;
|
|
58311
58729
|
const script = layer.script;
|
|
58312
58730
|
if (before?.script) {
|
|
58731
|
+
const shown = edited ?? toSentRequestSafely(step, collection);
|
|
58732
|
+
let view;
|
|
58313
58733
|
const error62 = await runScript(before.script, {
|
|
58314
58734
|
phase: "pre-request",
|
|
58315
58735
|
filename: filenameOf(layer, "before.script"),
|
|
58316
58736
|
logs,
|
|
58317
58737
|
...checks ? { checks } : {},
|
|
58318
|
-
globals: (adopt) =>
|
|
58319
|
-
|
|
58320
|
-
gta: preRequestGta(scope),
|
|
58321
|
-
|
|
58322
|
-
})
|
|
58738
|
+
globals: (adopt) => {
|
|
58739
|
+
view = requestView(shown, adopt);
|
|
58740
|
+
return { ...shared(adopt), gta: preRequestGta(scope, control), req: view };
|
|
58741
|
+
}
|
|
58323
58742
|
});
|
|
58324
58743
|
if (error62) return failed({ ...error62, script });
|
|
58744
|
+
try {
|
|
58745
|
+
edited = changedRequest(shown, view, step.body) ?? edited;
|
|
58746
|
+
} catch (cause) {
|
|
58747
|
+
return failed({ phase: "pre-request", message: cause.message, script });
|
|
58748
|
+
}
|
|
58749
|
+
if (control.skip !== null) {
|
|
58750
|
+
return {
|
|
58751
|
+
item,
|
|
58752
|
+
request: edited ?? toSentRequestSafely(step, collection),
|
|
58753
|
+
response: null,
|
|
58754
|
+
assertions: [],
|
|
58755
|
+
...withLogs(),
|
|
58756
|
+
error: null,
|
|
58757
|
+
status: "skipped",
|
|
58758
|
+
skipped: { reason: control.skip },
|
|
58759
|
+
durationMs: performance.now() - startedHr
|
|
58760
|
+
};
|
|
58761
|
+
}
|
|
58325
58762
|
}
|
|
58326
58763
|
}
|
|
58327
58764
|
let payload;
|
|
58328
58765
|
try {
|
|
58329
58766
|
const prepared = await prepareRequest(
|
|
58330
|
-
toSentRequest(step, collection),
|
|
58767
|
+
edited ?? toSentRequest(step, collection),
|
|
58331
58768
|
step.body,
|
|
58332
58769
|
scope,
|
|
58333
58770
|
filesRoot(set2?.path ?? context?.collectionPath ?? itemPath)
|
|
@@ -58382,6 +58819,7 @@ async function runRequest(input2) {
|
|
|
58382
58819
|
session,
|
|
58383
58820
|
scope,
|
|
58384
58821
|
pending,
|
|
58822
|
+
control,
|
|
58385
58823
|
warn: (message) => logs.push({ level: "warn", phase: "tests", message })
|
|
58386
58824
|
}),
|
|
58387
58825
|
req: requestView(outcome.request, adopt),
|
|
@@ -58411,8 +58849,36 @@ async function runRequest(input2) {
|
|
|
58411
58849
|
durationMs: performance.now() - startedHr
|
|
58412
58850
|
};
|
|
58413
58851
|
}
|
|
58852
|
+
function changedRequest(shown, view, body) {
|
|
58853
|
+
if (!view) return null;
|
|
58854
|
+
let next = null;
|
|
58855
|
+
if (view.body !== shown.body) {
|
|
58856
|
+
if (typeof view.body !== "string") {
|
|
58857
|
+
throw new Error(`req.body is the body's text; a script set it to ${typeof view.body}`);
|
|
58858
|
+
}
|
|
58859
|
+
const text = body?.json ?? body?.xml ?? body?.text ?? body?.graphql;
|
|
58860
|
+
if (text === void 0) {
|
|
58861
|
+
throw new Error(
|
|
58862
|
+
"req.body can be changed for a json, xml, text or graphql body; a form, multipart or file body is built from its parts, and a step with no body has none to change"
|
|
58863
|
+
);
|
|
58864
|
+
}
|
|
58865
|
+
next = { ...shown, body: view.body };
|
|
58866
|
+
}
|
|
58867
|
+
const was = Object.fromEntries(shown.headers.map((header) => [header.name, header.value]));
|
|
58868
|
+
if (JSON.stringify(view.headers) !== JSON.stringify(was)) {
|
|
58869
|
+
const headers = view.headers;
|
|
58870
|
+
if (headers === null || typeof headers !== "object" || Array.isArray(headers)) {
|
|
58871
|
+
throw new Error("req.headers is a map of header names to their values");
|
|
58872
|
+
}
|
|
58873
|
+
next = {
|
|
58874
|
+
...next ?? shown,
|
|
58875
|
+
headers: Object.entries(headers).filter(([, value]) => value !== void 0 && value !== null).map(([name, value]) => ({ name, value: String(value) }))
|
|
58876
|
+
};
|
|
58877
|
+
}
|
|
58878
|
+
return next;
|
|
58879
|
+
}
|
|
58414
58880
|
function filesRoot(file2) {
|
|
58415
|
-
return file2 ? projectRootOf(file2) ??
|
|
58881
|
+
return file2 ? projectRootOf(file2) ?? path9.dirname(file2) : null;
|
|
58416
58882
|
}
|
|
58417
58883
|
function interpolateError(cause) {
|
|
58418
58884
|
return {
|
|
@@ -58443,81 +58909,15 @@ function toSentRequestSafely(step, collection) {
|
|
|
58443
58909
|
|
|
58444
58910
|
// ../../packages/core/src/run/runCollection.ts
|
|
58445
58911
|
import path10 from "node:path";
|
|
58446
|
-
|
|
58447
|
-
// ../../packages/core/src/run/plan.ts
|
|
58448
|
-
import path9 from "node:path";
|
|
58449
|
-
async function planSteps(collection, collectionPath) {
|
|
58450
|
-
const planned = [];
|
|
58451
|
-
let project;
|
|
58452
|
-
for (const [index, step] of collection.steps.entries()) {
|
|
58453
|
-
if (!isUseStep(step)) {
|
|
58454
|
-
planned.push({ kind: "request", index, step });
|
|
58455
|
-
continue;
|
|
58456
|
-
}
|
|
58457
|
-
try {
|
|
58458
|
-
if (!collectionPath)
|
|
58459
|
-
throw new Error(`use: ${step.use} \u2014 save the collection in a project first`);
|
|
58460
|
-
project ??= await projectOf(collectionPath);
|
|
58461
|
-
const set2 = await resolveRequestSet(project.root, project.global, step.use);
|
|
58462
|
-
checkWith(step.use, set2.doc, step.with ?? {});
|
|
58463
|
-
if (set2.doc.steps.length === 0) throw new Error(`use: ${step.use} \u2014 the set has no steps`);
|
|
58464
|
-
const planSet = {
|
|
58465
|
-
name: step.use,
|
|
58466
|
-
path: set2.path,
|
|
58467
|
-
doc: set2.doc,
|
|
58468
|
-
with: step.with ?? {},
|
|
58469
|
-
useTests: step.tests
|
|
58470
|
-
};
|
|
58471
|
-
set2.doc.steps.forEach((child, number4) => {
|
|
58472
|
-
planned.push({ kind: "request", index, step: child, set: planSet, child: number4 });
|
|
58473
|
-
});
|
|
58474
|
-
} catch (cause) {
|
|
58475
|
-
planned.push({
|
|
58476
|
-
kind: "broken",
|
|
58477
|
-
index,
|
|
58478
|
-
step,
|
|
58479
|
-
error: { phase: "use", message: cause instanceof Error ? cause.message : String(cause) }
|
|
58480
|
-
});
|
|
58481
|
-
}
|
|
58482
|
-
}
|
|
58483
|
-
return planned;
|
|
58484
|
-
}
|
|
58485
|
-
async function projectOf(collectionPath) {
|
|
58486
|
-
const root = projectRootOf(collectionPath) ?? path9.dirname(collectionPath);
|
|
58487
|
-
const { global } = await readProject(root);
|
|
58488
|
-
return { root, global };
|
|
58489
|
-
}
|
|
58490
|
-
function checkWith(name, set2, given) {
|
|
58491
|
-
const params = set2.params ?? {};
|
|
58492
|
-
const unknown2 = Object.keys(given).filter((key) => !(key in params));
|
|
58493
|
-
if (unknown2.length > 0) {
|
|
58494
|
-
const takes = Object.keys(params).join(", ") || "none";
|
|
58495
|
-
throw new Error(`use: ${name} \u2014 it takes no ${unknown2.join(", ")} (it takes: ${takes})`);
|
|
58496
|
-
}
|
|
58497
|
-
const missing = Object.entries(params).filter(([key, spec]) => readParam(spec).required && !(key in given)).map(([key]) => key);
|
|
58498
|
-
if (missing.length > 0) {
|
|
58499
|
-
throw new Error(`use: ${name} \u2014 needs ${missing.join(", ")} in with:`);
|
|
58500
|
-
}
|
|
58501
|
-
}
|
|
58502
|
-
function resolveParams(set2, scope) {
|
|
58503
|
-
const params = {};
|
|
58504
|
-
for (const [key, spec] of Object.entries(set2.doc.params ?? {})) {
|
|
58505
|
-
const given = set2.with[key];
|
|
58506
|
-
if (given !== void 0)
|
|
58507
|
-
params[key] = typeof given === "string" ? interpolate(given, scope) : given;
|
|
58508
|
-
else {
|
|
58509
|
-
const fallback = readParam(spec).default;
|
|
58510
|
-
if (fallback !== void 0) params[key] = fallback;
|
|
58511
|
-
}
|
|
58512
|
-
}
|
|
58513
|
-
return params;
|
|
58514
|
-
}
|
|
58515
|
-
|
|
58516
|
-
// ../../packages/core/src/run/runCollection.ts
|
|
58517
58912
|
async function runCollection(options) {
|
|
58518
58913
|
const { collection, collectionPath = null, context, signal, onResult, bail = false } = options;
|
|
58914
|
+
const stage = options.stage ?? "steps";
|
|
58519
58915
|
const startedHr = performance.now();
|
|
58520
|
-
const scope = context ? await buildScope({
|
|
58916
|
+
const scope = context ? await buildScope({
|
|
58917
|
+
collectionExtends: collection.extends ?? null,
|
|
58918
|
+
...context,
|
|
58919
|
+
...options.run ? { run: options.run } : {}
|
|
58920
|
+
}) : withRun(new VariableScope(), options.run);
|
|
58521
58921
|
const library = collectionPath ? await loadLibrary(collectionPath) : null;
|
|
58522
58922
|
const checks = library?.checks ?? [];
|
|
58523
58923
|
const endpoints = library?.endpoints ?? [];
|
|
@@ -58532,50 +58932,88 @@ async function runCollection(options) {
|
|
|
58532
58932
|
baseError = { phase: "use", message: cause.message };
|
|
58533
58933
|
}
|
|
58534
58934
|
}
|
|
58935
|
+
const running = stage === "steps" ? collection : { ...collection, steps: collection[stage] ?? [] };
|
|
58535
58936
|
const own2 = collection.params ? selfAsSet(collection, collectionPath) : void 0;
|
|
58536
|
-
const plan = await planSteps(
|
|
58937
|
+
const plan = await planSteps(running, collectionPath);
|
|
58537
58938
|
const results = [];
|
|
58538
58939
|
let params = {};
|
|
58539
|
-
|
|
58940
|
+
let unstarted = null;
|
|
58941
|
+
let rest = null;
|
|
58942
|
+
let attempted = 0;
|
|
58943
|
+
const emit = (index, result) => {
|
|
58944
|
+
const marked = stage === "steps" ? result : { ...result, stage };
|
|
58945
|
+
results.push(marked);
|
|
58946
|
+
onResult?.(index, marked);
|
|
58947
|
+
};
|
|
58948
|
+
const failing = (result) => result.status === "fail" || result.status === "error";
|
|
58949
|
+
planning: for (const planned of plan) {
|
|
58540
58950
|
if (signal?.aborted) break;
|
|
58541
|
-
|
|
58951
|
+
attempted++;
|
|
58542
58952
|
if (baseError) {
|
|
58543
|
-
|
|
58544
|
-
|
|
58545
|
-
|
|
58546
|
-
}
|
|
58547
|
-
|
|
58548
|
-
|
|
58549
|
-
|
|
58550
|
-
|
|
58551
|
-
|
|
58552
|
-
|
|
58553
|
-
|
|
58554
|
-
|
|
58555
|
-
|
|
58556
|
-
|
|
58557
|
-
|
|
58558
|
-
|
|
58559
|
-
|
|
58560
|
-
|
|
58561
|
-
|
|
58562
|
-
|
|
58563
|
-
|
|
58564
|
-
|
|
58565
|
-
|
|
58566
|
-
|
|
58567
|
-
}
|
|
58953
|
+
emit(planned.index, brokenUse(planned.step, baseError, collectionPath));
|
|
58954
|
+
if (bail) break;
|
|
58955
|
+
continue;
|
|
58956
|
+
}
|
|
58957
|
+
if (planned.kind === "broken") {
|
|
58958
|
+
emit(planned.index, brokenUse(planned.step, planned.error, collectionPath));
|
|
58959
|
+
if (bail) break;
|
|
58960
|
+
continue;
|
|
58961
|
+
}
|
|
58962
|
+
const set2 = planned.set ? positionOf(planned.set, planned.child ?? 0) : void 0;
|
|
58963
|
+
const inSet = set2 ? { use: useOf(set2) } : {};
|
|
58964
|
+
if (rest !== null) {
|
|
58965
|
+
emit(planned.index, { ...skipped(planned.step, rest, collectionPath), ...inSet });
|
|
58966
|
+
continue;
|
|
58967
|
+
}
|
|
58968
|
+
if (set2 || own2) {
|
|
58969
|
+
const using = set2?.planned ?? own2;
|
|
58970
|
+
const starting = set2 ? set2.child === 0 : planned.index === 0;
|
|
58971
|
+
if (starting) {
|
|
58972
|
+
try {
|
|
58973
|
+
params = resolveParams(using.doc.params, using.with, scope);
|
|
58974
|
+
unstarted = null;
|
|
58975
|
+
} catch (cause) {
|
|
58976
|
+
params = {};
|
|
58977
|
+
unstarted = { phase: "use", message: `use: ${using.name} \u2014 ${cause.message}` };
|
|
58568
58978
|
}
|
|
58569
58979
|
}
|
|
58570
|
-
|
|
58571
|
-
|
|
58572
|
-
|
|
58573
|
-
|
|
58574
|
-
|
|
58575
|
-
|
|
58980
|
+
}
|
|
58981
|
+
const gate = flagGate(running, planned.index, planned.step, scope);
|
|
58982
|
+
if (gate) {
|
|
58983
|
+
const result = gate(collectionPath);
|
|
58984
|
+
emit(planned.index, result);
|
|
58985
|
+
if (bail && result.status === "error") break;
|
|
58986
|
+
continue;
|
|
58987
|
+
}
|
|
58988
|
+
if (unstarted && (set2 || own2)) {
|
|
58989
|
+
emit(planned.index, { ...brokenUse(planned.step, unstarted, collectionPath), ...inSet });
|
|
58990
|
+
if (bail) break;
|
|
58991
|
+
continue;
|
|
58992
|
+
}
|
|
58993
|
+
let items = [null];
|
|
58994
|
+
if (planned.step.forEach !== void 0) {
|
|
58995
|
+
try {
|
|
58996
|
+
items = forEachItems(planned.step.forEach, scope).map((value) => ({ value }));
|
|
58997
|
+
} catch (cause) {
|
|
58998
|
+
const error62 = { phase: "forEach", message: cause.message };
|
|
58999
|
+
emit(planned.index, { ...brokenUse(planned.step, error62, collectionPath), ...inSet });
|
|
59000
|
+
if (bail) break;
|
|
59001
|
+
continue;
|
|
59002
|
+
}
|
|
59003
|
+
if (items.length === 0) {
|
|
59004
|
+
const reason = `forEach: ${planned.step.forEach} is an empty list`;
|
|
59005
|
+
emit(planned.index, { ...skipped(planned.step, reason, collectionPath), ...inSet });
|
|
59006
|
+
continue;
|
|
59007
|
+
}
|
|
59008
|
+
}
|
|
59009
|
+
for (const [index, each] of items.entries()) {
|
|
59010
|
+
if (signal?.aborted) break planning;
|
|
59011
|
+
if (rest !== null) {
|
|
59012
|
+
emit(planned.index, { ...skipped(planned.step, rest, collectionPath), ...inSet });
|
|
58576
59013
|
continue;
|
|
58577
59014
|
}
|
|
58578
|
-
|
|
59015
|
+
const control = newStepControl();
|
|
59016
|
+
const result = await runRequest({
|
|
58579
59017
|
step: planned.step,
|
|
58580
59018
|
collection,
|
|
58581
59019
|
itemPath: collectionPath,
|
|
@@ -58583,10 +59021,13 @@ async function runCollection(options) {
|
|
|
58583
59021
|
checks,
|
|
58584
59022
|
endpoints,
|
|
58585
59023
|
base: base2,
|
|
59024
|
+
control,
|
|
58586
59025
|
...library ? { tls: library.tls } : {},
|
|
59026
|
+
...each ? { item: { value: each.value, index, of: items.length } } : {},
|
|
58587
59027
|
...set2 ? {
|
|
58588
59028
|
set: {
|
|
58589
59029
|
name: set2.planned.name,
|
|
59030
|
+
...set2.planned.useName ? { useName: set2.planned.useName } : {},
|
|
58590
59031
|
path: set2.planned.path,
|
|
58591
59032
|
doc: set2.planned.doc,
|
|
58592
59033
|
params,
|
|
@@ -58597,31 +59038,59 @@ async function runCollection(options) {
|
|
|
58597
59038
|
} : own2 ? { params } : {},
|
|
58598
59039
|
...signal ? { signal } : {}
|
|
58599
59040
|
});
|
|
59041
|
+
emit(planned.index, result);
|
|
59042
|
+
rest ??= control.rest;
|
|
59043
|
+
if (bail && failing(result)) break planning;
|
|
58600
59044
|
}
|
|
58601
|
-
results.push(result);
|
|
58602
|
-
onResult?.(planned.index, result);
|
|
58603
|
-
if (bail && result.status !== "pass") break;
|
|
58604
59045
|
}
|
|
59046
|
+
const notBegun = plan.length - attempted;
|
|
58605
59047
|
return {
|
|
58606
|
-
total:
|
|
59048
|
+
total: results.length + notBegun,
|
|
58607
59049
|
passed: results.filter((r) => r.status === "pass").length,
|
|
58608
59050
|
failed: results.filter((r) => r.status === "fail").length,
|
|
58609
59051
|
errored: results.filter((r) => r.status === "error").length,
|
|
58610
|
-
// A step a feature flag skipped, and one never
|
|
58611
|
-
skipped:
|
|
59052
|
+
// A step a feature flag or a script skipped, and one never begun, are all not run.
|
|
59053
|
+
skipped: notBegun + results.filter((r) => r.status === "skipped").length,
|
|
58612
59054
|
durationMs: performance.now() - startedHr,
|
|
58613
59055
|
results
|
|
58614
59056
|
};
|
|
58615
59057
|
}
|
|
59058
|
+
function withRun(scope, run) {
|
|
59059
|
+
if (!run) return scope;
|
|
59060
|
+
const built = new VariableScope([{ source: "run", vars: Object.fromEntries(run.values) }]);
|
|
59061
|
+
built.runValues = run.values;
|
|
59062
|
+
built.runWide = run.wide === true;
|
|
59063
|
+
return built;
|
|
59064
|
+
}
|
|
59065
|
+
function forEachItems(expression, scope) {
|
|
59066
|
+
const value = interpolate(expression, scope);
|
|
59067
|
+
if (typeof value === "string") {
|
|
59068
|
+
try {
|
|
59069
|
+
const parsed = JSON.parse(value);
|
|
59070
|
+
if (Array.isArray(parsed)) return parsed;
|
|
59071
|
+
} catch {
|
|
59072
|
+
}
|
|
59073
|
+
}
|
|
59074
|
+
throw new Error(
|
|
59075
|
+
`forEach: ${expression} is ${show(value)}, not a list; it needs a JSON array, such as ["a", "b"]`
|
|
59076
|
+
);
|
|
59077
|
+
}
|
|
58616
59078
|
function positionOf(planned, child) {
|
|
58617
59079
|
return { planned, child, of: planned.doc.steps.length };
|
|
58618
59080
|
}
|
|
59081
|
+
var useOf = (set2) => ({
|
|
59082
|
+
set: set2.planned.name,
|
|
59083
|
+
...set2.planned.useName ? { name: set2.planned.useName } : {},
|
|
59084
|
+
child: set2.child,
|
|
59085
|
+
of: set2.of
|
|
59086
|
+
});
|
|
58619
59087
|
function selfAsSet(collection, collectionPath) {
|
|
58620
59088
|
return {
|
|
58621
59089
|
name: collectionPath ? path10.basename(collectionPath, ".yml") : collection.id ?? "set",
|
|
58622
59090
|
path: collectionPath ?? "",
|
|
58623
59091
|
// Its own before, tests, headers and settings already apply as the collection's.
|
|
58624
59092
|
doc: { params: collection.params, steps: collection.steps },
|
|
59093
|
+
useName: void 0,
|
|
58625
59094
|
with: {},
|
|
58626
59095
|
useTests: void 0
|
|
58627
59096
|
};
|
|
@@ -58657,6 +59126,10 @@ function flagGate(collection, index, step, scope) {
|
|
|
58657
59126
|
return (collectionPath) => brokenUse(step, { phase: "flags", message: cause.message }, collectionPath);
|
|
58658
59127
|
}
|
|
58659
59128
|
}
|
|
59129
|
+
var skipped = (step, reason, collectionPath) => ({
|
|
59130
|
+
...notRun(step, collectionPath),
|
|
59131
|
+
skipped: { reason }
|
|
59132
|
+
});
|
|
58660
59133
|
var notRun = (step, collectionPath) => ({
|
|
58661
59134
|
item: { path: collectionPath, name: stepLabel(step), seq: null },
|
|
58662
59135
|
request: {
|
|
@@ -58672,6 +59145,62 @@ var notRun = (step, collectionPath) => ({
|
|
|
58672
59145
|
durationMs: 0
|
|
58673
59146
|
});
|
|
58674
59147
|
|
|
59148
|
+
// ../../packages/core/src/run/runSuite.ts
|
|
59149
|
+
async function runSuite(options) {
|
|
59150
|
+
const { collection, collectionPath = null, context, signal, bail = false, onResult } = options;
|
|
59151
|
+
const values = /* @__PURE__ */ new Map();
|
|
59152
|
+
const summaries = [];
|
|
59153
|
+
const once = (stage, stopEarly) => runCollection({
|
|
59154
|
+
collection,
|
|
59155
|
+
collectionPath,
|
|
59156
|
+
stage,
|
|
59157
|
+
run: { values, wide: true },
|
|
59158
|
+
...context ? { context: { ...context, dataRow: null } } : {},
|
|
59159
|
+
...signal ? { signal } : {},
|
|
59160
|
+
bail: stopEarly,
|
|
59161
|
+
onResult: (index, result) => onResult?.(index, result, void 0)
|
|
59162
|
+
});
|
|
59163
|
+
let stopped = false;
|
|
59164
|
+
if (collection.setup?.length) {
|
|
59165
|
+
const setup = await once("setup", bail);
|
|
59166
|
+
summaries.push(setup);
|
|
59167
|
+
stopped = setup.failed + setup.errored > 0;
|
|
59168
|
+
}
|
|
59169
|
+
const rows = options.rows?.length ? options.rows : null;
|
|
59170
|
+
const passes = rows ?? [null];
|
|
59171
|
+
let unrun = 0;
|
|
59172
|
+
for (const [i, row] of passes.entries()) {
|
|
59173
|
+
if (stopped || signal?.aborted) {
|
|
59174
|
+
unrun = (await planSteps(collection, collectionPath)).length * (passes.length - i);
|
|
59175
|
+
break;
|
|
59176
|
+
}
|
|
59177
|
+
const iteration = rows && row ? { index: i + 1, of: rows.length, label: row.label } : void 0;
|
|
59178
|
+
const summary = await runCollection({
|
|
59179
|
+
collection,
|
|
59180
|
+
collectionPath,
|
|
59181
|
+
run: { values },
|
|
59182
|
+
...context ? {
|
|
59183
|
+
context: row ? { ...context, dataRow: { source: row.source, vars: row.vars } } : context
|
|
59184
|
+
} : {},
|
|
59185
|
+
...signal ? { signal } : {},
|
|
59186
|
+
bail,
|
|
59187
|
+
onResult: (index, result) => onResult?.(index, result, iteration)
|
|
59188
|
+
});
|
|
59189
|
+
summaries.push(summary);
|
|
59190
|
+
if (bail && summary.failed + summary.errored > 0) stopped = true;
|
|
59191
|
+
}
|
|
59192
|
+
if (collection.teardown?.length && !signal?.aborted) summaries.push(await once("teardown", false));
|
|
59193
|
+
return {
|
|
59194
|
+
total: summaries.reduce((n, s) => n + s.total, 0) + unrun,
|
|
59195
|
+
passed: summaries.reduce((n, s) => n + s.passed, 0),
|
|
59196
|
+
failed: summaries.reduce((n, s) => n + s.failed, 0),
|
|
59197
|
+
errored: summaries.reduce((n, s) => n + s.errored, 0),
|
|
59198
|
+
skipped: summaries.reduce((n, s) => n + s.skipped, 0) + unrun,
|
|
59199
|
+
durationMs: summaries.reduce((n, s) => n + s.durationMs, 0),
|
|
59200
|
+
results: summaries.flatMap((s) => s.results)
|
|
59201
|
+
};
|
|
59202
|
+
}
|
|
59203
|
+
|
|
58675
59204
|
// ../../packages/core/src/flags/resolve.ts
|
|
58676
59205
|
import { spawn } from "node:child_process";
|
|
58677
59206
|
var FLAG_COMMAND_TIMEOUT_MS = 6e4;
|
|
@@ -58860,41 +59389,29 @@ async function runJob(job) {
|
|
|
58860
59389
|
const context = { collectionPath: loaded.path, environmentName: job.environment };
|
|
58861
59390
|
const lines = await stepLines(loaded.path);
|
|
58862
59391
|
const data = loaded.dataFile ? await readDataFile(loaded.dataFile.path) : null;
|
|
58863
|
-
const rows = data ? data.rows : [null];
|
|
58864
|
-
const summaries = [];
|
|
58865
59392
|
const refs = [];
|
|
58866
|
-
|
|
58867
|
-
|
|
58868
|
-
|
|
58869
|
-
|
|
58870
|
-
|
|
58871
|
-
|
|
58872
|
-
|
|
58873
|
-
|
|
58874
|
-
|
|
58875
|
-
|
|
58876
|
-
|
|
58877
|
-
|
|
58878
|
-
|
|
58879
|
-
|
|
58880
|
-
|
|
58881
|
-
|
|
58882
|
-
|
|
58883
|
-
|
|
58884
|
-
|
|
58885
|
-
|
|
58886
|
-
break;
|
|
59393
|
+
const summary = await runSuite({
|
|
59394
|
+
collection,
|
|
59395
|
+
collectionPath: loaded.path,
|
|
59396
|
+
context: { ...context, flags: job.flags, dataRow: null },
|
|
59397
|
+
rows: data ? data.rows.map((row, i) => ({
|
|
59398
|
+
source: `${path11.basename(data.path)} row ${i + 1}`,
|
|
59399
|
+
vars: row.values,
|
|
59400
|
+
label: row.label
|
|
59401
|
+
})) : null,
|
|
59402
|
+
bail: job.bail,
|
|
59403
|
+
// Each result reports the step it ran under — a use step's requests all under it.
|
|
59404
|
+
onResult: (index, result, iteration) => {
|
|
59405
|
+
const stage = result.stage;
|
|
59406
|
+
const step = stage ? index : job.steps ? job.steps[index] ?? index : index;
|
|
59407
|
+
refs.push({
|
|
59408
|
+
index: step,
|
|
59409
|
+
line: lines[stage ?? "steps"][step] ?? null,
|
|
59410
|
+
...stage ? { stage } : {},
|
|
59411
|
+
...iteration ? { iteration } : {}
|
|
59412
|
+
});
|
|
58887
59413
|
}
|
|
58888
|
-
}
|
|
58889
|
-
const summary = {
|
|
58890
|
-
total: summaries.reduce((n, s) => n + s.total, 0) + unrun,
|
|
58891
|
-
passed: summaries.reduce((n, s) => n + s.passed, 0),
|
|
58892
|
-
failed: summaries.reduce((n, s) => n + s.failed, 0),
|
|
58893
|
-
errored: summaries.reduce((n, s) => n + s.errored, 0),
|
|
58894
|
-
skipped: summaries.reduce((n, s) => n + s.skipped, 0) + unrun,
|
|
58895
|
-
durationMs: summaries.reduce((n, s) => n + s.durationMs, 0),
|
|
58896
|
-
results: summaries.flatMap((s) => s.results)
|
|
58897
|
-
};
|
|
59414
|
+
});
|
|
58898
59415
|
return {
|
|
58899
59416
|
ok: true,
|
|
58900
59417
|
summary: redact(summary, await secretsOf(context)),
|
|
@@ -58935,22 +59452,25 @@ function redact(value, secrets) {
|
|
|
58935
59452
|
return walk(value);
|
|
58936
59453
|
}
|
|
58937
59454
|
async function stepLines(file2) {
|
|
59455
|
+
const lines = { setup: [], steps: [], teardown: [] };
|
|
58938
59456
|
try {
|
|
58939
59457
|
const counter = new import_yaml7.LineCounter();
|
|
58940
59458
|
const document = (0, import_yaml7.parseDocument)(await fs9.readFile(file2, "utf8"), { lineCounter: counter });
|
|
58941
|
-
const
|
|
58942
|
-
|
|
58943
|
-
|
|
58944
|
-
|
|
58945
|
-
|
|
59459
|
+
const of = (key) => {
|
|
59460
|
+
const list = document.get(key, true);
|
|
59461
|
+
if (!list || typeof list !== "object" || !("items" in list)) return [];
|
|
59462
|
+
return list.items.map(
|
|
59463
|
+
(item) => item?.range ? counter.linePos(item.range[0]).line : null
|
|
59464
|
+
);
|
|
59465
|
+
};
|
|
59466
|
+
return { setup: of("setup"), steps: of("steps"), teardown: of("teardown") };
|
|
58946
59467
|
} catch {
|
|
58947
|
-
return
|
|
59468
|
+
return lines;
|
|
58948
59469
|
}
|
|
58949
59470
|
}
|
|
58950
59471
|
|
|
58951
59472
|
export {
|
|
58952
59473
|
__toESM,
|
|
58953
|
-
require_dist,
|
|
58954
59474
|
external_exports,
|
|
58955
59475
|
TagSchema,
|
|
58956
59476
|
stepsForTags,
|
|
@@ -58959,7 +59479,9 @@ export {
|
|
|
58959
59479
|
samePath,
|
|
58960
59480
|
relativePosix,
|
|
58961
59481
|
environmentNames,
|
|
59482
|
+
resultName,
|
|
58962
59483
|
parseMarkdown,
|
|
59484
|
+
require_dist,
|
|
58963
59485
|
COLLECTIONS_DIR,
|
|
58964
59486
|
SETTINGS_FILE,
|
|
58965
59487
|
DOC_EXTENSION,
|
|
@@ -58968,6 +59490,7 @@ export {
|
|
|
58968
59490
|
projectEnvironments,
|
|
58969
59491
|
loadProjectCollections,
|
|
58970
59492
|
loadProjectTls,
|
|
59493
|
+
referenceProblems,
|
|
58971
59494
|
resolveFlags,
|
|
58972
59495
|
flagOverrides,
|
|
58973
59496
|
unattempted,
|