/*
RFC-1027: unit tests for remediation.hint.validate and remediation.catalog.generate
commands, plus aggregateRemediationHints helper.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1027: initial tests for remediation commands and hint aggregation.
*/
import { test, expect } from "vitest";
import { mkdtemp, rm, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { runRemediationHintValidate } from "../remediation-hint-validate.ts";
import {
runRemediationCatalogGenerate,
type RemediationCatalogGenerateResult,
} from "../remediation-catalog-generate.ts";
import { buildActualState } from "../../runtime/reconciler.ts";
import type {
CheckResult,
KernelCommandInput,
KernelRuntimeContext,
KernelCommandDefinition,
} from "@warpgogol/werkstatt-shared/kernel";
import type { ModuleExport } from "@warpgogol/werkstatt-shared/kernel";
import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
function makeCommand(
name: string,
overrides: Partial = {},
): KernelCommandDefinition {
return {
name,
description: `Test command ${name}`,
scope: "workspace",
flags: {},
execute: async () => ({ exitCode: 0, summary: "ok" }),
modulePath: "packages/werkstatt-engine/src/remediation/tests/remediation.test.ts",
...overrides,
};
}
function makeContext(
commands: KernelCommandDefinition[],
workspaceRoot: string,
): KernelRuntimeContext {
const mod: ModuleExport = {
name: "test-module",
version: "1.0.0",
declarations: [],
commands,
pipelines: [],
};
const actualState = buildActualState([mod]);
return {
io: createDefaultIO().io,
workspaceRoot,
actualState,
dryRun: false,
} as unknown as KernelRuntimeContext;
}
const baseInput: KernelCommandInput = {
argv: [],
flags: {},
};
// ---------------------------------------------------------------------------
// remediation.hint.validate
// ---------------------------------------------------------------------------
test("hint.validate: warning mode — uncovered rule produces REMEDIATION-01 warning", async () => {
const dir = await mkdtemp(join(tmpdir(), "rem-test-"));
try {
const commands = [
makeCommand("content.links.validate", { contract: "content", rules: ["XLINK-01"] }),
];
const context = makeContext(commands, dir);
const result = await runRemediationHintValidate(
{ ...baseInput, flags: { mode: "warning" } },
context,
);
const data = result.data as CheckResult;
expect(result.exitCode).toBe(0);
const remediation01 = data.diagnostics.filter((d) => d.ruleId === "REMEDIATION-01");
expect(remediation01).toHaveLength(1);
expect(remediation01[0].severity).toBe("warning");
expect(remediation01[0].message).toContain("XLINK-01");
} finally {
await rm(dir, { recursive: true });
}
});
test("hint.validate: strict mode — covered rule produces no REMEDIATION-01", async () => {
const dir = await mkdtemp(join(tmpdir(), "rem-test-"));
try {
const commands = [
makeCommand("seo.meta.validate", {
contract: "seo",
rules: ["SEO-RUNTIME.CANONICAL-MISMATCH"],
}),
];
const context = makeContext(commands, dir);
const result = await runRemediationHintValidate(
{ ...baseInput, flags: { mode: "strict" } },
context,
);
const data = result.data as CheckResult;
expect(result.exitCode).toBe(0);
const remediation01 = data.diagnostics.filter((d) => d.ruleId === "REMEDIATION-01");
expect(remediation01).toHaveLength(0);
} finally {
await rm(dir, { recursive: true });
}
});
test("hint.validate: strict mode — unknown rule is blocking error", async () => {
const dir = await mkdtemp(join(tmpdir(), "rem-test-"));
try {
const commands = [
makeCommand("custom.thing.validate", { contract: "custom", rules: ["CUSTOM-UNKNOWN"] }),
];
const context = makeContext(commands, dir);
const result = await runRemediationHintValidate(
{ ...baseInput, flags: { mode: "strict" } },
context,
);
const data = result.data as CheckResult;
expect(result.exitCode).toBe(1);
expect(data.diagnostics[0].ruleId).toBe("REMEDIATION-01");
expect(data.diagnostics[0].severity).toBe("error");
} finally {
await rm(dir, { recursive: true });
}
});
test("hint.validate: REMEDIATION-02 for stale catalog entries", async () => {
const dir = await mkdtemp(join(tmpdir(), "rem-test-"));
try {
const commands: KernelCommandDefinition[] = [];
const context = makeContext(commands, dir);
const result = await runRemediationHintValidate(baseInput, context);
expect(result.exitCode).toBe(0);
const data = result.data as CheckResult;
const staleWarnings = data.diagnostics.filter((d) => d.ruleId === "REMEDIATION-02");
expect(staleWarnings.length).toBeGreaterThan(0);
} finally {
await rm(dir, { recursive: true });
}
});
// ---------------------------------------------------------------------------
// remediation.catalog.generate
// ---------------------------------------------------------------------------
test("catalog.generate: writes YAML with catalog entries and uncovered ruleIds", async () => {
const dir = await mkdtemp(join(tmpdir(), "rem-cat-"));
try {
const commands = [
makeCommand("content.links.validate", { contract: "content", rules: ["XLINK-01"] }),
makeCommand("seo.meta.validate", {
contract: "seo",
rules: ["SEO-RUNTIME.CANONICAL-MISMATCH"],
}),
];
const context = makeContext(commands, dir);
const result = await runRemediationCatalogGenerate(baseInput, context);
const data = result.data as RemediationCatalogGenerateResult;
expect(result.exitCode).toBe(0);
expect(data.written).toBe(true);
expect(data.entryCount).toBeGreaterThan(0);
const outputPath = join(dir, "docs", "remediation-catalog.generated.yaml");
const content = await readFile(outputPath, "utf8");
expect(content).toContain("GEN-FILES-01");
expect(content).toContain("SEO-RUNTIME.CANONICAL-MISMATCH");
expect(content).toContain("XLINK-01");
} finally {
await rm(dir, { recursive: true });
}
});