import { describe, it, expect, beforeEach, afterEach } from "bun:test"; import { CommandLevelRunner } from "./agent-mock.ts"; import { ScenarioRunner } from "./agent-scenario.ts"; import type { PlanOverride } from "./agent-scenario.ts"; import { setupTempDir, cleanupTempDir, getTempDir, createFreshProject, writeDoc, changeFileExists, } from "./fixtures.ts"; import { assertNoFile } from "./validators.ts"; import { scanChanges } from "../../src/core/scanner.ts"; import { loadConfig } from "../../src/core/config.ts"; import { mkdir, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { readFileSync } from "node:fs"; const brokenPlan: PlanOverride = async (_projectDir, changeName, docName, _cfg) => { const wrongDir = join(getTempDir(), "wrong-dir"); await mkdir(wrongDir, { recursive: true }); await writeFile(join(wrongDir, `${docName}.md`), "some content\n"); return { files: [], missed: [`${docName}.md`], }; }; const emptyPlan: PlanOverride = async (_projectDir, _changeName, _docName, _cfg) => { return { files: [] }; }; describe("e2e: 错误场景", () => { beforeEach(async () => { await setupTempDir(); }); afterEach(async () => { await cleanupTempDir(); }); it("agent 文件写错路径", async () => { const config = await createFreshProject(); const errorRunner = new ScenarioRunner(new CommandLevelRunner(), { plan: brokenPlan }); const result = await errorRunner.runPlan(getTempDir(), "wrong-path", "design", config); expect(result.files).toHaveLength(0); expect(result.missed).toEqual(["design.md"]); assertNoFile(getTempDir(), ".rune/changes/wrong-path/design.md"); }); it("agent 跳过依赖文档", async () => { const config = await createFreshProject(); const skipDepsPlan: PlanOverride = async (_projectDir, changeName, docName, _cfg) => { const standardRunner = new CommandLevelRunner(); if (docName === "task") { return standardRunner.runPlan(getTempDir(), changeName, "task", config); } if (docName === "design") { return standardRunner.runPlan(getTempDir(), changeName, "design", config); } return { files: [] }; }; const errorRunner = new ScenarioRunner(new CommandLevelRunner(), { plan: skipDepsPlan }); await errorRunner.runPlan(getTempDir(), "skip-deps", "task", config); expect(changeFileExists("skip-deps", "task.md")).toBe(true); let changes = await scanChanges(getTempDir(), config); expect(changes).toHaveLength(1); const taskDoc = changes[0]!.documents.find((d) => d.name === "task"); expect(taskDoc).toBeDefined(); expect(taskDoc!.completed).toBe(true); expect(taskDoc!.dependMet).toBe(false); const designDoc = changes[0]!.documents.find((d) => d.name === "design"); expect(designDoc).toBeDefined(); expect(designDoc!.completed).toBe(false); await errorRunner.runPlan(getTempDir(), "skip-deps", "design", config); expect(changeFileExists("skip-deps", "design.md")).toBe(true); changes = await scanChanges(getTempDir(), config); const taskDoc2 = changes[0]!.documents.find((d) => d.name === "task"); expect(taskDoc2!.dependMet).toBe(true); }); it("agent 创建空文件", async () => { const config = await createFreshProject(); await writeDoc("empty-file", "design", ""); const errorRunner = new ScenarioRunner(new CommandLevelRunner(), { plan: emptyPlan }); const result = await errorRunner.runPlan(getTempDir(), "empty-file", "design", config); expect(changeFileExists("empty-file", "design.md")).toBe(true); const filePath = join(getTempDir(), ".rune/changes/empty-file/design.md"); const content = readFileSync(filePath, "utf-8"); expect(content).toBe(""); expect(result.files).toHaveLength(0); }); it("config.yaml 语法错误", async () => { const runeDir = join(getTempDir(), ".rune"); await mkdir(runeDir, { recursive: true }); const invalidYaml = "stages\n plan documents\n"; await writeFile(join(runeDir, "config.yaml"), invalidYaml, "utf-8"); const loadedConfig = await loadConfig(getTempDir()); expect(loadedConfig).toBeDefined(); expect(loadedConfig.stages.plan).toBeDefined(); expect(loadedConfig.stages.plan!.documents).toBeDefined(); }); });