import { test, expect } from "vitest"; import { mkdtemp, rm, mkdir, writeFile, readFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { parse as yamlParse } from "yaml"; import { aggregateBudgets, parseTelemetryHistory, appendStepTelemetry, loadPipelineBudgets, lookupExpectedDurationMs, runPipelineBudgetGenerate, budgetsFilePath, type StepTelemetryRecord, type PipelineBudgetsFile, } from "../pipeline-budgets.ts"; import type { KernelCommandInput, KernelRuntimeContext } from "@warpgogol/werkstatt-shared/kernel"; import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io"; /* RFC-0270: unit tests for pipeline timing budget derivation, written before wiring the runner. Fixture-driven aggregation over a synthetic ndjson history (golden p50/p95/expectedDurationMs values), unparseable-line skip, empty-history no-op, and determinism for identical input. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. Fixtures stay inline and minimal so each case states its own setup. RFC-1311: Compass header sweep — declares the pipeline suite's contract for test-file scaffold coverage. */ // @ai-invariant fixtures must not couple to internals beyond the public surface under test function record(overrides: Partial): StepTelemetryRecord { return { pipeline: "packages.check", command: "manifest.contract.validate", app: null, durationMs: 100, timedOut: false, recordedAt: "2026-07-02T00:00:00.000Z", ...overrides, }; } test("aggregateBudgets: golden p50/p95/expectedDurationMs for a fixture sample", () => { // 10 samples: 100..1000ms in steps of 100. const records = Array.from({ length: 10 }, (_, i) => record({ durationMs: (i + 1) * 100 })); const budgets = aggregateBudgets(records); expect(budgets.length).toBe(1); const b = budgets[0]!; expect(b.sampleCount).toBe(10); expect(b.p50Ms).toBe(500); expect(b.p95Ms).toBe(1000); // expectedDurationMs = ceil(p95 * 1.5) rounded up to the nearest second = ceil(1500/1000)*1000 expect(b.expectedDurationMs).toBe(2000); }); test("aggregateBudgets: groups separately by (pipeline, command, app)", () => { const records = [ record({ app: "warpgogol-com", durationMs: 1000 }), record({ app: "nicaragua-projekt", durationMs: 2000 }), record({ pipeline: "build.check", durationMs: 3000 }), ]; const budgets = aggregateBudgets(records); expect(budgets.length).toBe(3); }); test("aggregateBudgets: deterministic output for identical input (sorted, repeatable)", () => { const records = [ record({ command: "b-command", durationMs: 500 }), record({ command: "a-command", durationMs: 200 }), ]; const first = aggregateBudgets(records); const second = aggregateBudgets([...records]); expect(first).toEqual(second); expect(first[0]?.command).toBe("a-command"); expect(first[1]?.command).toBe("b-command"); }); test("parseTelemetryHistory: skips unparseable lines and reports the count", () => { const raw = [ JSON.stringify(record({})), "not json at all", JSON.stringify({ pipeline: "x" }), // missing required fields JSON.stringify(record({ durationMs: 42 })), ].join("\n"); const { records, skipped } = parseTelemetryHistory(raw); expect(records.length).toBe(2); expect(skipped).toBe(2); }); test("parseTelemetryHistory: empty input yields no records and no skips", () => { const { records, skipped } = parseTelemetryHistory(""); expect(records).toEqual([]); expect(skipped).toBe(0); }); test("lookupExpectedDurationMs: exact app match wins over the app-agnostic budget", () => { const budgets: PipelineBudgetsFile = { meta: { schemaVersion: 1, deterministic: true, generatedAt: null, historyHash: "x" }, budgets: [ { pipeline: "p", command: "c", app: null, sampleCount: 1, p50Ms: 1, p95Ms: 1, expectedDurationMs: 1000, }, { pipeline: "p", command: "c", app: "warpgogol-com", sampleCount: 1, p50Ms: 1, p95Ms: 1, expectedDurationMs: 2000, }, ], }; expect(lookupExpectedDurationMs(budgets, "p", "c", "warpgogol-com")).toBe(2000); expect(lookupExpectedDurationMs(budgets, "p", "c", "nicaragua-projekt")).toBe(1000); expect(lookupExpectedDurationMs(null, "p", "c", "warpgogol-com")).toBe(undefined); }); // --------------------------------------------------------------------------- // Filesystem-backed integration // --------------------------------------------------------------------------- const logger = { section() {}, info() {}, warn() {}, error() {}, success() {}, getEvents() { return []; }, }; function ctx(root: string): KernelRuntimeContext { return { io: createDefaultIO().io, workspaceRoot: root, siteExplicit: false, logger, dryRun: false, outputFormat: "json", } as unknown as KernelRuntimeContext; } test("runPipelineBudgetGenerate: empty history is a no-op (exit 0, writes nothing)", async () => { const root = await mkdtemp(join(tmpdir(), "pipeline-budgets-")); try { const input = { argv: [], flags: {} } as unknown as KernelCommandInput; const result = await runPipelineBudgetGenerate(input, ctx(root)); expect(result.exitCode ?? 0).toBe(0); expect((result.data as { written: boolean }).written).toBe(false); } finally { await rm(root, { recursive: true, force: true }); } }); test("runPipelineBudgetGenerate: aggregates a real history file into the committed budgets file", async () => { const root = await mkdtemp(join(tmpdir(), "pipeline-budgets-")); try { const historyDir = join(root, "node_modules", ".cache", "werkstatt", "telemetry"); await mkdir(historyDir, { recursive: true }); const lines = Array.from({ length: 5 }, (_, i) => JSON.stringify(record({ durationMs: (i + 1) * 1000 })), ); await writeFile(join(historyDir, "steps.ndjson"), lines.join("\n") + "\n", "utf8"); const input = { argv: [], flags: {} } as unknown as KernelCommandInput; const result = await runPipelineBudgetGenerate(input, ctx(root)); expect(result.exitCode ?? 0).toBe(0); expect((result.data as { written: boolean }).written).toBe(true); const written = await readFile(budgetsFilePath(root), "utf8"); expect(written).toMatch(/GENERATED/); const parsed = yamlParse(written) as PipelineBudgetsFile; expect(parsed.budgets.length).toBe(1); expect(parsed.budgets[0]?.sampleCount).toBe(5); const loaded = await loadPipelineBudgets(root); expect(loaded).toBeTruthy(); expect(loaded?.budgets.length).toBe(1); } finally { await rm(root, { recursive: true, force: true }); } }); test("appendStepTelemetry: writes a record retrievable via parseTelemetryHistory", async () => { const root = await mkdtemp(join(tmpdir(), "pipeline-budgets-")); try { await appendStepTelemetry(root, record({ command: "x" })); await appendStepTelemetry(root, record({ command: "y" })); const raw = await readFile( join(root, "node_modules", ".cache", "werkstatt", "telemetry", "steps.ndjson"), "utf8", ); const { records, skipped } = parseTelemetryHistory(raw); expect(records.length).toBe(2); expect(skipped).toBe(0); } finally { await rm(root, { recursive: true, force: true }); } });