Files
fusion/packages/dashboard/scripts/__tests__/run-quality-tests.test.ts
gsxdsm 0512334e86 FN-8714: fix quality runner CLI argument passthrough
Allow dashboard aggregate quality commands through the pnpm script boundary.

- Normalize pnpm’s leading argument separator before validating runner options
- Add end-to-end aggregate and fail-fast quality-runner coverage with a deterministic fixture
- Document aggregate quality-runner invocation and failure reporting

Files changed:
 docs/testing.md                                    | 15 ++++
 packages/dashboard/scripts/__tests__/quality-runner-fixture.mjs | 18 +++++
 packages/dashboard/scripts/__tests__/run-quality-tests.test.ts | 88 ++++++++++++++++++++++
 packages/dashboard/scripts/run-quality-tests.mjs   | 23 ++++--
 4 files changed, 139 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-8714

Fusion-Task-Lineage: 48dbd434-a9e6-4283-8658-cbaa254a7dc7

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-01 12:13:52 -07:00

244 lines
10 KiB
TypeScript

// @vitest-environment node
import { spawnSync } from "node:child_process";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
const __dirname = dirname(fileURLToPath(import.meta.url));
const workspaceRoot = resolve(__dirname, "../../../..");
const fixtureRunnerPath = join(__dirname, "quality-runner-fixture.mjs");
interface QualityLane {
name: string;
group: "app" | "api";
args: string[];
}
interface LaneResult {
lane: QualityLane;
ok: boolean;
code?: number;
signal?: NodeJS.Signals;
}
interface RunQualityTestsModule {
qualityLanes: QualityLane[];
parseArgs(argv: string[]): { group: "all" | "app" | "api"; list: boolean; allLanes: boolean };
resolveConcurrency(env?: Record<string, string | undefined>): number;
runQualityTests(options?: {
failFast?: boolean;
group?: "all" | "app" | "api";
concurrency?: number;
lanes?: QualityLane[];
runner?: (lane: QualityLane) => Promise<LaneResult>;
}): Promise<{ ok: boolean; failed: LaneResult[]; completed: number; skipped: number }>;
}
async function loadModule(): Promise<RunQualityTestsModule> {
return (await import("../run-quality-tests.mjs")) as RunQualityTestsModule;
}
function lane(name: string): QualityLane {
return { name, group: "app", args: ["--heap=6144", "run", "--project", name] };
}
describe("dashboard quality orchestrator", () => {
it("clamps dashboard quality concurrency to the safe bound", async () => {
const { resolveConcurrency } = await loadModule();
expect(resolveConcurrency({})).toBe(2);
expect(resolveConcurrency({ FUSION_DASHBOARD_TEST_CONCURRENCY: "1" })).toBe(1);
expect(resolveConcurrency({ FUSION_DASHBOARD_TEST_CONCURRENCY: "5" })).toBe(2);
expect(resolveConcurrency({ FUSION_DASHBOARD_TEST_CONCURRENCY: "not-a-number" })).toBe(2);
});
it("runs only up to the configured concurrency and does not invoke artifact bootstrap per lane", async () => {
const { runQualityTests } = await loadModule();
const lanes = [lane("one"), lane("two"), lane("three")];
const running = new Set<string>();
let maxRunning = 0;
const launched: string[] = [];
const result = await runQualityTests({
lanes,
concurrency: 2,
runner: async (qualityLane) => {
launched.push(qualityLane.name);
expect(qualityLane.args.join(" ")).not.toContain("ensure-test-artifacts");
running.add(qualityLane.name);
maxRunning = Math.max(maxRunning, running.size);
await Promise.resolve();
running.delete(qualityLane.name);
return { lane: qualityLane, ok: true };
},
});
expect(result).toMatchObject({ ok: true, completed: 3, skipped: 0 });
expect(result.failed).toEqual([]);
expect(launched).toEqual(["one", "two", "three"]);
expect(maxRunning).toBeLessThanOrEqual(2);
});
it("stops scheduling new lanes after a failed lane", async () => {
const { runQualityTests } = await loadModule();
const lanes = [lane("one"), lane("two"), lane("three")];
const launched: string[] = [];
const result = await runQualityTests({
lanes,
concurrency: 1,
runner: async (qualityLane) => {
launched.push(qualityLane.name);
return { lane: qualityLane, ok: qualityLane.name !== "two", code: qualityLane.name === "two" ? 1 : 0 };
},
});
expect(result.ok).toBe(false);
expect(result.failed).toHaveLength(1);
expect(result.failed[0].lane.name).toBe("two");
expect(result.skipped).toBe(1);
expect(launched).toEqual(["one", "two"]);
});
it("treats signal-terminated lanes as failed", async () => {
const { runQualityTests } = await loadModule();
const killedLane = lane("killed");
const result = await runQualityTests({
lanes: [killedLane],
concurrency: 2,
runner: async (qualityLane) => ({ lane: qualityLane, ok: false, signal: "SIGKILL" }),
});
expect(result.ok).toBe(false);
expect(result.failed).toEqual([{ lane: killedLane, ok: false, signal: "SIGKILL" }]);
expect(result.completed).toBe(1);
expect(result.skipped).toBe(0);
});
/*
FNXC:DashboardQualityLanes 2026-07-31-18:10 (u12 — #2784's structural half):
Fail-fast hid 123 real failures behind one red lane: nine lanes were never run, and the report said
"skipped 9 lane(s)", which reads like a benign skip rather than "status unknown". These pin BOTH
halves — the default still stops (fast local feedback), and `--all` reaches every lane — because a
flag nobody can prove works is the same as no flag.
*/
it("DEFAULT stops scheduling after the first failing lane", async () => {
const { runQualityTests } = await loadModule();
const launched: string[] = [];
const result = await runQualityTests({
lanes: [lane("one"), lane("two"), lane("three")],
concurrency: 1,
runner: async (qualityLane) => {
launched.push(qualityLane.name);
return { lane: qualityLane, ok: qualityLane.name !== "one" };
},
});
expect(launched).toEqual(["one"]);
expect(result).toMatchObject({ ok: false, skipped: 2 });
});
it("parses direct and pnpm-forwarded aggregate aliases while rejecting genuine unknown arguments", async () => {
const { parseArgs } = await loadModule();
expect(parseArgs([])).toEqual({ group: "all", list: false, allLanes: false });
expect(parseArgs(["--all"])).toEqual({ group: "all", list: false, allLanes: true });
expect(parseArgs(["--no-fail-fast"])).toEqual({ group: "all", list: false, allLanes: true });
expect(parseArgs(["--", "--all"])).toEqual({ group: "all", list: false, allLanes: true });
expect(parseArgs(["--", "--no-fail-fast"])).toEqual({ group: "all", list: false, allLanes: true });
expect(parseArgs(["--group", "app", "--list"])).toEqual({ group: "app", list: true, allLanes: false });
expect(() => parseArgs(["--", "--not-a-quality-option"])).toThrow("Unknown argument: --not-a-quality-option");
});
it.each(["--all", "--no-fail-fast"])("runs package aggregate alias %s through all 15 lanes without hiding failures", async (aggregateAlias) => {
const { qualityLanes } = await loadModule();
const temporaryDirectory = mkdtempSync(join(tmpdir(), "fn-8714-quality-runner-"));
const laneLogPath = join(temporaryDirectory, "lanes.log");
try {
const result = spawnSync("pnpm", ["--filter", "@fusion/dashboard", "test", "--", aggregateAlias], {
cwd: workspaceRoot,
encoding: "utf8",
env: {
...process.env,
npm_config_ignore_scripts: "true",
FUSION_DASHBOARD_TEST_CONCURRENCY: "1",
FUSION_DASHBOARD_QUALITY_TEST_MODE: "1",
FUSION_DASHBOARD_QUALITY_RUNNER: fixtureRunnerPath,
FUSION_DASHBOARD_QUALITY_LANE_LOG: laneLogPath,
FUSION_DASHBOARD_QUALITY_FAIL_PROJECTS: "dashboard-app-quality-foundation-api,dashboard-api-quality",
},
});
const output = `${result.stdout}\n${result.stderr}`;
const startedLanes = [...output.matchAll(/^\[dashboard-quality\] start ([^:]+:[^:]+):/gm)].map((match) => match[1]);
const launchedProjects = existsSync(laneLogPath) ? readFileSync(laneLogPath, "utf8").trim().split("\n") : [];
expect(result.status, output).toBe(1);
expect(output).not.toContain("Unknown argument: --");
expect(startedLanes).toEqual(qualityLanes.map((qualityLane) => qualityLane.name));
expect(new Set(startedLanes).size).toBe(15);
expect(launchedProjects).toHaveLength(15);
expect(output).toContain("failed lane(s): app:foundation-api, api:curated");
expect(output).not.toContain("NOT RUN");
expect(output).not.toContain("all 15 lane(s) passed");
} finally {
rmSync(temporaryDirectory, { recursive: true, force: true });
}
});
it("keeps the no-argument package command fail-fast and labels unrun lanes unknown", async () => {
const temporaryDirectory = mkdtempSync(join(tmpdir(), "fn-8714-default-quality-runner-"));
const laneLogPath = join(temporaryDirectory, "lanes.log");
try {
const result = spawnSync("pnpm", ["--filter", "@fusion/dashboard", "test"], {
cwd: workspaceRoot,
encoding: "utf8",
env: {
...process.env,
npm_config_ignore_scripts: "true",
FUSION_DASHBOARD_TEST_CONCURRENCY: "1",
FUSION_DASHBOARD_QUALITY_TEST_MODE: "1",
FUSION_DASHBOARD_QUALITY_RUNNER: fixtureRunnerPath,
FUSION_DASHBOARD_QUALITY_LANE_LOG: laneLogPath,
FUSION_DASHBOARD_QUALITY_FAIL_PROJECTS: "dashboard-app-quality-foundation-api",
},
});
const output = `${result.stdout}\n${result.stderr}`;
const launchedProjects = readFileSync(laneLogPath, "utf8").trim().split("\n");
expect(result.status, output).toBe(1);
expect(launchedProjects).toEqual(["dashboard-app-quality-foundation-api"]);
expect(output).toContain("14 lane(s) were NOT RUN after the first failure — their status is UNKNOWN, not passing.");
expect(output).not.toContain("all 1 lane(s) passed");
} finally {
rmSync(temporaryDirectory, { recursive: true, force: true });
}
});
it("failFast:false runs EVERY lane and reports every failure, not just the first", async () => {
const { runQualityTests } = await loadModule();
const launched: string[] = [];
const result = await runQualityTests({
lanes: [lane("one"), lane("two"), lane("three")],
concurrency: 1,
failFast: false,
runner: async (qualityLane) => {
launched.push(qualityLane.name);
return { lane: qualityLane, ok: qualityLane.name === "two" };
},
});
// The half that matters: lane three ran even though lane one had already failed.
expect(launched).toEqual(["one", "two", "three"]);
expect(result).toMatchObject({ ok: false, completed: 3, skipped: 0 });
expect(result.failed.map((f: { lane: { name: string } }) => f.lane.name)).toEqual(["one", "three"]);
});
});