feat: 第二期 — Tier 2 场景级 mock + 错误/流程/依赖测试

This commit is contained in:
2026-06-09 15:52:01 +08:00
parent 8739a404f6
commit bb7d5e740c
5 changed files with 493 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
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 { assertAllTasksDone } from "./validators.ts";
import { scanChanges, scanArchives } from "../../src/core/scanner.ts";
import { assembleDiscussPrompt } from "../../src/core/assembler.ts";
import { validateChangeName } from "../../src/cli.ts";
describe("e2e: 全流程", () => {
let runner: ReturnType<typeof createRunner>;
beforeEach(async () => {
await setupTempDir();
runner = createRunner();
});
afterEach(async () => {
await cleanupTempDir();
});
it("完整四阶段流程discuss → plan → build → archive", async () => {
const config = await createFreshProject();
const discussPrompt = assembleDiscussPrompt(config);
expect(typeof discussPrompt).toBe("string");
expect(discussPrompt.length).toBeGreaterThan(0);
await runner.runPlan(getTempDir(), "full-flow", "design", config);
await runner.runPlan(getTempDir(), "full-flow", "task", config);
let changes = await scanChanges(getTempDir(), config);
expect(changes).toHaveLength(1);
expect(changes[0]!.name).toBe("full-flow");
expect(changes[0]!.planCompleted).toBe(true);
expect(changes[0]!.buildUnlocked).toBe(true);
const buildResult = await runner.runBuild(getTempDir(), "full-flow", config);
expect(buildResult.files.length).toBeGreaterThan(0);
changes = await scanChanges(getTempDir(), config);
expect(changes).toHaveLength(1);
assertAllTasksDone(changes[0]!);
await runner.runArchive(getTempDir(), "full-flow", config);
expect(changeFileExists("full-flow", "task.md")).toBe(false);
const archives = await scanArchives(getTempDir());
expect(archives.length).toBeGreaterThanOrEqual(1);
changes = await scanChanges(getTempDir(), config);
expect(changes.find((c) => c.name === "full-flow")).toBeUndefined();
});
it("多变更并行互不干扰", async () => {
const config = await createFreshProject();
await runner.runPlan(getTempDir(), "变更A", "design", config);
await runner.runPlan(getTempDir(), "变更A", "task", config);
await runner.runPlan(getTempDir(), "变更B", "design", config);
await runner.runPlan(getTempDir(), "变更B", "task", config);
let changes = await scanChanges(getTempDir(), config);
expect(changes).toHaveLength(2);
const changeA = changes.find((c) => c.name === "变更A");
const changeB = changes.find((c) => c.name === "变更B");
expect(changeA).toBeDefined();
expect(changeB).toBeDefined();
expect(changeA!.planCompleted).toBe(true);
expect(changeB!.planCompleted).toBe(true);
await runner.runBuild(getTempDir(), "变更A", config);
await runner.runBuild(getTempDir(), "变更B", config);
changes = await scanChanges(getTempDir(), config);
expect(changes).toHaveLength(2);
assertAllTasksDone(changes.find((c) => c.name === "变更A")!);
assertAllTasksDone(changes.find((c) => c.name === "变更B")!);
expect(changes[0]!.taskProgress?.completed).toBe(changes[0]!.taskProgress?.total);
expect(changes[1]!.taskProgress?.completed).toBe(changes[1]!.taskProgress?.total);
});
describe("变更名非法字符拒绝", () => {
it("空字符串抛出错误", () => {
expect(() => validateChangeName("")).toThrow();
});
it("包含 / 抛出错误", () => {
expect(() => validateChangeName("变更/A")).toThrow();
});
it("包含 . 抛出错误", () => {
expect(() => validateChangeName("变更.A")).toThrow();
});
it("合法中文名称不抛出错误", () => {
expect(() => validateChangeName("变更名")).not.toThrow();
});
it("合法英文名称不抛出错误", () => {
expect(() => validateChangeName("my-change")).not.toThrow();
});
it("合法短横线名称不抛出错误", () => {
expect(() => validateChangeName("abc-def-xyz")).not.toThrow();
});
});
});