🎉 init: 小龙的工作空间

This commit is contained in:
大海
2026-06-06 10:40:48 +08:00
commit a188ee1426
3201 changed files with 231817 additions and 0 deletions
+703
View File
@@ -0,0 +1,703 @@
/**
* Architecture Agent Test — SF-02
*
* 验证场景:
* 1. 电商小程序 → 技术选型(小程序 + 支付 + 物流)
* 2. 企业 OA → 审批/考勤架构
* 3. 教育平台 → 课程系统架构
* 4. 笔记应用 → 轻量架构
* 5. 宠物管理 → 完整架构生成
* 6. 空/invalid PRD → 错误处理
* 7. 通用 PRD → 降级处理
* 8. 架构图生成
* 9. 数据库设计
* 10. API 设计
* 11. 目录结构
* 12. 模块分解
* 13. 数据流
* 14. CLI 端到端
* 15. 集成 SF-01
*/
import { describe, it, before } from "node:test";
import assert from "node:assert/strict";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync, rmSync, readFileSync } from "node:fs";
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");
const FIXTURES_DIR = resolve(WORKSPACE, "test/fixtures/architecture-agent");
const ECO_PRJ = resolve(FIXTURES_DIR, "ecommerce-prd.json");
const ENT_PRJ = resolve(FIXTURES_DIR, "enterprise-prd.json");
const EDU_PRJ = resolve(FIXTURES_DIR, "education-prd.json");
const NOTE_PRJ = resolve(FIXTURES_DIR, "note-prd.json");
const EMPTY_PRJ = resolve(FIXTURES_DIR, "empty-prd.json");
const GENERIC_PRJ = resolve(FIXTURES_DIR, "generic-prd.json");
let generateArchitecture, resolveTechStack, generateArchDiagram;
let generateDataFlows, decomposeModules, generateDatabaseSchema;
let designAPI, generateDirectoryStructure;
let loadPRD, writeArchitecture;
before(async () => {
const mod = await import("../scripts/architecture-agent.mjs");
generateArchitecture = mod.generateArchitecture;
resolveTechStack = mod.resolveTechStack;
generateArchDiagram = mod.generateArchDiagram;
generateDataFlows = mod.generateDataFlows;
decomposeModules = mod.decomposeModules;
generateDatabaseSchema = mod.generateDatabaseSchema;
designAPI = mod.designAPI;
generateDirectoryStructure = mod.generateDirectoryStructure;
loadPRD = mod.loadPRD;
writeArchitecture = mod.writeArchitecture;
});
// ═══════════════════════════════════════════════════════
// 1 — 电商小程序架构
// ═══════════════════════════════════════════════════════
describe("1 — 电商小程序架构", () => {
it("技术选型为小程序栈", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.techStack.frontend.includes("uni-app") || arch.techStack.frontend.includes("小程序"));
assert.ok(arch.techStack.considerations.some(c => c.includes("支付")));
assert.ok(arch.techStack.considerations.some(c => c.includes("物流")));
});
it("包含订单和物流表", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
const tableNames = arch.databaseSchema.map(t => t.table);
assert.ok(tableNames.includes("orders"));
assert.ok(tableNames.includes("logistics"));
assert.ok(tableNames.includes("products"));
assert.ok(tableNames.includes("users"));
});
it("订单表有明细关联", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
const orderItems = arch.databaseSchema.find(t => t.table === "order_items");
assert.ok(orderItems);
assert.ok(orderItems.fields.some(f => f.name === "order_id"));
assert.ok(orderItems.fields.some(f => f.name === "product_id"));
});
it("API 按资源分组", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
const resources = arch.apiDesign.map(g => g.resource);
assert.ok(resources.includes("products"));
assert.ok(resources.includes("orders"));
});
});
// ═══════════════════════════════════════════════════════
// 2 — 企业 OA 架构
// ═══════════════════════════════════════════════════════
describe("2 — 企业 OA 架构", () => {
it("包含审批流核心表", () => {
const { data } = loadPRD(ENT_PRJ);
const arch = generateArchitecture(data);
const tableNames = arch.databaseSchema.map(t => t.table);
assert.ok(tableNames.includes("approvals"));
assert.ok(tableNames.includes("approval_steps"));
assert.ok(tableNames.includes("attendance"));
assert.ok(tableNames.includes("departments"));
});
it("审批步骤有步骤序号", () => {
const { data } = loadPRD(ENT_PRJ);
const arch = generateArchitecture(data);
const steps = arch.databaseSchema.find(t => t.table === "approval_steps");
assert.ok(steps.fields.some(f => f.name === "step_order"));
assert.ok(steps.fields.some(f => f.name === "status"));
assert.ok(steps.fields.some(f => f.name === "approver_id"));
});
it("模块包含审批和考勤", () => {
const { data } = loadPRD(ENT_PRJ);
const arch = generateArchitecture(data);
const modNames = arch.modules.map(m => m.name);
assert.ok(modNames.some(n => n.includes("approval")));
assert.ok(modNames.some(n => n.includes("attendance")));
});
it("部门表支持自引用", () => {
const { data } = loadPRD(ENT_PRJ);
const arch = generateArchitecture(data);
const dept = arch.databaseSchema.find(t => t.table === "departments");
const parentField = dept.fields.find(f => f.name === "parent_id");
assert.ok(parentField.constraints.includes("自引用") || parentField.constraints.includes("departments.id"));
});
});
// ═══════════════════════════════════════════════════════
// 3 — 教育平台架构
// ═══════════════════════════════════════════════════════
describe("3 — 教育平台架构", () => {
it("包含课程和学习进度表", () => {
const { data } = loadPRD(EDU_PRJ);
const arch = generateArchitecture(data);
const tableNames = arch.databaseSchema.map(t => t.table);
assert.ok(tableNames.includes("courses"));
assert.ok(tableNames.includes("lessons"));
assert.ok(tableNames.includes("exercises"));
assert.ok(tableNames.includes("user_progress"));
});
it("课时表有排序字段", () => {
const { data } = loadPRD(EDU_PRJ);
const arch = generateArchitecture(data);
const lessons = arch.databaseSchema.find(t => t.table === "lessons");
assert.ok(lessons.fields.some(f => f.name === "sort_order"));
assert.ok(lessons.fields.some(f => f.name === "video_url"));
});
it("API 包含 courses 和 progress", () => {
const { data } = loadPRD(EDU_PRJ);
const arch = generateArchitecture(data);
const resources = arch.apiDesign.map(g => g.resource);
assert.ok(resources.includes("courses"));
assert.ok(resources.includes("progress"));
});
});
// ═══════════════════════════════════════════════════════
// 4 — 笔记应用架构
// ═══════════════════════════════════════════════════════
describe("4 — 笔记应用架构", () => {
it("包含全文搜索索引", () => {
const { data } = loadPRD(NOTE_PRJ);
const arch = generateArchitecture(data);
const notes = arch.databaseSchema.find(t => t.table === "notes");
const ftsIdx = notes.indexes?.find(i => i.includes("fts") || i.includes("GIN"));
assert.ok(ftsIdx, `Expected FTS index, got: ${JSON.stringify(notes.indexes)}`);
});
it("标签多对多关联", () => {
const { data } = loadPRD(NOTE_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.databaseSchema.some(t => t.table === "tags"));
assert.ok(arch.databaseSchema.some(t => t.table === "note_tags"));
});
it("笔记表支持 Markdown", () => {
const { data } = loadPRD(NOTE_PRJ);
const arch = generateArchitecture(data);
const notes = arch.databaseSchema.find(t => t.table === "notes");
assert.ok(notes.fields.some(f => f.name === "is_markdown"));
});
});
// ═══════════════════════════════════════════════════════
// 5 — 完整架构输出
// ═══════════════════════════════════════════════════════
describe("5 — 完整架构输出结构", () => {
it("架构包含所有必需字段", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
const required = [
"projectName", "domain", "techStack", "architectureDiagram",
"dataFlows", "modules", "databaseSchema", "apiDesign",
"directoryStructure", "deployment", "meta",
];
for (const field of required) {
assert.ok(arch[field] !== undefined, `Missing: ${field}`);
}
});
it("techStack 包含四层", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.techStack.frontend);
assert.ok(arch.techStack.backend);
assert.ok(arch.techStack.database);
assert.ok(arch.techStack.deployment);
});
it("deployment 包含环境和 CI", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.deployment.environments.includes("production"));
assert.ok(arch.deployment.strategy);
assert.ok(arch.deployment.ci.includes("GitHub Actions") || arch.deployment.ci.includes("CI"));
});
it("meta 包含统计信息", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.meta.generatedAt);
assert.ok(typeof arch.meta.moduleCount === "number");
assert.ok(typeof arch.meta.tableCount === "number");
assert.ok(typeof arch.meta.apiEndpointCount === "number");
});
});
// ═══════════════════════════════════════════════════════
// 6 — 错误处理
// ═══════════════════════════════════════════════════════
describe("6 — 错误处理", () => {
it("空的 error PRD 返回错误", () => {
const { data } = loadPRD(EMPTY_PRJ);
const arch = generateArchitecture(data);
assert.equal(arch.error, "INVALID_PRD");
assert.ok(arch.message);
});
it("null 输入返回错误", () => {
const arch = generateArchitecture(null);
assert.equal(arch.error, "INVALID_PRD");
});
it("缺少 projectName 返回错误", () => {
const arch = generateArchitecture({ features: [] });
assert.equal(arch.error, "INCOMPLETE_PRD");
});
it("缺少 features 返回错误", () => {
const arch = generateArchitecture({ projectName: "test" });
assert.equal(arch.error, "INCOMPLETE_PRD");
});
});
// ═══════════════════════════════════════════════════════
// 7 — 通用 PRD 降级
// ═══════════════════════════════════════════════════════
describe("7 — 通用 PRD 降级", () => {
it("generic domain 生成合理的架构", () => {
const { data } = loadPRD(GENERIC_PRJ);
const arch = generateArchitecture(data);
assert.equal(arch.domain, "generic");
assert.ok(arch.techStack.frontend);
assert.ok(arch.techStack.backend);
assert.ok(arch.databaseSchema.length >= 1);
assert.ok(arch.modules.length >= 1);
});
it("通用 PRD 有最小表结构", () => {
const { data } = loadPRD(GENERIC_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.databaseSchema.some(t => t.table === "users"));
assert.ok(arch.databaseSchema.some(t => t.table === "items"));
});
});
// ═══════════════════════════════════════════════════════
// 8 — 架构图生成
// ═══════════════════════════════════════════════════════
describe("8 — 架构图生成", () => {
it("架构图包含项目名", () => {
const diagram = generateArchDiagram(
"PetCare",
{ frontend: "React Native", backend: "NestJS", database: "PostgreSQL", deployment: "Docker" },
["宠物管理", "健康日程", "用户中心"]
);
assert.ok(diagram.includes("PetCare"));
assert.ok(diagram.includes("System Architecture"));
});
it("架构图包含模块列表", () => {
const diagram = generateArchDiagram(
"TestApp", { frontend: "React", backend: "Node", database: "PG" },
["Auth", "API", "Dashboard"]
);
assert.ok(diagram.includes("Auth"));
assert.ok(diagram.includes("API"));
assert.ok(diagram.includes("Dashboard"));
});
it("架构图是 ASCII 格式", () => {
const diagram = generateArchDiagram("X", {}, []);
assert.ok(diagram.includes("┌"));
assert.ok(diagram.includes("└"));
assert.ok(diagram.includes("│"));
});
});
// ═══════════════════════════════════════════════════════
// 9 — 数据库设计
// ═══════════════════════════════════════════════════════
describe("9 — 数据库设计", () => {
it("所有表都有 fields 数组", () => {
const domains = ["pet", "ecommerce", "education", "enterprise", "fitness", "note", "generic"];
for (const domain of domains) {
const tables = generateDatabaseSchema(
[{ name: "测试功能", description: "测试", priority: "P0" }],
[{ method: "GET", path: "/api/test", description: "test" }],
domain
);
for (const table of tables) {
assert.ok(table.table, `${domain}: table has name`);
assert.ok(Array.isArray(table.fields), `${domain}.${table.table}: fields is array`);
assert.ok(table.fields.length > 0, `${domain}.${table.table}: fields not empty`);
}
}
});
it("所有域都有 users 表", () => {
const domains = ["pet", "ecommerce", "education", "enterprise"];
for (const domain of domains) {
const tables = generateDatabaseSchema([], [], domain);
assert.ok(tables.some(t => t.table === "users"), `${domain}: missing users table`);
}
});
it("表字段包含类型和约束", () => {
const tables = generateDatabaseSchema(
[{ name: "测试", description: "", priority: "P0" }],
[{ method: "GET", path: "/api/test", description: "" }],
"pet"
);
for (const table of tables) {
for (const field of table.fields) {
assert.ok(field.name);
assert.ok(field.type);
// constraints may be null/empty for some fields
}
}
});
});
// ═══════════════════════════════════════════════════════
// 10 — API 设计
// ═══════════════════════════════════════════════════════
describe("10 — API 设计", () => {
it("按资源分组 API", () => {
const apis = [
{ method: "GET", path: "/api/pets", description: "列表" },
{ method: "POST", path: "/api/pets", description: "创建" },
{ method: "GET", path: "/api/schedules", description: "日程" },
];
const design = designAPI(apis);
assert.equal(design.length, 2);
const resources = design.map(g => g.resource);
assert.ok(resources.includes("pets"));
assert.ok(resources.includes("schedules"));
});
it("每个资源组有 basePath", () => {
const apis = [{ method: "GET", path: "/api/orders", description: "订单" }];
const design = designAPI(apis);
assert.equal(design[0].basePath, "/api/orders");
assert.equal(design[0].endpoints.length, 1);
});
it("endpoint 包含 method 和 path", () => {
const apis = [
{ method: "POST", path: "/api/pets", description: "创建宠物" },
{ method: "GET", path: "/api/pets/:id", description: "获取宠物" },
];
const design = designAPI(apis);
const group = design.find(g => g.resource === "pets");
assert.equal(group.endpoints.length, 2);
assert.ok(group.endpoints.some(e => e.method === "POST"));
assert.ok(group.endpoints.some(e => e.path.includes(":id")));
});
});
// ═══════════════════════════════════════════════════════
// 11 — 目录结构
// ═══════════════════════════════════════════════════════
describe("11 — 目录结构生成", () => {
it("包含 apps 和 server 目录", () => {
const dirs = generateDirectoryStructure("PetCare", "pet", [
{ name: "pet", label: "宠物管理", features: ["宠物档案"] },
]);
const text = dirs.join("\n");
assert.ok(text.includes("apps/"));
assert.ok(text.includes("server/"));
});
it("包含 docker-compose 和 CI 配置", () => {
const dirs = generateDirectoryStructure("TestApp", "generic", []);
const text = dirs.join("\n");
assert.ok(text.includes("docker-compose.yml"));
assert.ok(text.includes(".github/workflows"));
assert.ok(text.includes("ci.yml"));
});
it("模块目录包含 controller/service/module", () => {
const dirs = generateDirectoryStructure("PetCare", "pet", [
{ name: "pets", label: "宠物管理", features: [] },
]);
const text = dirs.join("\n");
assert.ok(text.includes("pets.controller.ts"));
assert.ok(text.includes("pets.service.ts"));
assert.ok(text.includes("pets.module.ts"));
});
it("包含 Prisma schema", () => {
const dirs = generateDirectoryStructure("App", "generic", []);
const text = dirs.join("\n");
assert.ok(text.includes("schema.prisma"));
});
});
// ═══════════════════════════════════════════════════════
// 12 — 模块分解
// ═══════════════════════════════════════════════════════
describe("12 — 模块分解", () => {
it("按关键词分组合并功能", () => {
const features = [
{ name: "用户管理", description: "", priority: "P0" },
{ name: "权限控制", description: "", priority: "P0" },
{ name: "设置中心", description: "", priority: "P1" },
];
const modules = decomposeModules(features, "generic");
// "用户管理" → auth, "权限控制" → auth (matches "权限"), "设置中心" → settings (matches "设置")
assert.ok(modules.some(m => m.name === "auth"));
assert.ok(modules.some(m => m.name === "settings"));
});
it("未知功能各自独立模块", () => {
const features = [
{ name: "量子计算", description: "", priority: "P0" },
];
const modules = decomposeModules(features, "generic");
assert.ok(modules.some(m => m.name === "量子计算"));
});
it("电商域添加订单和商品模块", () => {
const features = [{ name: "商品浏览", description: "", priority: "P0" }];
const modules = decomposeModules(features, "ecommerce");
const names = modules.map(m => m.name);
assert.ok(names.includes("product"));
assert.ok(names.includes("order"));
});
it("企业域添加审批/考勤/部门模块", () => {
const features = [
{ name: "审批流程", description: "", priority: "P0" },
{ name: "考勤打卡", description: "", priority: "P0" },
{ name: "部门管理", description: "", priority: "P0" },
];
const modules = decomposeModules(features, "enterprise");
const names = modules.map(m => m.name);
assert.ok(names.includes("approval"));
assert.ok(names.includes("attendance"));
assert.ok(names.includes("department"));
});
it("每个模块有 responsibilities", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
for (const mod of arch.modules) {
assert.ok(mod.name);
assert.ok(mod.label);
assert.ok(Array.isArray(mod.features));
assert.ok(Array.isArray(mod.responsibilities));
assert.ok(mod.responsibilities.length > 0);
}
});
});
// ═══════════════════════════════════════════════════════
// 13 — 数据流
// ═══════════════════════════════════════════════════════
describe("13 — 数据流生成", () => {
it("为页面生成数据流", () => {
const { data } = loadPRD(ECO_PRJ);
const flows = generateDataFlows(data.pages, data.apiRequirements);
assert.ok(flows.length > 0);
for (const flow of flows) {
assert.ok(flow.page);
assert.ok(flow.route);
assert.ok(flow.description);
assert.ok(Array.isArray(flow.dataFlow));
assert.ok(flow.direction.includes("Database"));
}
});
it("API 为空时回退到通用数据流", () => {
const flows = generateDataFlows(
[{ name: "首页", route: "/home", description: "首页" }],
[{ method: "GET", path: "/api/health", description: "健康检查" }]
);
assert.ok(flows.length > 0);
});
});
// ═══════════════════════════════════════════════════════
// 14 — CLI 端到端
// ═══════════════════════════════════════════════════════
describe("14 — CLI 端到端", () => {
it("--input 模式生成架构", async () => {
const { execSync } = await import("node:child_process");
const result = execSync(
`node scripts/architecture-agent.mjs --input test/fixtures/architecture-agent/ecommerce-prd.json --pretty`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
const parsed = JSON.parse(result);
assert.equal(parsed.projectName, "ShopApp");
assert.equal(parsed.domain, "ecommerce");
assert.ok(parsed.techStack.frontend.includes("uni-app") || parsed.techStack.frontend.includes("小程序"));
});
it("--input-text 模式自动调 SF-01", async () => {
const { execSync } = await import("node:child_process");
const result = execSync(
`node scripts/architecture-agent.mjs --input-text "做一个在线教育平台" --pretty`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
const parsed = JSON.parse(result);
assert.equal(parsed.projectName, "EduPlatform");
assert.equal(parsed.domain, "education");
});
it("--help 显示帮助", async () => {
const { execSync } = await import("node:child_process");
const result = execSync(
`node scripts/architecture-agent.mjs --help`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
assert.ok(result.includes("Usage"));
assert.ok(result.includes("architecture-agent"));
});
it("--output 写入文件", async () => {
const { execSync } = await import("node:child_process");
const tmpPath = resolve(WORKSPACE, ".tmp-arch-e2e.json");
execSync(
`node scripts/architecture-agent.mjs --input test/fixtures/architecture-agent/note-prd.json --output ${tmpPath} --pretty`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
assert.ok(existsSync(tmpPath));
const content = JSON.parse(readFileSync(tmpPath, "utf-8"));
assert.equal(content.projectName, "NoteApp");
assert.ok(content.architectureDiagram.includes("NoteApp"));
rmSync(tmpPath);
});
it("invalid PRD 返回非零退出码", async () => {
const { execSync } = await import("node:child_process");
try {
execSync(
`node scripts/architecture-agent.mjs --input test/fixtures/architecture-agent/empty-prd.json`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
assert.fail("Should have thrown");
} catch (e) {
assert.ok(e.status !== 0);
}
});
});
// ═══════════════════════════════════════════════════════
// 15 — 技术栈解析
// ═══════════════════════════════════════════════════════
describe("15 — 技术栈解析", () => {
it("wechat-miniapp 平台使用微信栈", () => {
const prd = {
techConstraints: { platforms: ["wechat-miniapp"], considerations: [] },
extraFeatures: [],
};
const stack = resolveTechStack(prd);
assert.ok(stack.frontend.includes("微信"));
});
it("iOS 平台推荐 SwiftUI", () => {
const prd = {
techConstraints: { platforms: ["ios"], considerations: [] },
extraFeatures: [],
};
const stack = resolveTechStack(prd);
assert.ok(stack.frontend.includes("SwiftUI"));
});
it("mobile+web 推荐跨平台框架", () => {
const prd = {
techConstraints: { platforms: ["mobile", "web"], considerations: [] },
extraFeatures: [],
};
const stack = resolveTechStack(prd);
assert.ok(
stack.frontend.includes("React Native") ||
stack.frontend.includes("Flutter")
);
});
it("实时特性添加 WebSocket", () => {
const prd = {
techConstraints: { platforms: ["web"], considerations: [] },
extraFeatures: ["real-time"],
};
const stack = resolveTechStack(prd);
assert.ok(stack.backend.includes("WebSocket") || stack.backend.includes("Socket.io"));
});
it("AI 特性添加 AI 服务", () => {
const prd = {
techConstraints: { platforms: ["web"], considerations: [] },
extraFeatures: ["ai-powered"],
};
const stack = resolveTechStack(prd);
assert.ok(stack.backend.includes("AI"));
});
});
// ═══════════════════════════════════════════════════════
// 16 — File I/O
// ═══════════════════════════════════════════════════════
describe("16 — File I/O", () => {
it("loadPRD 读取文件", () => {
const { data } = loadPRD(ECO_PRJ);
assert.ok(data.projectName);
});
it("loadPRD 对不存在文件返回 error", () => {
const { data, error } = loadPRD("/nonexistent.json");
assert.equal(data, null);
assert.ok(error);
});
it("writeArchitecture 写入文件", () => {
const arch = generateArchitecture(JSON.parse(readFileSync(ECO_PRJ, "utf-8")));
const tmpPath = resolve(WORKSPACE, ".tmp-arch-write-test.json");
writeArchitecture(arch, tmpPath, true);
assert.ok(existsSync(tmpPath));
rmSync(tmpPath);
});
});