From 6214eedf4ddce82706099242124da50fbcb95887 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 9 Jun 2026 15:17:09 +0800 Subject: [PATCH] =?UTF-8?q?test:=20plan=20=E9=98=B6=E6=AE=B5=E7=AB=AF?= =?UTF-8?q?=E5=88=B0=E7=AB=AF=E6=B5=8B=E8=AF=95=EF=BC=888=20=E4=B8=AA?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/agent/e2e-plan.test.ts | 132 +++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tests/agent/e2e-plan.test.ts diff --git a/tests/agent/e2e-plan.test.ts b/tests/agent/e2e-plan.test.ts new file mode 100644 index 0000000..fc88411 --- /dev/null +++ b/tests/agent/e2e-plan.test.ts @@ -0,0 +1,132 @@ +import { describe, it, expect, beforeEach, afterEach } from "bun:test"; +import { createRunner } from "./tier1-command.ts"; +import { + setupTempDir, + cleanupTempDir, + getTempDir, + createFreshProject, + changeFileExists, +} from "./fixtures.ts"; +import { assertDocCreated, assertDocContains, assertConfigInvalid } from "./validators.ts"; +import { scanChanges } from "../../src/core/scanner.ts"; +import type { RuneConfig } from "../../src/types.ts"; + +describe("e2e: plan 阶段", () => { + let runner: ReturnType; + + beforeEach(async () => { + await setupTempDir(); + runner = createRunner(); + }); + + afterEach(async () => { + await cleanupTempDir(); + }); + + it("单文档按模板生成在正确路径", async () => { + const config = await createFreshProject(); + const result = await runner.runPlan(getTempDir(), "user-auth", "design", config); + + expect(result.files).toContain("design.md"); + assertDocCreated(getTempDir(), "user-auth", "design"); + assertDocContains(getTempDir(), "user-auth", "design", "user-auth"); + }); + + it("多文档无依赖时均生成", async () => { + const config = await createFreshProject(); + await runner.runPlan(getTempDir(), "my-change", "design", config); + await runner.runPlan(getTempDir(), "my-change", "task", config); + + expect(changeFileExists("my-change", "design.md")).toBe(true); + expect(changeFileExists("my-change", "task.md")).toBe(true); + + const changes = await scanChanges(getTempDir(), config); + expect(changes).toHaveLength(1); + expect(changes[0]!.planCompleted).toBe(true); + expect(changes[0]!.buildUnlocked).toBe(true); + }); + + it("模板变量 {{change-name}} 被替换", async () => { + const config = await createFreshProject(); + await runner.runPlan(getTempDir(), "用户-login", "design", config); + + assertDocContains(getTempDir(), "用户-login", "design", "用户-login 设计文档"); + }); + + it("使用自定义 plan 配置(单文档 spec)", async () => { + const config: RuneConfig = { + stages: { + plan: { + documents: [{ name: "spec", prompt: "生成规格", template: "# {{change-name}} 规格\n" }], + }, + }, + }; + await createFreshProject(["opencode"]); + const result = await runner.runPlan(getTempDir(), "my-feature", "spec", config); + + expect(result.files).toContain("spec.md"); + assertDocContains(getTempDir(), "my-feature", "spec", "my-feature 规格"); + }); + + it("多文档有依赖时 planCompleted 全完成后才为 true", async () => { + const config = await createFreshProject(); + + await runner.runPlan(getTempDir(), "dep-test", "design", config); + + let changes = await scanChanges(getTempDir(), config); + expect(changes[0]!.planCompleted).toBe(false); + + await runner.runPlan(getTempDir(), "dep-test", "task", config); + + changes = await scanChanges(getTempDir(), config); + expect(changes[0]!.planCompleted).toBe(true); + expect(changes[0]!.buildUnlocked).toBe(true); + }); + + it("已有变更再次 plan 另一个文档(重复 plan)", async () => { + const config: RuneConfig = { + stages: { + plan: { + documents: [ + { name: "design", prompt: "生成设计", template: "# 设计 #\n" }, + { name: "task", prompt: "生成任务", template: "# 任务 #\n" }, + ], + }, + }, + }; + + await runner.runPlan(getTempDir(), "multi", "design", config); + assertDocCreated(getTempDir(), "multi", "design"); + + const result = await runner.runPlan(getTempDir(), "multi", "task", config); + expect(result.files).toContain("task.md"); + + const changes = await scanChanges(getTempDir(), config); + expect(changes[0]!.planCompleted).toBe(true); + }); + + it("循环依赖配置被校验拒绝", () => { + const config: RuneConfig = { + stages: { + plan: { + documents: [ + { name: "a", prompt: "a", depend: ["b"] }, + { name: "b", prompt: "b", depend: ["a"] }, + ], + }, + }, + }; + assertConfigInvalid(config); + }); + + it("自依赖配置被校验拒绝", () => { + const config: RuneConfig = { + stages: { + plan: { + documents: [{ name: "design", prompt: "设计", depend: ["design"] }], + }, + }, + }; + assertConfigInvalid(config); + }); +});