/** * parallelChaos unit tests use a stubbed `chaos()` via dependency * injection at the module boundary — vitest mock of `./chaos.js`. We * verify orchestration (concurrency cap, per-shard option merging, * merged report, exit code aggregation) without launching browsers. */ import { describe, expect, it, vi } from "vitest"; import type { ChaosResult } from "./chaos.js"; import type { CrawlReport } from "./types.js"; const fakeReport = (overrides: Partial = {}): CrawlReport => ({ baseUrl: "https://example.test/", seed: 1, reproCommand: "chaosbringer --url https://example.test/", startTime: 0, endTime: 0, duration: 0, pagesVisited: 0, totalErrors: 0, totalWarnings: 0, blockedExternalNavigations: 0, recoveryCount: 0, pages: [], actions: [], summary: { totalPages: 0, successfulPages: 0, failedPages: 0, errorRate: 0, averageActionsPerPage: 0, errorsByType: {}, topErrorPages: [], discoveryMetrics: { initialUrls: 0, extractedLinks: 0, clickedLinks: 0, spaNavigations: 0, totalDiscovered: 0, }, }, errorClusters: [], ...overrides, }); const fakeResult = (exitCode = 0): ChaosResult => ({ report: fakeReport({ seed: exitCode + 1 }), passed: exitCode === 0, exitCode, }); vi.mock("./chaos.js", async () => { const fakeReport = (overrides: Partial = {}): CrawlReport => ({ baseUrl: "https://example.test/", seed: 1, reproCommand: "chaosbringer", startTime: 0, endTime: 0, duration: 0, pagesVisited: 0, totalErrors: 0, totalWarnings: 0, blockedExternalNavigations: 0, recoveryCount: 0, pages: [], actions: [], summary: { totalPages: 0, successfulPages: 0, failedPages: 0, errorRate: 0, averageActionsPerPage: 0, errorsByType: {}, topErrorPages: [], discoveryMetrics: { initialUrls: 0, extractedLinks: 0, clickedLinks: 0, spaNavigations: 0, totalDiscovered: 0, }, }, errorClusters: [], ...overrides, }); const seenSeeds: number[] = []; return { chaos: vi.fn(async (opts: { seed?: number }) => { const seed = opts.seed ?? 0; seenSeeds.push(seed); return { report: fakeReport({ seed }), passed: seed % 2 === 0, exitCode: seed % 2 === 0 ? 0 : 1, }; }), __seenSeeds: seenSeeds, }; }); import { parallelChaos } from "./parallel.js"; describe("parallelChaos", () => { it("invokes chaos() once per shard with merged options", async () => { const { chaos } = await import("./chaos.js"); const out = await parallelChaos({ base: { baseUrl: "https://example.test/" }, shards: [ { name: "form", options: { seed: 2 } }, { name: "payload", options: { seed: 4 } }, ], }); expect(chaos).toHaveBeenCalledTimes(2); expect(out.shards.map((s) => s.name)).toEqual(["form", "payload"]); expect(out.passed).toBe(true); expect(out.exitCode).toBe(0); }); it("aggregates exit codes via max across shards", async () => { const out = await parallelChaos({ base: { baseUrl: "https://example.test/" }, shards: [ { options: { seed: 2 } }, // exits 0 { options: { seed: 3 } }, // exits 1 ], }); expect(out.passed).toBe(false); expect(out.exitCode).toBe(1); }); it("respects the concurrency cap", async () => { // We can't directly assert wall-clock parallelism with a synchronous // mocked chaos, so use the fact that concurrency=1 still completes // every shard. const out = await parallelChaos({ base: { baseUrl: "https://example.test/" }, shards: [ { options: { seed: 2 } }, { options: { seed: 4 } }, { options: { seed: 6 } }, ], concurrency: 1, }); expect(out.shards.length).toBe(3); }); it("throws when shards list is empty", async () => { await expect( parallelChaos({ base: { baseUrl: "https://example.test/" }, shards: [] }), ).rejects.toThrow(); }); // Reference fakeResult so the import isn't unused — keeps tsc strict // about exported helpers happy in this file's review pass. it("fakeResult shape is referenced", () => { expect(fakeResult(0).exitCode).toBe(0); }); });