bruno-lifecycle-adapter 0.1.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 +403 -0
- package/dist/index.d.mts +254 -0
- package/dist/index.d.ts +254 -0
- package/dist/index.js +668 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +638 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BruFileScanner: () => BruFileScanner,
|
|
24
|
+
BrunoJsonReportParser: () => BrunoJsonReportParser,
|
|
25
|
+
BrunoLifecycleAdapter: () => BrunoLifecycleAdapter,
|
|
26
|
+
TypedEventBus: () => TypedEventBus
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/application/BrunoLifecycleAdapter.ts
|
|
31
|
+
var import_node_child_process = require("child_process");
|
|
32
|
+
var import_node_path2 = require("path");
|
|
33
|
+
|
|
34
|
+
// src/infrastructure/BrunoJsonReportParser.ts
|
|
35
|
+
var import_promises = require("fs/promises");
|
|
36
|
+
|
|
37
|
+
// src/shared/utils.ts
|
|
38
|
+
var import_node_crypto = require("crypto");
|
|
39
|
+
function generateRunId() {
|
|
40
|
+
return (0, import_node_crypto.randomUUID)();
|
|
41
|
+
}
|
|
42
|
+
function nowIso() {
|
|
43
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
44
|
+
}
|
|
45
|
+
function elapsedMs(startedAt) {
|
|
46
|
+
return Date.now() - new Date(startedAt).getTime();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/infrastructure/BrunoJsonReportParser.ts
|
|
50
|
+
var BrunoJsonReportParser = class {
|
|
51
|
+
async parse(reportPath) {
|
|
52
|
+
const raw = await this.readRaw(reportPath);
|
|
53
|
+
return this.mapToSummary(raw);
|
|
54
|
+
}
|
|
55
|
+
async readRaw(reportPath) {
|
|
56
|
+
let content;
|
|
57
|
+
try {
|
|
58
|
+
content = await (0, import_promises.readFile)(reportPath, "utf-8");
|
|
59
|
+
} catch (err) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`BrunoJsonReportParser: cannot read report at "${reportPath}": ${String(err)}`,
|
|
62
|
+
{ cause: err }
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
let parsed;
|
|
66
|
+
try {
|
|
67
|
+
parsed = JSON.parse(content);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`BrunoJsonReportParser: invalid JSON in report at "${reportPath}": ${String(err)}`,
|
|
71
|
+
{ cause: err }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(parsed)) {
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
78
|
+
const obj = parsed;
|
|
79
|
+
const results = Array.isArray(obj.results) ? obj.results : [];
|
|
80
|
+
const summary = obj.summary !== null && typeof obj.summary === "object" ? obj.summary : void 0;
|
|
81
|
+
const iteration = {
|
|
82
|
+
iterationIndex: typeof obj.iterationIndex === "number" ? obj.iterationIndex : 0,
|
|
83
|
+
results,
|
|
84
|
+
...summary !== void 0 ? { summary } : {}
|
|
85
|
+
};
|
|
86
|
+
return [iteration];
|
|
87
|
+
}
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
mapToSummary(raw) {
|
|
91
|
+
const requests = raw.flatMap((iter) => (iter.results ?? []).map((r) => this.mapRequest(r)));
|
|
92
|
+
const summary = raw[0]?.summary ?? {};
|
|
93
|
+
const totalRequests = summary.totalRequests ?? requests.length;
|
|
94
|
+
const passedRequests = summary.passedRequests ?? requests.filter((r) => r.status === "finished").length;
|
|
95
|
+
const failedRequests = summary.failedRequests ?? requests.filter((r) => r.status === "failed").length;
|
|
96
|
+
const skippedRequests = summary.skippedRequests ?? requests.filter((r) => r.status === "skipped").length;
|
|
97
|
+
const allTests = requests.flatMap((r) => r.tests);
|
|
98
|
+
const totalTests = summary.totalTests ?? allTests.length;
|
|
99
|
+
const passedTests = summary.passedTests ?? allTests.filter((t) => t.status === "passed").length;
|
|
100
|
+
const failedTests = summary.failedTests ?? allTests.filter((t) => t.status === "failed").length;
|
|
101
|
+
const allAssertions = requests.flatMap((r) => r.assertions);
|
|
102
|
+
const totalAssertions = summary.totalAssertions ?? allAssertions.length;
|
|
103
|
+
const passedAssertions = summary.passedAssertions ?? allAssertions.filter((a) => a.passed).length;
|
|
104
|
+
const failedAssertions = summary.failedAssertions ?? allAssertions.filter((a) => !a.passed).length;
|
|
105
|
+
const now = nowIso();
|
|
106
|
+
return {
|
|
107
|
+
runId: "",
|
|
108
|
+
collectionPath: "",
|
|
109
|
+
startedAt: now,
|
|
110
|
+
finishedAt: now,
|
|
111
|
+
durationMs: 0,
|
|
112
|
+
exitCode: 0,
|
|
113
|
+
status: failedRequests > 0 ? "failed" : "finished",
|
|
114
|
+
totalRequests,
|
|
115
|
+
passedRequests,
|
|
116
|
+
failedRequests,
|
|
117
|
+
skippedRequests,
|
|
118
|
+
totalTests,
|
|
119
|
+
passedTests,
|
|
120
|
+
failedTests,
|
|
121
|
+
totalAssertions,
|
|
122
|
+
passedAssertions,
|
|
123
|
+
failedAssertions,
|
|
124
|
+
requests
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
mapRequest(raw) {
|
|
128
|
+
const tests = (raw.testResults ?? []).map((t) => this.mapTest(t));
|
|
129
|
+
const assertions = (raw.assertionResults ?? []).map((a) => this.mapAssertion(a));
|
|
130
|
+
const responseStatus = typeof raw.response?.status === "number" ? raw.response.status : void 0;
|
|
131
|
+
return {
|
|
132
|
+
requestName: raw.name ?? raw.test?.filename ?? "unknown",
|
|
133
|
+
requestFile: raw.test?.filename ?? "",
|
|
134
|
+
status: this.mapRequestStatus(raw.status),
|
|
135
|
+
...responseStatus !== void 0 ? { responseStatus } : {},
|
|
136
|
+
...raw.response?.responseTime !== void 0 ? { durationMs: raw.response.responseTime } : {},
|
|
137
|
+
...raw.error ? { error: this.makeError(String(raw.error)) } : {},
|
|
138
|
+
tests,
|
|
139
|
+
assertions
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
mapTest(raw) {
|
|
143
|
+
return {
|
|
144
|
+
testName: raw.description ?? "unknown",
|
|
145
|
+
status: this.mapTestStatus(raw.status),
|
|
146
|
+
...raw.error ? { error: this.makeError(String(raw.error)) } : {}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
mapAssertion(raw) {
|
|
150
|
+
const passed = raw.status === "pass" || raw.status === "passed";
|
|
151
|
+
const assertion = raw.lhsExpr !== void 0 && raw.rhsExpr !== void 0 ? `${raw.lhsExpr} ${raw.rhsExpr}` : "unknown";
|
|
152
|
+
return {
|
|
153
|
+
assertion,
|
|
154
|
+
passed,
|
|
155
|
+
...raw.error ? { error: this.makeError(String(raw.error)) } : {}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
mapRequestStatus(status) {
|
|
159
|
+
switch (status?.toLowerCase()) {
|
|
160
|
+
case "pass":
|
|
161
|
+
case "passed":
|
|
162
|
+
case "success":
|
|
163
|
+
return "finished";
|
|
164
|
+
case "fail":
|
|
165
|
+
case "failed":
|
|
166
|
+
case "error":
|
|
167
|
+
return "failed";
|
|
168
|
+
case "skip":
|
|
169
|
+
case "skipped":
|
|
170
|
+
return "skipped";
|
|
171
|
+
default:
|
|
172
|
+
return "finished";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
mapTestStatus(status) {
|
|
176
|
+
switch (status?.toLowerCase()) {
|
|
177
|
+
case "pass":
|
|
178
|
+
case "passed":
|
|
179
|
+
return "passed";
|
|
180
|
+
case "fail":
|
|
181
|
+
case "failed":
|
|
182
|
+
return "failed";
|
|
183
|
+
case "skip":
|
|
184
|
+
case "skipped":
|
|
185
|
+
return "skipped";
|
|
186
|
+
default:
|
|
187
|
+
return "failed";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
makeError(message) {
|
|
191
|
+
return { message };
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/infrastructure/BruFileScanner.ts
|
|
196
|
+
var import_promises2 = require("fs/promises");
|
|
197
|
+
var import_node_path = require("path");
|
|
198
|
+
var BruFileScanner = class {
|
|
199
|
+
/**
|
|
200
|
+
* Returns all HTTP request files found under `rootDir`.
|
|
201
|
+
*
|
|
202
|
+
* @param rootDir Absolute path to the collection root (the directory
|
|
203
|
+
* passed to `bru run`).
|
|
204
|
+
* @param recursive When `true`, descends into sub-directories. Mirrors
|
|
205
|
+
* the `-r` flag behaviour of `bru run`.
|
|
206
|
+
*/
|
|
207
|
+
async scan(rootDir, recursive) {
|
|
208
|
+
const found = await this.collectBruFiles(rootDir, rootDir, recursive);
|
|
209
|
+
found.sort((a, b) => a.requestFile.localeCompare(b.requestFile));
|
|
210
|
+
return found;
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Private helpers
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
async collectBruFiles(rootDir, dir, recursive) {
|
|
216
|
+
let entries;
|
|
217
|
+
try {
|
|
218
|
+
entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
|
|
219
|
+
} catch {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
const results = [];
|
|
223
|
+
for (const entry of entries) {
|
|
224
|
+
const fullPath = (0, import_node_path.join)(dir, entry.name);
|
|
225
|
+
if (entry.isDirectory()) {
|
|
226
|
+
if (entry.name === "environments") continue;
|
|
227
|
+
if (recursive) {
|
|
228
|
+
const nested = await this.collectBruFiles(rootDir, fullPath, true);
|
|
229
|
+
results.push(...nested);
|
|
230
|
+
}
|
|
231
|
+
} else if (entry.isFile() && entry.name.endsWith(".bru")) {
|
|
232
|
+
const discovered = await this.parseRequestFile(rootDir, fullPath);
|
|
233
|
+
if (discovered !== null) {
|
|
234
|
+
results.push(discovered);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return results;
|
|
239
|
+
}
|
|
240
|
+
async parseRequestFile(rootDir, filePath) {
|
|
241
|
+
let content;
|
|
242
|
+
try {
|
|
243
|
+
content = await (0, import_promises2.readFile)(filePath, "utf-8");
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const meta = this.parseMetaBlock(content);
|
|
248
|
+
if (!meta.type || meta.type !== "http") {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const requestName = meta.name ?? this.nameFromPath(filePath);
|
|
252
|
+
const requestFile = (0, import_node_path.relative)(rootDir, filePath).replace(/\\/g, "/");
|
|
253
|
+
return { requestName, requestFile };
|
|
254
|
+
}
|
|
255
|
+
parseMetaBlock(content) {
|
|
256
|
+
const metaMatch = content.match(/meta\s*\{([^}]*)\}/s);
|
|
257
|
+
if (!metaMatch) return { name: void 0, type: void 0 };
|
|
258
|
+
const block = metaMatch[1];
|
|
259
|
+
const nameMatch = block.match(/\bname:\s*([^\n]+)/);
|
|
260
|
+
const typeMatch = block.match(/\btype:\s*([^\n]+)/);
|
|
261
|
+
return {
|
|
262
|
+
name: nameMatch?.[1]?.trim(),
|
|
263
|
+
type: typeMatch?.[1]?.trim()
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
nameFromPath(filePath) {
|
|
267
|
+
const parts = filePath.replace(/\\/g, "/").split("/");
|
|
268
|
+
return parts.at(-1)?.replace(/\.bru$/, "") ?? "unknown";
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/infrastructure/TypedEventBus.ts
|
|
273
|
+
var TypedEventBus = class {
|
|
274
|
+
handlers = /* @__PURE__ */ new Map();
|
|
275
|
+
on(event, handler) {
|
|
276
|
+
this.getOrCreate(event).add(handler);
|
|
277
|
+
return () => this.off(event, handler);
|
|
278
|
+
}
|
|
279
|
+
once(event, handler) {
|
|
280
|
+
const wrapper = (payload) => {
|
|
281
|
+
this.off(event, wrapper);
|
|
282
|
+
handler(payload);
|
|
283
|
+
};
|
|
284
|
+
return this.on(event, wrapper);
|
|
285
|
+
}
|
|
286
|
+
off(event, handler) {
|
|
287
|
+
this.handlers.get(event)?.delete(handler);
|
|
288
|
+
}
|
|
289
|
+
emit(event, payload) {
|
|
290
|
+
const set = this.handlers.get(event);
|
|
291
|
+
if (!set) return;
|
|
292
|
+
for (const handler of set) {
|
|
293
|
+
handler(payload);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
removeAllListeners(event) {
|
|
297
|
+
if (event !== void 0) {
|
|
298
|
+
this.handlers.delete(event);
|
|
299
|
+
} else {
|
|
300
|
+
this.handlers.clear();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
getOrCreate(event) {
|
|
304
|
+
let set = this.handlers.get(event);
|
|
305
|
+
if (!set) {
|
|
306
|
+
set = /* @__PURE__ */ new Set();
|
|
307
|
+
this.handlers.set(event, set);
|
|
308
|
+
}
|
|
309
|
+
return set;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/application/BrunoLifecycleAdapter.ts
|
|
314
|
+
var BrunoLifecycleAdapter = class {
|
|
315
|
+
bus = new TypedEventBus();
|
|
316
|
+
parser;
|
|
317
|
+
scanner;
|
|
318
|
+
constructor(parser, scanner) {
|
|
319
|
+
this.parser = parser ?? new BrunoJsonReportParser();
|
|
320
|
+
this.scanner = scanner ?? new BruFileScanner();
|
|
321
|
+
}
|
|
322
|
+
on(event, handler) {
|
|
323
|
+
return this.bus.on(event, handler);
|
|
324
|
+
}
|
|
325
|
+
once(event, handler) {
|
|
326
|
+
return this.bus.once(event, handler);
|
|
327
|
+
}
|
|
328
|
+
off(event, handler) {
|
|
329
|
+
this.bus.off(event, handler);
|
|
330
|
+
}
|
|
331
|
+
async run(config) {
|
|
332
|
+
const runId = generateRunId();
|
|
333
|
+
const startedAt = nowIso();
|
|
334
|
+
this.emit("run:starting", {
|
|
335
|
+
event: "run:starting",
|
|
336
|
+
runId,
|
|
337
|
+
timestamp: startedAt,
|
|
338
|
+
reliability: "native",
|
|
339
|
+
cwd: config.cwd,
|
|
340
|
+
collectionPath: config.collectionPath
|
|
341
|
+
});
|
|
342
|
+
const resolvedCollection = (0, import_node_path2.resolve)(config.cwd, config.collectionPath);
|
|
343
|
+
const collectionRoot = resolvedCollection.endsWith(".bru") ? (0, import_node_path2.dirname)(resolvedCollection) : resolvedCollection;
|
|
344
|
+
const discoveryPromise = this.scanner.scan(collectionRoot, config.recursive ?? false).catch(() => []);
|
|
345
|
+
const args = this.buildArgs(config);
|
|
346
|
+
const bin = config.bruBin ?? "bru";
|
|
347
|
+
return new Promise((resolveRun, rejectRun) => {
|
|
348
|
+
let timedOut = false;
|
|
349
|
+
let timeoutHandle;
|
|
350
|
+
const child = (0, import_node_child_process.spawn)(bin, args, {
|
|
351
|
+
cwd: config.cwd,
|
|
352
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
353
|
+
shell: false
|
|
354
|
+
});
|
|
355
|
+
if (child.pid === void 0) {
|
|
356
|
+
const err = new Error(`Failed to spawn "${bin}". Is Bruno CLI installed?`);
|
|
357
|
+
const finishedAt = nowIso();
|
|
358
|
+
const durationMs = elapsedMs(startedAt);
|
|
359
|
+
const detail = { message: err.message };
|
|
360
|
+
const summary = this.buildEmptySummary({
|
|
361
|
+
runId,
|
|
362
|
+
collectionPath: config.collectionPath,
|
|
363
|
+
startedAt,
|
|
364
|
+
finishedAt,
|
|
365
|
+
durationMs,
|
|
366
|
+
exitCode: -1,
|
|
367
|
+
status: "failed",
|
|
368
|
+
error: detail
|
|
369
|
+
});
|
|
370
|
+
this.emit("run:failed", {
|
|
371
|
+
event: "run:failed",
|
|
372
|
+
runId,
|
|
373
|
+
timestamp: finishedAt,
|
|
374
|
+
reliability: "native",
|
|
375
|
+
exitCode: -1,
|
|
376
|
+
durationMs,
|
|
377
|
+
error: detail,
|
|
378
|
+
summary
|
|
379
|
+
});
|
|
380
|
+
rejectRun(err);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this.emit("run:started", {
|
|
384
|
+
event: "run:started",
|
|
385
|
+
runId,
|
|
386
|
+
timestamp: nowIso(),
|
|
387
|
+
reliability: "native",
|
|
388
|
+
cwd: config.cwd,
|
|
389
|
+
collectionPath: config.collectionPath,
|
|
390
|
+
pid: child.pid
|
|
391
|
+
});
|
|
392
|
+
if (config.timeoutMs && config.timeoutMs > 0) {
|
|
393
|
+
timeoutHandle = setTimeout(() => {
|
|
394
|
+
timedOut = true;
|
|
395
|
+
child.kill("SIGTERM");
|
|
396
|
+
}, config.timeoutMs);
|
|
397
|
+
}
|
|
398
|
+
child.stdout.setEncoding("utf-8");
|
|
399
|
+
child.stderr.setEncoding("utf-8");
|
|
400
|
+
child.stdout.on("data", (chunk) => {
|
|
401
|
+
this.emit("stdout", {
|
|
402
|
+
event: "stdout",
|
|
403
|
+
runId,
|
|
404
|
+
timestamp: nowIso(),
|
|
405
|
+
reliability: "native",
|
|
406
|
+
chunk
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
child.stderr.on("data", (chunk) => {
|
|
410
|
+
this.emit("stderr", {
|
|
411
|
+
event: "stderr",
|
|
412
|
+
runId,
|
|
413
|
+
timestamp: nowIso(),
|
|
414
|
+
reliability: "native",
|
|
415
|
+
chunk
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
child.on("error", (err) => {
|
|
419
|
+
clearTimeout(timeoutHandle);
|
|
420
|
+
const detail = { message: err.message, stack: err.stack };
|
|
421
|
+
const finishedAt = nowIso();
|
|
422
|
+
const durationMs = elapsedMs(startedAt);
|
|
423
|
+
const summary = this.buildEmptySummary({
|
|
424
|
+
runId,
|
|
425
|
+
collectionPath: config.collectionPath,
|
|
426
|
+
startedAt,
|
|
427
|
+
finishedAt,
|
|
428
|
+
durationMs,
|
|
429
|
+
exitCode: -1,
|
|
430
|
+
status: "failed",
|
|
431
|
+
error: detail
|
|
432
|
+
});
|
|
433
|
+
this.emit("run:failed", {
|
|
434
|
+
event: "run:failed",
|
|
435
|
+
runId,
|
|
436
|
+
timestamp: finishedAt,
|
|
437
|
+
reliability: "native",
|
|
438
|
+
exitCode: -1,
|
|
439
|
+
durationMs,
|
|
440
|
+
error: detail,
|
|
441
|
+
summary
|
|
442
|
+
});
|
|
443
|
+
rejectRun(err);
|
|
444
|
+
});
|
|
445
|
+
child.on("close", (code) => {
|
|
446
|
+
clearTimeout(timeoutHandle);
|
|
447
|
+
void (async () => {
|
|
448
|
+
const exitCode = code ?? -1;
|
|
449
|
+
const finishedAt = nowIso();
|
|
450
|
+
const durationMs = elapsedMs(startedAt);
|
|
451
|
+
const runStatus = timedOut || exitCode !== 0 ? "failed" : "finished";
|
|
452
|
+
const discovered = await discoveryPromise;
|
|
453
|
+
for (const req of discovered) {
|
|
454
|
+
this.emit("request:discovered", {
|
|
455
|
+
event: "request:discovered",
|
|
456
|
+
runId,
|
|
457
|
+
timestamp: finishedAt,
|
|
458
|
+
reliability: "inferred",
|
|
459
|
+
requestName: req.requestName,
|
|
460
|
+
requestFile: req.requestFile
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
let summary;
|
|
464
|
+
let reportParsedSuccessfully = false;
|
|
465
|
+
if (config.reporterJsonPath) {
|
|
466
|
+
try {
|
|
467
|
+
summary = await this.parser.parse(config.reporterJsonPath);
|
|
468
|
+
reportParsedSuccessfully = true;
|
|
469
|
+
} catch {
|
|
470
|
+
summary = this.buildEmptySummary({
|
|
471
|
+
runId,
|
|
472
|
+
collectionPath: config.collectionPath,
|
|
473
|
+
startedAt,
|
|
474
|
+
finishedAt,
|
|
475
|
+
durationMs,
|
|
476
|
+
exitCode,
|
|
477
|
+
status: runStatus
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
summary = this.buildEmptySummary({
|
|
482
|
+
runId,
|
|
483
|
+
collectionPath: config.collectionPath,
|
|
484
|
+
startedAt,
|
|
485
|
+
finishedAt,
|
|
486
|
+
durationMs,
|
|
487
|
+
exitCode,
|
|
488
|
+
status: runStatus
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
const enriched = {
|
|
492
|
+
...summary,
|
|
493
|
+
runId,
|
|
494
|
+
collectionPath: config.collectionPath,
|
|
495
|
+
startedAt,
|
|
496
|
+
finishedAt,
|
|
497
|
+
durationMs,
|
|
498
|
+
exitCode,
|
|
499
|
+
status: runStatus
|
|
500
|
+
};
|
|
501
|
+
this.emitReportEvents(enriched, runId, finishedAt);
|
|
502
|
+
if (config.reporterJsonPath && reportParsedSuccessfully) {
|
|
503
|
+
this.emit("report:json:ready", {
|
|
504
|
+
event: "report:json:ready",
|
|
505
|
+
runId,
|
|
506
|
+
timestamp: finishedAt,
|
|
507
|
+
reliability: "derived",
|
|
508
|
+
path: config.reporterJsonPath,
|
|
509
|
+
summary: enriched
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
if (runStatus === "finished") {
|
|
513
|
+
this.emit("run:finished", {
|
|
514
|
+
event: "run:finished",
|
|
515
|
+
runId,
|
|
516
|
+
timestamp: finishedAt,
|
|
517
|
+
reliability: "native",
|
|
518
|
+
exitCode,
|
|
519
|
+
durationMs,
|
|
520
|
+
summary: enriched
|
|
521
|
+
});
|
|
522
|
+
} else {
|
|
523
|
+
const error = timedOut ? { message: `Run timed out after ${config.timeoutMs ?? 0}ms` } : { message: `Run exited with code ${exitCode}` };
|
|
524
|
+
this.emit("run:failed", {
|
|
525
|
+
event: "run:failed",
|
|
526
|
+
runId,
|
|
527
|
+
timestamp: finishedAt,
|
|
528
|
+
reliability: "native",
|
|
529
|
+
exitCode,
|
|
530
|
+
durationMs,
|
|
531
|
+
error,
|
|
532
|
+
summary: enriched
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
resolveRun(enriched);
|
|
536
|
+
})().catch((err) => {
|
|
537
|
+
rejectRun(err instanceof Error ? err : new Error(String(err)));
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
emitReportEvents(summary, runId, timestamp) {
|
|
543
|
+
for (const req of summary.requests) {
|
|
544
|
+
const reqStarted = {
|
|
545
|
+
event: "request:started",
|
|
546
|
+
runId,
|
|
547
|
+
timestamp,
|
|
548
|
+
reliability: "derived",
|
|
549
|
+
requestName: req.requestName,
|
|
550
|
+
requestFile: req.requestFile
|
|
551
|
+
};
|
|
552
|
+
this.emit("request:started", reqStarted);
|
|
553
|
+
if (req.status === "skipped") {
|
|
554
|
+
const skipped = {
|
|
555
|
+
event: "request:skipped",
|
|
556
|
+
runId,
|
|
557
|
+
timestamp,
|
|
558
|
+
reliability: "derived",
|
|
559
|
+
requestName: req.requestName,
|
|
560
|
+
requestFile: req.requestFile
|
|
561
|
+
};
|
|
562
|
+
this.emit("request:skipped", skipped);
|
|
563
|
+
} else {
|
|
564
|
+
const finished = {
|
|
565
|
+
event: "request:finished",
|
|
566
|
+
runId,
|
|
567
|
+
timestamp,
|
|
568
|
+
reliability: "derived",
|
|
569
|
+
requestName: req.requestName,
|
|
570
|
+
requestFile: req.requestFile,
|
|
571
|
+
status: req.status,
|
|
572
|
+
...req.responseStatus !== void 0 ? { responseStatus: req.responseStatus } : {},
|
|
573
|
+
...req.durationMs !== void 0 ? { durationMs: req.durationMs } : {},
|
|
574
|
+
...req.error !== void 0 ? { error: req.error } : {}
|
|
575
|
+
};
|
|
576
|
+
this.emit("request:finished", finished);
|
|
577
|
+
}
|
|
578
|
+
for (const test of req.tests) {
|
|
579
|
+
const testStarted = {
|
|
580
|
+
event: "test:started",
|
|
581
|
+
runId,
|
|
582
|
+
timestamp,
|
|
583
|
+
reliability: "derived",
|
|
584
|
+
requestName: req.requestName,
|
|
585
|
+
testName: test.testName
|
|
586
|
+
};
|
|
587
|
+
this.emit("test:started", testStarted);
|
|
588
|
+
const testFinished = {
|
|
589
|
+
event: "test:finished",
|
|
590
|
+
runId,
|
|
591
|
+
timestamp,
|
|
592
|
+
reliability: "derived",
|
|
593
|
+
requestName: req.requestName,
|
|
594
|
+
testName: test.testName,
|
|
595
|
+
status: test.status,
|
|
596
|
+
...test.error !== void 0 ? { error: test.error } : {}
|
|
597
|
+
};
|
|
598
|
+
this.emit("test:finished", testFinished);
|
|
599
|
+
}
|
|
600
|
+
for (const a of req.assertions) {
|
|
601
|
+
const assertionResult = {
|
|
602
|
+
event: "assertion:result",
|
|
603
|
+
runId,
|
|
604
|
+
timestamp,
|
|
605
|
+
reliability: "derived",
|
|
606
|
+
requestName: req.requestName,
|
|
607
|
+
assertion: a.assertion,
|
|
608
|
+
passed: a.passed,
|
|
609
|
+
...a.actual !== void 0 ? { actual: a.actual } : {},
|
|
610
|
+
...a.expected !== void 0 ? { expected: a.expected } : {},
|
|
611
|
+
...a.error !== void 0 ? { error: a.error } : {}
|
|
612
|
+
};
|
|
613
|
+
this.emit("assertion:result", assertionResult);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
buildArgs(config) {
|
|
618
|
+
const args = ["run", config.collectionPath];
|
|
619
|
+
if (config.recursive) {
|
|
620
|
+
args.push("-r");
|
|
621
|
+
}
|
|
622
|
+
if (config.env) {
|
|
623
|
+
args.push("--env", config.env);
|
|
624
|
+
}
|
|
625
|
+
if (config.reporterJsonPath) {
|
|
626
|
+
args.push("--reporter-json", config.reporterJsonPath);
|
|
627
|
+
}
|
|
628
|
+
if (config.extraArgs) {
|
|
629
|
+
args.push(...config.extraArgs);
|
|
630
|
+
}
|
|
631
|
+
return args;
|
|
632
|
+
}
|
|
633
|
+
buildEmptySummary(opts) {
|
|
634
|
+
const requests = opts.requests ?? [];
|
|
635
|
+
return {
|
|
636
|
+
runId: opts.runId,
|
|
637
|
+
collectionPath: opts.collectionPath,
|
|
638
|
+
startedAt: opts.startedAt,
|
|
639
|
+
finishedAt: opts.finishedAt,
|
|
640
|
+
durationMs: opts.durationMs,
|
|
641
|
+
exitCode: opts.exitCode,
|
|
642
|
+
status: opts.status,
|
|
643
|
+
totalRequests: requests.length,
|
|
644
|
+
passedRequests: requests.filter((r) => r.status === "finished").length,
|
|
645
|
+
failedRequests: requests.filter((r) => r.status === "failed").length,
|
|
646
|
+
skippedRequests: requests.filter((r) => r.status === "skipped").length,
|
|
647
|
+
totalTests: 0,
|
|
648
|
+
passedTests: 0,
|
|
649
|
+
failedTests: 0,
|
|
650
|
+
totalAssertions: 0,
|
|
651
|
+
passedAssertions: 0,
|
|
652
|
+
failedAssertions: 0,
|
|
653
|
+
requests,
|
|
654
|
+
...opts.error !== void 0 ? { error: opts.error } : {}
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
emit(event, payload) {
|
|
658
|
+
this.bus.emit(event, payload);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
662
|
+
0 && (module.exports = {
|
|
663
|
+
BruFileScanner,
|
|
664
|
+
BrunoJsonReportParser,
|
|
665
|
+
BrunoLifecycleAdapter,
|
|
666
|
+
TypedEventBus
|
|
667
|
+
});
|
|
668
|
+
//# sourceMappingURL=index.js.map
|