Files
codex-game-studio/tests/validation.test.ts
T

88 lines
4.6 KiB
TypeScript

import { rmSync, writeFileSync } from "node:fs";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, test } from "vitest";
import { freezeProject, initProject } from "../src/projects.js";
import { runValidation, validateProject } from "../src/validation.js";
describe("validation", () => {
test("fresh initialized projects validate and failures are explicit", () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-val-"));
for (const [name, engine, mode] of [
["Godot Val", "godot", "prototype"],
["Unity Val", "unity", "design"],
["Unreal Val", "ue5", "development"]
] as const) {
const { projectRoot } = initProject({ name, engine, mode, nonInteractive: true }, cwd);
expect(validateProject(projectRoot).filter((c) => c.status === "fail")).toEqual([]);
}
});
test("missing required project artifacts fail", () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-val-"));
const { projectRoot } = initProject({ name: "Broken Game", engine: "godot", mode: "prototype", nonInteractive: true }, cwd);
rmSync(path.join(projectRoot, "AGENTS.md"));
rmSync(path.join(projectRoot, "source", "project-broken-game", "project.godot"));
const failures = validateProject(projectRoot).filter((c) => c.status === "fail");
expect(failures.map((f) => f.id)).toContain("project.agents_md");
expect(failures.map((f) => f.id)).toContain("project.engine_file");
});
test("malformed AGENTS contract and prompt files fail validation", () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-val-"));
const { projectRoot } = initProject({ name: "Prompt Game", engine: "godot", mode: "prototype", nonInteractive: true }, cwd);
writeFileSync(path.join(projectRoot, "AGENTS.md"), "# Broken\n");
writeFileSync(path.join(projectRoot, ".codex", "prompts", "producer.md"), "");
const failures = validateProject(projectRoot).filter((c) => c.status === "fail");
expect(failures.map((f) => f.id)).toContain("codex.project.AGENTS.md.## Project Goal");
expect(failures.map((f) => f.id)).toContain("codex.prompt.producer");
});
test("empty timeline sections fail validation", () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-val-"));
const { projectRoot } = initProject({ name: "Timeline Game", engine: "godot", mode: "prototype", nonInteractive: true }, cwd);
writeFileSync(
path.join(projectRoot, "documentation", "production", "timeline.md"),
"# Timeline\n\nTBD\n\n# Milestones\n\n# Risks\n\n- Scope risk.\n\n# Next Validation Gate\n\nRun validation.\n"
);
const failures = validateProject(projectRoot).filter((c) => c.status === "fail");
expect(failures.map((f) => f.id)).toContain("project.timeline.# Milestones");
});
test("Unity validation fails when ProjectSettings marker is missing", () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-val-"));
const { projectRoot } = initProject({ name: "Broken Unity", engine: "unity", mode: "design", nonInteractive: true }, cwd);
rmSync(path.join(projectRoot, "source", "project-broken-unity", "ProjectSettings", "ProjectSettings.asset"));
const failures = validateProject(projectRoot).filter((c) => c.status === "fail");
expect(failures.map((f) => f.id)).toContain("project.engine_settings");
});
test("invalid studio json fails", () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-val-"));
const { projectRoot } = initProject({ name: "Stale Game", engine: "godot", mode: "prototype", nonInteractive: true }, cwd);
writeFileSync(path.join(projectRoot, ".codex", "studio.json"), "{ invalid json");
expect(validateProject(projectRoot)[0].status).toBe("fail");
});
test("freeze status-only changes keep project validation green", () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-val-"));
const { projectRoot } = initProject({ name: "Freeze Valid", engine: "godot", mode: "prototype", nonInteractive: true }, cwd);
freezeProject(projectRoot, cwd);
expect(validateProject(projectRoot).filter((c) => c.status === "fail")).toEqual([]);
});
test("repo validation reports Codex readiness hard failure when unavailable", async () => {
const old = process.env.CODEX_BIN;
process.env.CODEX_BIN = "/missing/codex";
try {
const result = await runValidation();
expect(result.checks.some((c) => c.id === "codex.cli" && c.status === "fail")).toBe(true);
expect(result.failed).toBe(true);
} finally {
if (old === undefined) delete process.env.CODEX_BIN;
else process.env.CODEX_BIN = old;
}
});
});