Files
codex-game-studio/tests/documentation-impact.test.ts

104 lines
4.8 KiB
TypeScript

import { execFileSync } from "node:child_process";
import { cpSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, test } from "node:test";
import { expect } from "expect";
import { documentationImpactChecks, parseDocumentationImpactRecord } from "../src/documentation-impact.js";
import { initProject } from "../src/projects.js";
import { validateProject } from "../src/validation.js";
function tempProject(): string {
const root = mkdtempSync(path.join(tmpdir(), "ogs-doc-impact-"));
mkdirSync(path.join(root, "production", "session-state"), { recursive: true });
mkdirSync(path.join(root, "src"), { recursive: true });
mkdirSync(path.join(root, "design"), { recursive: true });
mkdirSync(path.join(root, "docs"), { recursive: true });
return root;
}
function writeImpact(root: string, body: string): void {
writeFileSync(path.join(root, "production", "session-state", "active.md"), body);
}
function initializedGitProject(): string {
const root = mkdtempSync(path.join(tmpdir(), "ogs-doc-impact-git-"));
for (const entry of ["AGENTS.md", ".codex/agents", ".codex/workflows", ".agents/skills"]) {
cpSync(path.join(process.cwd(), entry), path.join(root, entry), { recursive: true });
}
initProject({ name: "Documentation Impact Game", engine: "godot", mode: "prototype", nonInteractive: true }, root);
const git = (args: string[]) => execFileSync("git", args, { cwd: root, encoding: "utf8" });
git(["init"]);
git(["config", "user.email", "tests@example.com"]);
git(["config", "user.name", "Test Runner"]);
git(["add", "."]);
git(["commit", "-m", "initial game"]);
return root;
}
describe("documentation impact", () => {
test("parses a bounded updated-document decision", () => {
expect(
parseDocumentationImpactRecord("# Active Feature\n\n## Documentation Impact\n\n- Decision: updated\n- Reason: Movement rules changed.\n- Documents: design/gdd.md, docs/architecture/movement.md\n")
).toEqual({ decision: "updated", reason: "Movement rules changed.", documents: ["design/gdd.md", "docs/architecture/movement.md"] });
});
test("accepts a changed game source file with a changed named document", () => {
const root = tempProject();
writeFileSync(path.join(root, "design", "gdd.md"), "# GDD\n");
writeImpact(root, "## Documentation Impact\n\n- Decision: updated\n- Reason: Movement rules changed.\n- Documents: design/gdd.md\n");
expect(documentationImpactChecks(root, { changedPaths: ["src/player.gd", "design/gdd.md"] }).filter((check) => check.status === "fail")).toEqual([]);
});
test("requires an active-session decision for functional changes", () => {
const root = tempProject();
expect(documentationImpactChecks(root, { changedPaths: ["src/player.gd"] })).toContainEqual(
expect.objectContaining({ id: "project.documentation_impact.record", status: "fail" })
);
});
test("requires a reason for no-update decisions", () => {
const root = tempProject();
writeImpact(root, "## Documentation Impact\n\n- Decision: no-update\n- Reason:\n- Documents: none\n");
expect(documentationImpactChecks(root, { changedPaths: ["src/refactor.gd"] })).toContainEqual(
expect.objectContaining({ id: "project.documentation_impact.reason", status: "fail" })
);
});
test("requires updated documents to differ from the selected base", () => {
const root = tempProject();
writeFileSync(path.join(root, "design", "gdd.md"), "# GDD\n");
writeImpact(root, "## Documentation Impact\n\n- Decision: updated\n- Reason: Movement rules changed.\n- Documents: design/gdd.md\n");
expect(documentationImpactChecks(root, { changedPaths: ["src/player.gd"] })).toContainEqual(
expect.objectContaining({ id: "project.documentation_impact.document.design/gdd.md", status: "fail" })
);
});
test("does not require a record for documentation-only changes", () => {
const root = tempProject();
expect(documentationImpactChecks(root, { changedPaths: ["design/gdd.md", "docs/changelog.md"] }).filter((check) => check.status === "fail")).toEqual([]);
});
test("reports an unavailable Git base instead of silently passing", () => {
const root = tempProject();
expect(documentationImpactChecks(root, { base: "missing-base" })).toContainEqual(
expect.objectContaining({ id: "project.documentation_impact.base", status: "fail" })
);
});
test("project validation includes documentation impact checks when a base is supplied", () => {
const root = initializedGitProject();
writeFileSync(path.join(root, "src", "player.gd"), "extends Node\n");
expect(validateProject(root, { documentationBase: "HEAD" })).toContainEqual(
expect.objectContaining({ id: "project.documentation_impact.record", status: "fail" })
);
});
});