Files
Rune-Spec/tests/core/assembler.test.ts

164 lines
5.7 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import { mkdir, writeFile, rm } from "node:fs/promises";
import { join } from "node:path";
import {
assembleDiscussPrompt,
assemblePlanPrompt,
assembleBuildPrompt,
assembleArchivePrompt,
} from "../../src/core/assembler.ts";
import type { RuneConfig } from "../../src/types.ts";
import { defaultConfig } from "../../src/defaults/config.ts";
const TMP_DIR = join(import.meta.dir, "__tmp_assembler_test__");
beforeEach(async () => {
await mkdir(TMP_DIR, { recursive: true });
});
afterEach(async () => {
await rm(TMP_DIR, { recursive: true, force: true });
});
describe("assembleDiscussPrompt", () => {
it("返回默认 discuss 提示词", () => {
const prompt = assembleDiscussPrompt(defaultConfig);
expect(prompt).toBeTruthy();
expect(prompt).toContain("软件架构师");
});
it("返回自定义 discuss 提示词", () => {
const config: RuneConfig = {
stages: { discuss: { prompt: "自定义讨论" } },
};
const prompt = assembleDiscussPrompt(config);
expect(prompt).toBe("自定义讨论");
});
});
describe("assemblePlanPrompt", () => {
it("包含指定文档名称和提示词", async () => {
const prompt = await assemblePlanPrompt(defaultConfig, TMP_DIR, "user-auth", "design");
expect(prompt).toContain("user-auth");
expect(prompt).toContain("design");
expect(prompt).not.toContain("task");
});
it("包含已有文档内容(重复 plan 场景)", async () => {
const changeDir = join(TMP_DIR, ".rune", "changes", "user-auth");
await mkdir(changeDir, { recursive: true });
await writeFile(join(changeDir, "design.md"), "# 已有设计");
const prompt = await assemblePlanPrompt(defaultConfig, TMP_DIR, "user-auth", "design");
expect(prompt).toContain("已有设计");
expect(prompt).toContain("在此基础上修订");
});
it("替换模板中的 {{change-name}}", async () => {
const prompt = await assemblePlanPrompt(defaultConfig, TMP_DIR, "user-auth", "design");
expect(prompt).toContain("user-auth 设计文档");
expect(prompt).not.toContain("{{change-name}}");
});
it("包含依赖说明(有依赖时)", async () => {
const config: RuneConfig = {
stages: {
plan: {
documents: [
{ name: "design", prompt: "生成设计" },
{ name: "task", prompt: "生成任务", depend: ["design"] },
],
},
},
};
const prompt = await assemblePlanPrompt(config, TMP_DIR, "user-auth", "task");
expect(prompt).toContain("依赖说明");
expect(prompt).toContain("design.md");
});
it("无依赖时不包含依赖说明", async () => {
const prompt = await assemblePlanPrompt(defaultConfig, TMP_DIR, "user-auth", "design");
expect(prompt).not.toContain("依赖说明");
});
it("依赖说明标注完成状态", async () => {
const changeDir = join(TMP_DIR, ".rune", "changes", "user-auth");
await mkdir(changeDir, { recursive: true });
await writeFile(join(changeDir, "design.md"), "# 设计");
const config: RuneConfig = {
stages: {
plan: {
documents: [
{ name: "design", prompt: "生成设计" },
{ name: "task", prompt: "生成任务", depend: ["design"] },
],
},
},
};
const prompt = await assemblePlanPrompt(config, TMP_DIR, "user-auth", "task");
expect(prompt).toContain("已完成");
});
it("使用自定义 plan 配置", async () => {
const config: RuneConfig = {
stages: {
plan: {
documents: [
{
name: "spec",
prompt: "生成规格",
template: "# {{change-name}} 规格",
},
],
},
},
};
const prompt = await assemblePlanPrompt(config, TMP_DIR, "my-feature", "spec");
expect(prompt).toContain("spec");
expect(prompt).toContain("my-feature 规格");
expect(prompt).not.toContain("design");
});
});
describe("assembleBuildPrompt", () => {
it("包含待执行任务列表", async () => {
const changeDir = join(TMP_DIR, ".rune", "changes", "user-auth");
await mkdir(changeDir, { recursive: true });
await writeFile(join(changeDir, "task.md"), `- [x] 任务一\n- [ ] 任务二\n- [ ] 任务三`);
const prompt = await assembleBuildPrompt(defaultConfig, TMP_DIR, "user-auth");
expect(prompt).toContain("任务二");
expect(prompt).toContain("待执行任务");
expect(prompt).toContain("共 2 项");
});
it("所有任务完成时提示可归档", async () => {
const changeDir = join(TMP_DIR, ".rune", "changes", "user-auth");
await mkdir(changeDir, { recursive: true });
await writeFile(join(changeDir, "task.md"), `- [x] 任务一\n- [x] 任务二`);
const prompt = await assembleBuildPrompt(defaultConfig, TMP_DIR, "user-auth");
expect(prompt).toContain("已完成");
expect(prompt).toContain("归档");
});
it("task.md 不存在时抛出 CommandError 并附带提示", async () => {
try {
await assembleBuildPrompt(defaultConfig, TMP_DIR, "nonexistent");
expect.unreachable();
} catch (e: any) {
expect(e.message).toContain("尚未完成规划");
expect(e.message).toContain("nonexistent");
expect(e.hint).toContain("rune plan");
}
});
});
describe("assembleArchivePrompt", () => {
it("返回归档提示词", async () => {
const changeDir = join(TMP_DIR, ".rune", "changes", "user-auth");
await mkdir(changeDir, { recursive: true });
const prompt = await assembleArchivePrompt(defaultConfig, TMP_DIR, "user-auth");
expect(prompt).toContain("user-auth");
expect(prompt).toContain("归档");
});
});