/** * SF-02 Requirement Engineering Factory — Test Suite * * Part S:全覆盖测试 * * 覆盖: * A — Requirement Domain Model * B — Strategy Conversion Engine * C — Epic Generation Test * D — Feature Generation Test * E — User Story Generation Test * F — Acceptance Criteria Generation Test * G — Business Rules Generation Test * H — Permission Requirement Test * I — NFR Generation Test * J — Integration Requirement Test * K — Data Requirement Test * L — Dependency Graph Test * M — Priority Matrix Test * N — MVP Scope Test * O — Traceability Test * P — Requirement Package Test * Q — Factory Integration Test (PR-36) * R — Artifact Registration Test * S — Runtime Integration Test (PR-35) * T — SF-01 Compatibility Test * U — Edge Cases Test */ import { describe, it } from "node:test"; import assert from "node:assert/strict"; // ─── SF-02 Domain Model ────────────────────────── import * as dm from "../src/sf-02-requirement-engineering/domain-model.mjs"; // ─── SF-02 Conversion Engine ───────────────────── import { convertStrategyToRequirements, generateEpicsFromStrategy, generateFeaturesFromEpic, generateUserStoriesFromFeature, generateAcceptanceCriteriaForStory, generateIntegrationRequirements, generateNFRs, generatePermissionRequirements, generateBusinessRules, generateDataRequirements, generateConstraints, } from "../src/sf-02-requirement-engineering/strategy-to-requirement.mjs"; // ─── SF-02 Dependency & Priority & MVP & Trace ──── import { buildDependencyGraph, buildPriorityMatrix, generateMVPScope, buildTraceabilityMatrix, prioritizeByMoSCoW, prioritizeByRICE, prioritizeByWSJF, } from "../src/sf-02-requirement-engineering/dependency-engine.mjs"; // ─── SF-02 Factory ─────────────────────────────── import { sf02RequirementEngineeringFactory, registerSF02, generateRequirementReport, SF02_ARTIFACTS, } from "../src/sf-02-requirement-engineering/index.mjs"; // ─── SF-01 (for compatibility test) ────────────── import { sf01ProductStrategyFactory, registerSF01, } from "../src/sf-01-product-strategy/index.mjs"; // ─── PR-36 Factory Core ────────────────────────── import { createFactoryRegistry } from "../src/software-factory-core/factory-registry.mjs"; import { createFactoryInput, createFactoryOutput } from "../src/software-factory-core/factory-contract.mjs"; import { createArtifactGraph, createArtifactNode } from "../src/software-factory-core/artifact-graph.mjs"; // ─── PR-35 Runtime ─────────────────────────────── import { createTask, createExecutionPlan, createRuntimeContext, executePipeline, } from "../src/agent-runtime/index.mjs"; // ═════════════════════════════════════════════════════ // Test Fixture: Mock Strategy Package // ═════════════════════════════════════════════════════ function mockStrategyPackage(category = "AI平台") { return { executiveSummary: { title: "Product Strategy: AI客服平台", viabilityVerdict: "STRONG_GO", viabilityScore: 82, priorityLevel: "P0", recommendation: "强烈建议立即启动", }, problemStatement: { who: "中小企业客服团队", what: "客服效率低,知识管理混乱", why: "人工客服成本高,响应慢", severity: 75, }, targetCustomer: { segment: "中小企业", willingnessToPay: 70, painPoints: ["客服效率低", "知识碎片化", "培训成本高"], }, targetMarket: { tam: 50_000_000_000, sam: 5_000_000_000, som: 500_000_000, growthRate: 35, }, businessModel: { type: "SaaS", ltvCacRatio: 5.2, grossMargin: 85 }, revenueModel: { year1Revenue: 2_000_000 }, productVision: { vision: "让每个企业拥有智能客服", mission: "通过AI技术降低客服成本80%", }, riskAssessment: { overallRisk: 40, breakdown: { marketRisk: 30, techRisk: 50 } }, gtmStrategy: { entryStrategy: "PLG", timeToMarketMonths: 6, initialBudget: 500_000 }, roadmap: { totalDurationMonths: 18, totalBudget: 2_000_000, phases: [ { phase: "mvp", scope: "核心AI客服功能", durationMonths: 6, budget: 500_000 }, { phase: "v1", scope: "多语言+多渠道", durationMonths: 6, budget: 700_000 }, { phase: "v2", scope: "高级分析+自动化", durationMonths: 4, budget: 500_000 }, { phase: "enterprise", scope: "企业版功能", durationMonths: 2, budget: 300_000 }, ], }, metadata: { factoryId: "SF-01", category, version: "1.0" }, }; } // ═════════════════════════════════════════════════════ // A — Requirement Domain Model // ═════════════════════════════════════════════════════ describe("SF-02 Part A — Requirement Domain Model", () => { it("creates a valid Epic", () => { const epic = dm.createEpic({ title: "知识库管理", description: "知识文档的全生命周期管理", category: dm.EPIC_CATEGORY.CONTENT_MANAGEMENT, objective: "构建高质量知识体系", }); assert.ok(epic.id.startsWith("ep-")); assert.equal(epic.title, "知识库管理"); assert.equal(epic.category, "内容管理"); assert.equal(epic.objective, "构建高质量知识体系"); }); it("creates a valid Feature", () => { const feature = dm.createFeature({ title: "知识库创建", description: "创建知识库并设置基本属性", epicId: "ep-1", featureType: "CRUD", }); assert.ok(feature.id.startsWith("ft-")); assert.equal(feature.epicId, "ep-1"); assert.equal(feature.featureType, "CRUD"); }); it("creates a valid UserStory with standard format", () => { const story = dm.createUserStory({ role: "客服人员", goal: "快速搜索知识库", value: "高效回答用户问题", featureId: "ft-1", epicId: "ep-1", priority: dm.STORY_PRIORITY.MUST_HAVE, }); assert.ok(story.id.startsWith("us-")); assert.equal(story.role, "客服人员"); assert.equal(story.goal, "快速搜索知识库"); assert.equal(story.priority, "Must Have"); assert.ok(story.statement.includes("As a 客服人员")); assert.ok(story.statement.includes("I want 快速搜索知识库")); }); it("creates valid AcceptanceCriteria in Given/When/Then format", () => { const ac = dm.createAcceptanceCriteria({ storyId: "us-1", scenario: "正常搜索", given: "用户已登录系统", when: "用户输入搜索关键词", then: "系统返回相关结果", }); assert.ok(ac.id.startsWith("ac-")); assert.ok(ac.statement.includes("Given 用户已登录系统")); assert.ok(ac.statement.includes("When 用户输入搜索关键词")); assert.ok(ac.statement.includes("Then 系统返回相关结果")); }); it("creates valid BusinessRule", () => { const br = dm.createBusinessRule({ name: "密码安全策略", description: "密码必须满足安全要求", ruleType: dm.RULE_TYPE.CONSTRAINT, condition: "用户设置新密码", action: "验证密码复杂度", }); assert.ok(br.id.startsWith("br-")); assert.equal(br.ruleType, "业务约束"); }); it("creates valid Constraint", () => { const ct = dm.createConstraint({ name: "交付时间", description: "MVP 6 个月交付", constraintType: dm.CONSTRAINT_TYPE.TIME, }); assert.ok(ct.id.startsWith("ct-")); assert.equal(ct.constraintType, "时间约束"); }); it("creates valid NFR", () => { const nfr = dm.createNonFunctionalRequirement({ name: "页面响应时间", description: "页面需快速响应", category: dm.NFR_CATEGORY.PERFORMANCE, metric: "P95 响应时间", target: "< 500ms", }); assert.ok(nfr.id.startsWith("nfr-")); assert.equal(nfr.category, "性能需求"); }); it("creates valid PermissionRequirement", () => { const pr = dm.createPermissionRequirement({ accessModel: dm.ACCESS_MODEL.HYBRID, roles: [ { name: dm.ROLE_TYPE.SUPER_ADMIN, description: "超级管理员" }, { name: dm.ROLE_TYPE.USER, description: "普通用户" }, ], permissions: [ { resource: "content", action: "read" }, { resource: "content", action: "create" }, ], }); assert.equal(pr.accessModel, "Hybrid"); assert.equal(pr.roles.length, 2); assert.equal(pr.permissions.length, 2); }); it("creates valid IntegrationRequirement", () => { const ir = dm.createIntegrationRequirement({ name: "支付集成", category: dm.INTEGRATION_CATEGORY.PAYMENT, description: "集成微信支付和支付宝", necessity: "required", }); assert.ok(ir.id.startsWith("ir-")); assert.equal(ir.category, "支付系统"); }); it("creates valid DataRequirement (no DB design)", () => { const dr = dm.createDataRequirement({ entityName: "用户 (User)", description: "平台用户基本信息", requiredFields: ["用户名", "邮箱", "密码哈希"], relationships: ["用户 → 角色(多对多)"], dataClassification: dm.DATA_CLASSIFICATION.PII, lifecycle: [dm.DATA_LIFECYCLE.CREATE, dm.DATA_LIFECYCLE.READ], owner: "用户本人 + 管理员", complianceReqs: ["GDPR", "个人信息保护法"], }); assert.ok(dr.id.startsWith("dr-")); assert.equal(dr.dataClassification, "个人隐私数据"); assert.equal(dr.owner, "用户本人 + 管理员"); }); it("creates valid RiskRequirement with score calculation", () => { const rr = dm.createRiskRequirement({ name: "第三方API不稳定", description: "AI模型API可能不稳定", level: dm.RISK_LEVEL.HIGH, probability: 0.3, impact: 0.8, }); assert.equal(rr.riskScore, 0.24); assert.equal(rr.level, "high"); }); it("creates valid TraceabilityLink", () => { const tl = dm.createTraceabilityLink({ sourceId: "ep-1", sourceType: "epic", targetId: "ft-1", targetType: "feature", linkType: dm.LINK_TYPE.EPIC_TO_FEATURE, }); assert.ok(tl.id.startsWith("tl-")); assert.equal(tl.linkType, "epic→feature"); }); it("creates valid Priority with RICE scoring", () => { const pri = dm.createPriority({ itemId: "ep-1", itemType: "epic", category: dm.PRIORITY_CATEGORY.MUST_HAVE, method: dm.PRIORITY_METHOD.RICE, score: 65, scores: { reach: 80, impact: 3, confidence: 0.8, effort: 3 }, }); assert.equal(pri.category, "Must Have"); assert.equal(pri.method, "RICE"); // RICE: 80*3*0.8/3 = 64 assert.ok(Math.abs(pri.score - 64) <= 2, `Expected ~64, got ${pri.score}`); }); it("creates valid Dependency", () => { const dep = dm.createDependency({ fromId: "ep-2", fromType: "epic", toId: "ep-1", toType: "epic", depType: dm.DEPENDENCY_TYPE.REQUIRED_BY, description: "Agent 管理需要用户管理", }); assert.ok(dep.id.startsWith("dep-")); assert.equal(dep.depType, "required_by"); }); it("creates valid RequirementVersion", () => { const rv = dm.createRequirementVersion({ requirementId: "us-5", requirementType: "story", versionNumber: 2, changeDescription: "更新了权限验证流程", }); assert.equal(rv.versionNumber, 2); }); it("creates complete RequirementPackage", () => { const pkg = dm.createRequirementPackage({ productName: "AI客服平台", strategyPackage: mockStrategyPackage(), executiveSummary: { summary: "test" }, epics: [dm.createEpic({ title: "t", description: "d", category: dm.EPIC_CATEGORY.SYSTEM })], }); assert.equal(pkg.productName, "AI客服平台"); assert.equal(pkg.epics.length, 1); assert.equal(pkg.metadata.factoryId, "SF-02"); }); it("Throws on required fields missing", () => { assert.throws(() => dm.createEpic({}), /title/); assert.throws(() => dm.createUserStory({}), /role/); assert.throws(() => dm.createAcceptanceCriteria({}), /storyId/); assert.throws(() => dm.createBusinessRule({}), /name/); assert.throws(() => dm.createConstraint({}), /name/); assert.throws(() => dm.createNonFunctionalRequirement({}), /name/); assert.throws(() => dm.createIntegrationRequirement({}), /name/); assert.throws(() => dm.createDataRequirement({}), /entityName/); assert.throws(() => dm.createDependency({}), /fromId/); assert.throws(() => dm.createPriority({}), /itemId/); assert.throws(() => dm.createTraceabilityLink({}), /sourceId/); }); }); // ═════════════════════════════════════════════════════ // B — Strategy Conversion Engine // ═════════════════════════════════════════════════════ describe("SF-02 Part B — Strategy → Requirement Conversion", () => { it("converts AI platform strategy to requirements", () => { const strategy = mockStrategyPackage("AI平台"); const result = convertStrategyToRequirements(strategy); assert.ok(result.epics.length >= 5, `Expected >=5 epics, got ${result.epics.length}`); assert.ok(result.features.length >= 15, `Expected >=15 features, got ${result.features.length}`); assert.ok(result.userStories.length >= 40, `Expected >=40 stories, got ${result.userStories.length}`); assert.ok(result.acceptanceCriteria.length >= 50, `Expected >=50 AC, got ${result.acceptanceCriteria.length}`); assert.ok(result.businessRules.length >= 5); assert.ok(result.nonFunctionalReqs.length >= 5); assert.ok(result.integrationReqs.length >= 3, `Expected >=3 integration reqs, got ${result.integrationReqs.length}`); assert.ok(result.dataReqs.length >= 3); }); it("converts enterprise SaaS strategy", () => { const strategy = mockStrategyPackage("企业SaaS"); const result = convertStrategyToRequirements(strategy); assert.ok(result.epics.length >= 5); assert.ok(result.features.length >= 15); }); it("converts ecommerce strategy", () => { const strategy = mockStrategyPackage("电商平台"); const result = convertStrategyToRequirements(strategy); const epicTitles = result.epics.map(e => e.title); assert.ok(epicTitles.some(t => t.includes("商品")), "Should have product management epic"); assert.ok(epicTitles.some(t => t.includes("订单")), "Should have order management epic"); }); it("converts education platform strategy", () => { const strategy = mockStrategyPackage("教育平台"); const result = convertStrategyToRequirements(strategy); const epicTitles = result.epics.map(e => e.title); assert.ok(epicTitles.some(t => t.includes("课程"))); assert.ok(epicTitles.some(t => t.includes("学习"))); }); it("falls back to generic template for unknown category", () => { const strategy = mockStrategyPackage("量子计算平台"); const result = convertStrategyToRequirements(strategy); assert.ok(result.epics.length >= 5, `Expected >=5 epics for unknown category, got ${result.epics.length}`); }); it("empty strategy returns empty requirements", () => { const result = convertStrategyToRequirements({ metadata: { category: "通用" } }); assert.equal(result.epics.length, 6); // generic template has 6 }); }); // ═════════════════════════════════════════════════════ // C — Epic Generation // ═════════════════════════════════════════════════════ describe("SF-02 Part C — Epic Generation", () => { it("generates AI platform epics with expected categories", () => { const strategy = mockStrategyPackage("AI平台"); const epics = generateEpicsFromStrategy(strategy); const titles = epics.map(e => e.title); assert.ok(titles.includes("用户管理"), `Expected '用户管理', got: ${titles}`); assert.ok(titles.includes("知识库管理"), `Expected '知识库管理', got: ${titles}`); assert.ok(titles.includes("Agent管理"), `Expected 'Agent管理', got: ${titles}`); assert.ok(titles.includes("对话管理"), `Expected '对话管理', got: ${titles}`); assert.ok(titles.includes("权限管理"), `Expected '权限管理', got: ${titles}`); assert.ok(titles.includes("运营分析"), `Expected '运营分析', got: ${titles}`); assert.ok(titles.includes("系统设置"), `Expected '系统设置', got: ${titles}`); }); it("each epic has required fields", () => { const epics = generateEpicsFromStrategy(mockStrategyPackage()); for (const epic of epics) { assert.ok(epic.id, "Epic must have id"); assert.ok(epic.title, "Epic must have title"); assert.ok(epic.description, "Epic must have description"); assert.ok(epic.category, "Epic must have category"); assert.ok(epic.objective, "Epic must have objective"); assert.ok(epic.successMetric, "Epic must have successMetric"); } }); }); // ═════════════════════════════════════════════════════ // D — Feature Generation // ═════════════════════════════════════════════════════ describe("SF-02 Part D — Feature Generation", () => { it("generates features from knowledge base epic", () => { const epic = dm.createEpic({ title: "知识库管理", description: "知识管理", category: dm.EPIC_CATEGORY.CONTENT_MANAGEMENT, objective: "构建知识体系", }); const features = generateFeaturesFromEpic(epic); assert.ok(features.length >= 3, `Expected >=3 features, got ${features.length}`); const titles = features.map(f => f.title); assert.ok(titles.includes("知识库创建")); assert.ok(titles.includes("文档上传")); assert.ok(titles.includes("索引构建")); }); it("generates features from user management epic", () => { const epic = dm.createEpic({ title: "用户管理", description: "用户管理", category: dm.EPIC_CATEGORY.USER_MANAGEMENT, objective: "用户身份管理", }); const features = generateFeaturesFromEpic(epic); const titles = features.map(f => f.title); assert.ok(titles.includes("用户注册")); assert.ok(titles.includes("用户登录")); assert.ok(titles.includes("个人信息管理")); }); it("all features reference correct epicId", () => { const epic = dm.createEpic({ title: "知识库管理", description: "d", category: dm.EPIC_CATEGORY.CONTENT_MANAGEMENT, objective: "o", }); const features = generateFeaturesFromEpic(epic); for (const f of features) { assert.equal(f.epicId, epic.id); } }); it("generates fallback features for unknown epic titles", () => { const epic = dm.createEpic({ title: "宇宙探索", description: "d", category: dm.EPIC_CATEGORY.SYSTEM, objective: "o", }); const features = generateFeaturesFromEpic(epic); assert.ok(features.length >= 3); assert.ok(features[0].title.includes("宇宙探索")); }); }); // ═════════════════════════════════════════════════════ // E — User Story Generation // ═════════════════════════════════════════════════════ describe("SF-02 Part E — User Story Generation", () => { it("generates CRUD stories", () => { const feature = dm.createFeature({ title: "文档管理", description: "d", epicId: "ep-1", featureType: "CRUD", }); const stories = generateUserStoriesFromFeature(feature, { title: "知识库" }, ["客服人员", "管理员"]); assert.ok(stories.length >= 4); const goals = stories.map(s => s.goal); assert.ok(goals.some(g => g.includes("浏览")), `Should have browse story, got: ${goals}`); assert.ok(goals.some(g => g.includes("创建")), `Should have create story, got: ${goals}`); assert.ok(goals.some(g => g.includes("编辑")), `Should have edit story, got: ${goals}`); }); it("generates workflow stories", () => { const feature = dm.createFeature({ title: "审批流程", description: "d", epicId: "ep-1", featureType: "workflow", }); const stories = generateUserStoriesFromFeature(feature, { title: "业务流程" }); assert.ok(stories.length >= 3); const goals = stories.map(s => s.goal); assert.ok(goals.some(g => g.includes("发起")), `Should have initiate story, got: ${goals}`); }); it("generates analytics stories", () => { const feature = dm.createFeature({ title: "数据看板", description: "d", epicId: "ep-1", featureType: "analytics", }); const stories = generateUserStoriesFromFeature(feature, { title: "运营分析" }); assert.ok(stories.length >= 3); const goals = stories.map(s => s.goal); assert.ok(goals.some(g => g.includes("导出")), `Should have export story, got: ${goals}`); }); it("all stories use As a/I want/So that format", () => { const feature = dm.createFeature({ title: "知识编辑", description: "d", epicId: "ep-1", featureType: "CRUD", }); const stories = generateUserStoriesFromFeature(feature, { title: "知识库" }, ["用户"]); for (const story of stories) { assert.ok(story.statement.includes("As a ")); assert.ok(story.statement.includes("I want ")); assert.ok(story.statement.includes("so that ")); } }); it("assigns correct priority distribution", () => { const feature = dm.createFeature({ title: "文档管理", description: "d", epicId: "ep-1", featureType: "CRUD", }); const stories = generateUserStoriesFromFeature(feature, { title: "知识库" }, ["用户", "管理员"]); const mustHave = stories.filter(s => s.priority === "Must Have"); const shouldHave = stories.filter(s => s.priority === "Should Have"); const couldHave = stories.filter(s => s.priority === "Could Have"); assert.ok(mustHave.length >= 1, "Must have at least 1 Must Have"); assert.ok(shouldHave.length >= 1, "Must have at least 1 Should Have"); assert.ok(couldHave.length >= 1, "Must have at least 1 Could Have"); }); }); // ═════════════════════════════════════════════════════ // F — Acceptance Criteria // ═════════════════════════════════════════════════════ describe("SF-02 Part F — Acceptance Criteria", () => { it("generates 3-10 ACs per story", () => { const story = dm.createUserStory({ role: "用户", goal: "创建文档", value: "管理知识", featureId: "ft-1", epicId: "ep-1", }); const acs = generateAcceptanceCriteriaForStory(story); assert.ok(acs.length >= 3, `Expected >=3 AC, got ${acs.length}`); assert.ok(acs.length <= 12); }); it("all ACs use Given/When/Then format", () => { const story = dm.createUserStory({ role: "用户", goal: "搜索知识", value: "快速查找", featureId: "ft-1", epicId: "ep-1", }); const acs = generateAcceptanceCriteriaForStory(story); for (const ac of acs) { assert.ok(ac.given, `AC missing given: ${ac.scenario}`); assert.ok(ac.when, `AC missing when: ${ac.scenario}`); assert.ok(ac.then, `AC missing then: ${ac.scenario}`); } }); it("covers normal flow, error flow, edge cases", () => { const story = dm.createUserStory({ role: "用户", goal: "上传文档", value: "添加知识", featureId: "ft-1", epicId: "ep-1", }); const acs = generateAcceptanceCriteriaForStory(story); const scenarios = acs.map(a => a.scenario); assert.ok(scenarios.some(s => s.includes("正常")), "Should have normal flow"); assert.ok(scenarios.some(s => s.includes("空数据") || s.includes("验证")), "Should have edge case"); assert.ok(scenarios.some(s => s.includes("权限")), "Should have permission check"); }); it("delete stories have delete-specific ACs", () => { const story = dm.createUserStory({ role: "管理员", goal: "删除知识库", value: "清理无效数据", featureId: "ft-1", epicId: "ep-1", }); const acs = generateAcceptanceCriteriaForStory(story); const scenarios = acs.map(a => a.scenario); assert.ok(scenarios.some(s => s.includes("确认")), `Expected delete confirmation, got: ${scenarios}`); assert.ok(scenarios.some(s => s.includes("关联")), `Expected related data check, got: ${scenarios}`); }); it("search stories have search-specific ACs", () => { const story = dm.createUserStory({ role: "用户", goal: "搜索文档内容", value: "快速定位信息", featureId: "ft-1", epicId: "ep-1", }); const acs = generateAcceptanceCriteriaForStory(story); const scenarios = acs.map(a => a.scenario); assert.ok(scenarios.some(s => s.includes("搜索结果") || s.includes("无搜索")), `Expected search scenarios, got: ${scenarios}`); }); }); // ═════════════════════════════════════════════════════ // G — Business Rules // ═════════════════════════════════════════════════════ describe("SF-02 Part G — Business Rules", () => { it("generates standard business rules", () => { const rules = generateBusinessRules(mockStrategyPackage()); assert.ok(rules.length >= 5, `Expected >=5 rules, got ${rules.length}`); }); it("covers all rule types", () => { const rules = generateBusinessRules(mockStrategyPackage()); const types = rules.map(r => r.ruleType); assert.ok(types.includes("验证规则"), `Expected validation rule, got: ${types}`); assert.ok(types.includes("业务约束"), `Expected constraint rule, got: ${types}`); assert.ok(types.includes("流程规则"), `Expected process rule, got: ${types}`); assert.ok(types.includes("审批规则"), `Expected approval rule, got: ${types}`); assert.ok(types.includes("状态机规则"), `Expected state machine rule, got: ${types}`); }); it("each business rule has condition and action", () => { const rules = generateBusinessRules(mockStrategyPackage()); for (const r of rules) { assert.ok(r.condition, `Rule ${r.name} missing condition`); assert.ok(r.action, `Rule ${r.name} missing action`); } }); }); // ═════════════════════════════════════════════════════ // H — Permission Requirements // ═════════════════════════════════════════════════════ describe("SF-02 Part H — Permission Requirements", () => { it("generates permission model with all standard roles", () => { const pr = generatePermissionRequirements(mockStrategyPackage()); assert.equal(pr.accessModel, "Hybrid"); const roleNames = pr.roles.map(r => r.name); assert.ok(roleNames.includes("超级管理员")); assert.ok(roleNames.includes("管理员")); assert.ok(roleNames.includes("运营人员")); assert.ok(roleNames.includes("普通用户")); assert.ok(roleNames.includes("访客")); assert.ok(roleNames.includes("审计员")); }); it("provides role-permission mappings", () => { const pr = generatePermissionRequirements(mockStrategyPackage()); assert.ok(pr.rolePermissions.length >= 4); const superAdmin = pr.rolePermissions.find(rp => rp.role === "超级管理员"); assert.deepEqual(superAdmin.permissions, ["*"]); }); it("AI platform has agent/conversation resources", () => { const pr = generatePermissionRequirements(mockStrategyPackage("AI平台")); assert.ok(pr.resourceTypes.includes("agent")); assert.ok(pr.resourceTypes.includes("conversation")); }); }); // ═════════════════════════════════════════════════════ // I — Non-Functional Requirements // ═════════════════════════════════════════════════════ describe("SF-02 Part I — NFR Generation", () => { it("generates NFRs covering all categories", () => { const nfrs = generateNFRs(mockStrategyPackage()); assert.ok(nfrs.length >= 8); const categories = nfrs.map(n => n.category); assert.ok(categories.includes("性能需求")); assert.ok(categories.includes("安全需求")); assert.ok(categories.includes("可靠性需求")); assert.ok(categories.includes("扩展性需求")); assert.ok(categories.includes("可观测性需求")); }); it("each NFR has quantifiable metric and target", () => { const nfrs = generateNFRs(mockStrategyPackage()); for (const nfr of nfrs) { assert.ok(nfr.metric, `NFR ${nfr.name} missing metric`); assert.ok(nfr.target, `NFR ${nfr.name} missing target`); } }); it("enterprise scope raises availability target", () => { const enterpriseStrategy = mockStrategyPackage(); enterpriseStrategy.roadmap.phases.push({ phase: "enterprise", scope: "Enterprise", durationMonths: 4, budget: 500000, }); const nfrs = generateNFRs(enterpriseStrategy); const availability = nfrs.find(n => n.name === "系统可用性"); assert.ok(availability.target.includes("99.99")); }); }); // ═════════════════════════════════════════════════════ // J — Integration Requirements // ═════════════════════════════════════════════════════ describe("SF-02 Part J — Integration Requirements", () => { it("generates AI platform integration requirements", () => { const irs = generateIntegrationRequirements("AI平台"); assert.ok(irs.length >= 4); const names = irs.map(ir => ir.name); assert.ok(names.some(n => n.includes("AI模型"))); assert.ok(names.some(n => n.includes("邮件"))); assert.ok(names.some(n => n.includes("搜索"))); }); it("generates ecommerce integration requirements", () => { const irs = generateIntegrationRequirements("电商平台"); const names = irs.map(ir => ir.name); assert.ok(names.some(n => n.includes("支付"))); }); it("each integration requirement has providers listed", () => { const irs = generateIntegrationRequirements("AI平台"); for (const ir of irs) { if (ir.necessity === "required") { assert.ok(ir.providers.length >= 1, `${ir.name} should have providers`); } } }); }); // ═════════════════════════════════════════════════════ // K — Data Requirements // ═════════════════════════════════════════════════════ describe("SF-02 Part K — Data Requirements", () => { it("generates data requirements from epics (no DB design)", () => { const strategy = mockStrategyPackage("AI平台"); const epics = generateEpicsFromStrategy(strategy); const dataReqs = generateDataRequirements(epics, strategy); assert.ok(dataReqs.length >= 4); // Verify it's NOT a database design (no column types, no normalization) for (const dr of dataReqs) { assert.ok(dr.entityName, "Must have entity name"); assert.ok(dr.description, "Must have business description"); assert.ok(dr.dataClassification, "Must have data classification"); assert.ok(dr.owner, "Must have data owner"); assert.ok(Array.isArray(dr.lifecycle)); // Must NOT contain SQL or table definition patterns const allKeys = JSON.stringify(dr).toLowerCase(); assert.ok(!allKeys.includes("varchar"), "Must not contain SQL types"); assert.ok(!allKeys.includes("primary key"), "Must not contain DB design"); } }); it("includes user data requirement with PII classification", () => { const dataReqs = generateDataRequirements( generateEpicsFromStrategy(mockStrategyPackage()), mockStrategyPackage() ); const userReq = dataReqs.find(d => d.entityName.includes("用户")); assert.ok(userReq); assert.equal(userReq.dataClassification, "个人隐私数据"); assert.ok(userReq.complianceReqs.some(c => c.includes("GDPR"))); }); it("includes audit log requirement", () => { const dataReqs = generateDataRequirements( generateEpicsFromStrategy(mockStrategyPackage()), mockStrategyPackage() ); const logReq = dataReqs.find(d => d.entityName.includes("日志")); assert.ok(logReq, "Must include audit log data requirement"); }); }); // ═════════════════════════════════════════════════════ // L — Dependency Graph // ═════════════════════════════════════════════════════ describe("SF-02 Part L — Dependency Graph", () => { it("builds dependency graph from requirements", () => { const strategy = mockStrategyPackage("AI平台"); const req = convertStrategyToRequirements(strategy); const { dependencies, graph } = buildDependencyGraph(req); assert.ok(dependencies.length >= 5, `Expected >=5 dependencies, got ${dependencies.length}`); assert.ok(graph.nodes.length > 0); assert.ok(graph.edges.length > 0); }); it("establishes epic-level dependencies", () => { const req = convertStrategyToRequirements(mockStrategyPackage("AI平台")); const { dependencies } = buildDependencyGraph(req); const epicDeps = dependencies.filter(d => d.fromType === "epic" && d.toType === "epic"); assert.ok(epicDeps.length >= 2, `Expected >=2 epic deps, got ${epicDeps.length}`); }); it("establishes story-level CRUD dependencies", () => { const req = convertStrategyToRequirements(mockStrategyPackage("AI平台")); const { dependencies } = buildDependencyGraph(req); const storyDeps = dependencies.filter(d => d.fromType === "story"); assert.ok(storyDeps.length >= 3, `Expected >=3 story deps, got ${storyDeps.length}`); }); }); // ═════════════════════════════════════════════════════ // M — Priority Matrix // ═════════════════════════════════════════════════════ describe("SF-02 Part M — Priority Matrix", () => { it("MoSCoW prioritization produces all 4 categories", () => { const items = Array.from({ length: 20 }, (_, i) => ({ id: `item-${i}`, title: `Item ${i}`, })); const priorities = prioritizeByMoSCoW(items, "story", mockStrategyPackage()); assert.equal(priorities.length, 20); const mustHave = priorities.filter(p => p.category === "Must Have"); const shouldHave = priorities.filter(p => p.category === "Should Have"); const couldHave = priorities.filter(p => p.category === "Could Have"); const wontHave = priorities.filter(p => p.category === "Won't Have"); assert.ok(mustHave.length > 0, "Must have Must Have items"); assert.ok(shouldHave.length > 0, "Must have Should Have items"); assert.ok(couldHave.length > 0, "Must have Could Have items"); assert.ok(wontHave.length > 0, "Must have Won't Have items"); }); it("system setup epic is always Must Have", () => { const items = [ { id: "ep-1", title: "系统设置" }, { id: "ep-2", title: "用户管理" }, ]; const priorities = prioritizeByMoSCoW(items, "epic", mockStrategyPackage()); const sysEpic = priorities.find(p => p.itemId === "ep-1"); assert.equal(sysEpic.category, "Must Have", "System setup should be Must Have"); }); it("RICE prioritization calculates correct formula", () => { const items = Array.from({ length: 10 }, (_, i) => ({ id: `item-${i}`, title: `Item ${i}`, })); const priorities = prioritizeByRICE(items, "feature", mockStrategyPackage()); assert.equal(priorities.length, 10); for (const p of priorities) { assert.ok(p.scores.reach > 0); assert.ok(p.scores.impact > 0); assert.ok(p.scores.confidence > 0); assert.ok(p.scores.effort > 0); } }); it("WSJF prioritization calculates CoD/Duration", () => { const items = Array.from({ length: 10 }, (_, i) => ({ id: `item-${i}`, title: `Item ${i}`, })); const priorities = prioritizeByWSJF(items, "story"); assert.equal(priorities.length, 10); for (const p of priorities) { assert.ok(p.scores.costOfDelay > 0); assert.ok(p.scores.jobDuration > 0); } }); it("buildPriorityMatrix returns combined results", () => { const items = Array.from({ length: 10 }, (_, i) => ({ id: `item-${i}`, title: `Item ${i}`, })); const { priorities, matrix } = buildPriorityMatrix(items, "feature", mockStrategyPackage()); assert.equal(priorities.length, 10); assert.ok(matrix.mustHave.length > 0); assert.ok(matrix.shouldHave.length > 0); assert.ok(matrix.couldHave.length > 0); assert.ok(matrix.wontHave.length > 0); const total = Object.values(matrix.summary).reduce((a, b) => a + b, 0); assert.equal(total, 10); }); }); // ═════════════════════════════════════════════════════ // N — MVP Scope // ═════════════════════════════════════════════════════ describe("SF-02 Part N — MVP Scope", () => { it("generates MVP scope aligned with roadmap", () => { const strategy = mockStrategyPackage("AI平台"); const req = convertStrategyToRequirements(strategy); const epicPri = buildPriorityMatrix(req.epics, "epic", strategy); const featurePri = buildPriorityMatrix(req.features, "feature", strategy); const storyPri = buildPriorityMatrix(req.userStories, "story", strategy); const mvpScope = generateMVPScope({ strategyPackage: strategy, epicPriorities: epicPri.priorities, featurePriorities: featurePri.priorities, storyPriorities: storyPri.priorities, epics: req.epics, features: req.features, userStories: req.userStories, }); assert.ok(mvpScope.phases.length >= 4); assert.ok(mvpScope.mvp.userStories > 0, `MVP must have stories, got ${mvpScope.mvp.userStories}`); assert.ok(mvpScope.mvp.totalStoryPoints > 0); // V1 >= MVP assert.ok(mvpScope.v1.userStories >= mvpScope.mvp.userStories, `V1 stories (${mvpScope.v1.userStories}) must >= MVP stories (${mvpScope.mvp.userStories})`); // V2 >= V1 assert.ok(mvpScope.v2.userStories >= mvpScope.v1.userStories); }); it("MVP only contains Must Have items", () => { const strategy = mockStrategyPackage("AI平台"); const req = convertStrategyToRequirements(strategy); const storyPri = buildPriorityMatrix(req.userStories, "story", strategy); const mvpScope = generateMVPScope({ strategyPackage: strategy, epicPriorities: [], featurePriorities: [], storyPriorities: storyPri.priorities, epics: req.epics, features: req.features, userStories: req.userStories, }); const mustHaveStories = storyPri.priorities.filter(p => p.category === "Must Have"); assert.equal(mvpScope.mvp.userStories, mustHaveStories.length, `MVP should have ${mustHaveStories.length} stories, got ${mvpScope.mvp.userStories}`); }); }); // ═════════════════════════════════════════════════════ // O — Traceability Matrix // ═════════════════════════════════════════════════════ describe("SF-02 Part O — Traceability", () => { it("builds complete traceability matrix from requirements", () => { const strategy = mockStrategyPackage("AI平台"); const req = convertStrategyToRequirements(strategy); const { links, matrix } = buildTraceabilityMatrix(req); assert.ok(links.length >= 20, `Expected >=20 traceability links, got ${links.length}`); assert.ok(matrix.strategyToEpic > 0); assert.ok(matrix.epicToFeature > 0); assert.ok(matrix.featureToStory > 0); assert.ok(matrix.storyToAC > 0); }); it("traces every epic back to strategy", () => { const req = convertStrategyToRequirements(mockStrategyPackage("AI平台")); const { links } = buildTraceabilityMatrix(req); const epicIds = req.epics.map(e => e.id); const tracedEpics = new Set( links.filter(l => l.linkType === "strategy→epic").map(l => l.targetId) ); assert.equal(tracedEpics.size, epicIds.length, `All ${epicIds.length} epics should be traced, got ${tracedEpics.size}`); }); it("traces every feature to an epic", () => { const req = convertStrategyToRequirements(mockStrategyPackage("AI平台")); const { links } = buildTraceabilityMatrix(req); const featureIds = req.features.map(f => f.id); const tracedFeatures = new Set( links.filter(l => l.linkType === "epic→feature").map(l => l.targetId) ); assert.equal(tracedFeatures.size, featureIds.length, `All features should be traced to epics`); }); it("traces every story to a feature", () => { const req = convertStrategyToRequirements(mockStrategyPackage("AI平台")); const { links } = buildTraceabilityMatrix(req); const storyIds = req.userStories.map(s => s.id); const tracedStories = new Set( links.filter(l => l.linkType === "feature→story").map(l => l.targetId) ); assert.equal(tracedStories.size, storyIds.length, `All stories should be traced to features`); }); it("every acceptance criteria is traced to its story", () => { const req = convertStrategyToRequirements(mockStrategyPackage("AI平台")); const { links } = buildTraceabilityMatrix(req); const acIds = req.acceptanceCriteria.map(a => a.id); const tracedACs = new Set( links.filter(l => l.linkType === "story→ac").map(l => l.targetId) ); assert.equal(tracedACs.size, acIds.length, `All ${acIds.length} ACs should be traced, got ${tracedACs.size}`); }); }); // ═════════════════════════════════════════════════════ // P — Requirement Package // ═════════════════════════════════════════════════════ describe("SF-02 Part P — Requirement Package", () => { it("generates complete requirement report markdown", () => { const strategy = mockStrategyPackage("AI平台"); const req = convertStrategyToRequirements(strategy); const epics = req.epics; const report = generateRequirementReport({ productName: "AI客服平台", strategyPackage: strategy, executiveSummary: { epicCount: epics.length }, epics: req.epics, features: req.features, userStories: req.userStories, acceptanceCriteria: req.acceptanceCriteria, businessRules: req.businessRules, nonFunctionalReqs: req.nonFunctionalReqs, integrationReqs: req.integrationReqs, dataReqs: req.dataReqs, permissionReqs: [req.permissionReqs], }); assert.ok(report.includes("Requirement Package: AI客服平台")); assert.ok(report.includes("Executive Summary")); assert.ok(report.includes("Epic Catalog")); assert.ok(report.includes("User Story Catalog")); assert.ok(report.includes("MVP Scope")); assert.ok(report.includes("Priority Distribution")); assert.ok(report.includes("Traceability Coverage")); }); }); // ═════════════════════════════════════════════════════ // Q — Factory Integration (PR-36) // ═════════════════════════════════════════════════════ describe("SF-02 Part Q — Factory Integration (PR-36)", () => { it("registers in PR-36 Factory Registry", () => { const registry = createFactoryRegistry(); registerSF02(registry); const sf02 = registry.get("SF-02"); assert.equal(sf02.id, "SF-02"); assert.equal(sf02.status, "active"); assert.equal(sf02.domain, "requirement-engineering"); }); it("executes SF-02 factory with SF-01 output as input", () => { const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); // Step 1: Run SF-01 const sf01Input = createFactoryInput({ factoryId: "SF-01", parameters: { idea: "开发一个AI Agent平台" }, }); const sf01Output = registry.execute("SF-01", sf01Input); assert.equal(sf01Output.status, "passed", `SF-01 should pass, got: ${sf01Output.status}`); // Step 2: Run SF-02 with SF-01 output const sf02Input = createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: sf01Output.artifacts, }); const sf02Output = registry.execute("SF-02", sf02Input); assert.equal(sf02Output.status, "passed", `SF-02 should pass, got: ${sf02Output.status} — ${sf02Output.error || ''}`); assert.ok(sf02Output.artifacts.length >= 10, `Expected >=10 artifacts, got ${sf02Output.artifacts.length}`); assert.ok(sf02Output.metrics.length >= 8); }); it("SF-02 fails with no upstream artifacts", () => { const registry = createFactoryRegistry(); registerSF02(registry); const input = createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: [], }); const output = registry.execute("SF-02", input); assert.equal(output.status, "failed"); assert.ok(output.error.includes("SF-01")); }); it("factory output contains all required artifact types", () => { const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); const sf01Output = registry.execute("SF-01", createFactoryInput({ factoryId: "SF-01", parameters: { idea: "AI Agent平台" }, })); const sf02Output = registry.execute("SF-02", createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: sf01Output.artifacts, })); const artifactTypes = sf02Output.artifacts.map(a => a.metadata?.artifactType); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.REQUIREMENT_PACKAGE)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.EPIC_CATALOG)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.FEATURE_CATALOG)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.USER_STORY_CATALOG)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.ACCEPTANCE_CRITERIA_PACKAGE)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.BUSINESS_RULES_CATALOG)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.PERMISSION_REQUIREMENTS)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.NFR_PACKAGE)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.INTEGRATION_REQUIREMENTS)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.DATA_REQUIREMENTS)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.DEPENDENCY_GRAPH)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.PRIORITY_MATRIX)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.MVP_SCOPE)); assert.ok(artifactTypes.includes(SF02_ARTIFACTS.TRACEABILITY_MATRIX)); }); it("SF-02 has correct dependency on SF-01 in registry", () => { const registry = createFactoryRegistry(); const deps = registry.getDependencies("SF-02"); assert.equal(deps.length, 1); assert.equal(deps[0].from, "SF-01"); assert.equal(deps[0].kind, "data"); }); }); // ═════════════════════════════════════════════════════ // R — Artifact Graph Registration // ═════════════════════════════════════════════════════ describe("SF-02 Part R — Artifact Graph", () => { it("registers SF-02 artifacts in artifact graph", () => { const graph = createArtifactGraph(); const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); const sf01Out = registry.execute("SF-01", createFactoryInput({ factoryId: "SF-01", parameters: { idea: "AI Agent平台" }, })); const sf02Out = registry.execute("SF-02", createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: sf01Out.artifacts, })); // Register SF-02 artifacts as ArtifactGraph nodes for (const art of sf02Out.artifacts) { const node = createArtifactNode({ kind: art.kind, name: art.name, factoryId: art.factoryId, content: art.content, metadata: art.metadata, }); graph.addNode(node); } const stats = graph.stats(); assert.ok(stats.totalNodes >= 10, `Expected >=10 graph nodes, got ${stats.totalNodes}`); }); }); // ═════════════════════════════════════════════════════ // S — Runtime Integration (PR-35) // ═════════════════════════════════════════════════════ describe("SF-02 Part S — Runtime Integration (PR-35)", () => { it("executes within PR-35 pipeline context", () => { const task = createTask({ id: "T-SF02-001", title: "SF-02 Requirement Engineering" }); const plan = createExecutionPlan({ taskId: task.id, steps: [] }); const ctx = createRuntimeContext({ task, plan }); // Run SF-01 first const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); const sf01Out = registry.execute("SF-01", createFactoryInput({ factoryId: "SF-01", parameters: { idea: "智能客服系统" }, })); // Then SF-02 const sf02Out = registry.execute("SF-02", createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: sf01Out.artifacts, })); assert.equal(sf02Out.status, "passed"); assert.ok(sf02Out.artifacts.length >= 10); assert.equal(sf02Out.metrics.length, 11, `Expected 11 metrics, got ${sf02Out.metrics.length}`); }); }); // ═════════════════════════════════════════════════════ // T — SF-01 Compatibility // ═════════════════════════════════════════════════════ describe("SF-02 Part T — SF-01 Compatibility", () => { it("SF-02 correctly consumes SF-01 output artifacts", () => { const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); // Test with multiple product ideas const ideas = ["AI Agent平台", "电商SaaS平台", "在线教育平台"]; for (const idea of ideas) { const sf01Out = registry.execute("SF-01", createFactoryInput({ factoryId: "SF-01", parameters: { idea }, })); assert.equal(sf01Out.status, "passed", `SF-01 should pass for: ${idea}`); const sf02Out = registry.execute("SF-02", createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: sf01Out.artifacts, })); assert.equal(sf02Out.status, "passed", `SF-02 should pass for: ${idea} — ${sf02Out.error || ''}`); assert.ok(sf02Out.artifacts.length >= 10, `${idea}: expected >=10 artifacts, got ${sf02Out.artifacts.length}`); } }); it("roadmap phase alignment: SF-01 → SF-02 MVP scope", () => { const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); const sf01Out = registry.execute("SF-01", createFactoryInput({ factoryId: "SF-01", parameters: { idea: "AI Agent平台" }, })); // Extract roadmap from SF-01 const roadmapArtifact = sf01Out.artifacts.find(a => a.metadata?.artifactType === "roadmap-report"); assert.ok(roadmapArtifact, "SF-01 must produce roadmap"); const sf02Out = registry.execute("SF-02", createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: sf01Out.artifacts, })); // Check MVP scope artifact const mvpArtifact = sf02Out.artifacts.find(a => a.metadata?.artifactType === SF02_ARTIFACTS.MVP_SCOPE); assert.ok(mvpArtifact, "SF-02 must produce MVP scope artifact"); // Verify the scope metadata shows MVP stories assert.ok(mvpArtifact.metadata.mvpStories > 0, "MVP should have stories"); }); }); // ═════════════════════════════════════════════════════ // U — Edge Cases // ═════════════════════════════════════════════════════ describe("SF-02 Part U — Edge Cases", () => { it("handles empty strategy package gracefully", () => { const result = convertStrategyToRequirements({ metadata: { category: "通用" } }); assert.ok(result.epics.length >= 5); // falls back to generic template assert.ok(result.features.length > 0); assert.ok(result.userStories.length > 0); }); it("handles strategy with missing roadmap", () => { const strategy = { metadata: { category: "AI平台" } }; const result = convertStrategyToRequirements(strategy); assert.ok(result.epics.length >= 5); }); it("handles extremely large strategy (stress test)", () => { const strategy = mockStrategyPackage("AI平台"); strategy.roadmap.phases.push( { phase: "v3", scope: "V3", durationMonths: 3, budget: 300000 }, { phase: "v4", scope: "V4", durationMonths: 3, budget: 300000 }, { phase: "v5", scope: "V5", durationMonths: 3, budget: 300000 }, ); const result = convertStrategyToRequirements(strategy); assert.ok(result.userStories.length >= 40); assert.ok(result.acceptanceCriteria.length >= 50); }); it("no SQL or code artifacts in output", () => { const strategy = mockStrategyPackage("AI平台"); const req = convertStrategyToRequirements(strategy); const allContent = JSON.stringify(req); assert.ok(!allContent.includes("CREATE TABLE"), "Must not contain SQL DDL"); assert.ok(!allContent.includes("import React"), "Must not contain React code"); assert.ok(!allContent.includes("function Component"), "Must not contain component code"); assert.ok(!allContent.includes("PRIMARY KEY"), "Must not contain DB design"); assert.ok(!allContent.includes("