Files
Rune-Spec/tests/integration/flow.test.ts

130 lines
4.7 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import { existsSync } from "node:fs";
import { mkdir, writeFile, rm, readFile, rename } from "node:fs/promises";
import { join } from "node:path";
import { runInit } from "../../src/commands/init.ts";
import { loadConfig, getChangeDir, getArchiveDir } from "../../src/core/config.ts";
import {
assembleDiscussPrompt,
assemblePlanPrompt,
assembleBuildPrompt,
assembleArchivePrompt,
} from "../../src/core/assembler.ts";
import { scanChanges, scanArchives } from "../../src/core/scanner.ts";
const TMP_DIR = join(import.meta.dir, "__tmp_flow_test__");
beforeEach(async () => {
await mkdir(TMP_DIR, { recursive: true });
});
afterEach(async () => {
await rm(TMP_DIR, { recursive: true, force: true });
});
describe("完整 SDD 流程", () => {
it("init → discuss → plan → build → archive 完整流程", async () => {
await runInit(TMP_DIR, ["opencode"]);
expect(existsSync(join(TMP_DIR, ".rune", "config.yaml"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".opencode", "commands", "discuss.md"))).toBe(true);
const config = await loadConfig(TMP_DIR);
const discussPrompt = assembleDiscussPrompt(config);
expect(discussPrompt).toContain("软件架构师");
const changeName = "user-auth";
await mkdir(getChangeDir(TMP_DIR, changeName), { recursive: true });
const planPrompt = await assemblePlanPrompt(config, TMP_DIR, changeName);
expect(planPrompt).toContain("user-auth");
const changeDir = getChangeDir(TMP_DIR, changeName);
await writeFile(join(changeDir, "design.md"), "# 用户认证设计\n\n## 背景\n需要用户登录功能");
await writeFile(join(changeDir, "task.md"), "- [ ] 实现登录 API\n- [ ] 编写登录测试");
const changes = await scanChanges(TMP_DIR);
expect(changes).toHaveLength(1);
expect(changes[0].name).toBe("user-auth");
expect(changes[0].taskProgress).toEqual({ completed: 0, total: 2 });
const buildPrompt = await assembleBuildPrompt(config, TMP_DIR, changeName);
expect(buildPrompt).toContain("实现登录 API");
expect(buildPrompt).toContain("共 2 项");
await writeFile(join(changeDir, "task.md"), "- [x] 实现登录 API\n- [x] 编写登录测试");
const updatedChanges = await scanChanges(TMP_DIR);
expect(updatedChanges[0].taskProgress).toEqual({ completed: 2, total: 2 });
const buildPrompt2 = await assembleBuildPrompt(config, TMP_DIR, changeName);
expect(buildPrompt2).toContain("已完成");
const archivePrompt = assembleArchivePrompt(config, changeName);
expect(archivePrompt).toContain("归档");
const today = new Date().toISOString().slice(0, 10);
const src = getChangeDir(TMP_DIR, changeName);
const dest = join(getArchiveDir(TMP_DIR), `${today}-${changeName}`);
await mkdir(join(TMP_DIR, ".rune", "archive"), { recursive: true });
await rename(src, dest);
expect(existsSync(dest)).toBe(true);
expect(existsSync(src)).toBe(false);
const archives = await scanArchives(TMP_DIR);
expect(archives).toContain(`${today}-${changeName}`);
const postArchiveChanges = await scanChanges(TMP_DIR);
expect(postArchiveChanges).toHaveLength(0);
});
it("多变更并行", async () => {
await runInit(TMP_DIR, ["opencode"]);
const config = await loadConfig(TMP_DIR);
for (const name of ["auth", "payment"]) {
const changeDir = getChangeDir(TMP_DIR, name);
await mkdir(changeDir, { recursive: true });
await writeFile(join(changeDir, "task.md"), `- [ ] ${name} 任务`);
}
const changes = await scanChanges(TMP_DIR);
expect(changes).toHaveLength(2);
const authPrompt = await assembleBuildPrompt(config, TMP_DIR, "auth");
expect(authPrompt).toContain("auth 任务");
const paymentPrompt = await assembleBuildPrompt(config, TMP_DIR, "payment");
expect(paymentPrompt).toContain("payment 任务");
});
it("自定义配置覆盖默认配置", async () => {
await runInit(TMP_DIR, ["opencode"]);
await writeFile(
join(TMP_DIR, ".rune", "config.yaml"),
`stages:
discuss:
prompt: 自定义讨论
plan:
documents:
- name: spec
prompt: 生成规格文档
template: "# {{change-name}} 规格"
`,
);
const config = await loadConfig(TMP_DIR);
const discussPrompt = assembleDiscussPrompt(config);
expect(discussPrompt).toBe("自定义讨论");
await mkdir(getChangeDir(TMP_DIR, "test"), { recursive: true });
const planPrompt = await assemblePlanPrompt(config, TMP_DIR, "test");
expect(planPrompt).toContain("spec");
expect(planPrompt).toContain("test 规格");
expect(planPrompt).not.toContain("design");
expect(config.stages.build).toBeDefined();
expect(config.stages.archive).toBeDefined();
});
});