import { describe, it } from "vitest"; import { capabilityIds } from "../../src/engine/registry"; import { writeFileSync, readFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import "../../src/capabilities/index"; // 汇总导入全部能力模块(与运行时同构) interface CatalogComp { id: string; name?: string; fields?: { key: string; label?: string; widget?: string; options?: { label: string; value: string }[]; defaultValue?: unknown }[]; } describe("系统组件兼容性全量审查", () => { it("注册覆盖 + 面板可见性 + 字段契约核对", async () => { const catalog: CatalogComp[] = JSON.parse(readFileSync("resources/catalog.json", "utf-8")).components ?? []; const dup = JSON.parse(readFileSync("resources/capability-duplicates.json", "utf-8")); const dupMap = new Map( (Array.isArray(dup) ? dup : []).map((d: { id: string; status: string; canonical?: string }) => [d.id, { status: d.status, canonical: d.canonical }]) ); const registered = new Set(capabilityIds()); // 1) catalog 组件 → 运行时注册 const unregistered: { id: string; name?: string }[] = []; for (const c of catalog) { if (!registered.has(c.id)) unregistered.push({ id: c.id, name: c.name }); } // 2) 幽灵注册:handler 有但 catalog 没有(可能是内部别名,不一定是问题,但要列出来) const catalogIds = new Set(catalog.map((c) => c.id)); const ghosts: string[] = []; for (const id of registered) { if (!catalogIds.has(id)) ghosts.push(id); } // 3) duplicates 一致性:hide 必须有 canonical 且 canonical 已注册;keep/隐藏组件的执行器必须在 const dupIssues: string[] = []; for (const [id, info] of dupMap) { if (info.status === "hide" && info.canonical && !registered.has(info.canonical)) { dupIssues.push(`hide 项 ${id} 的 canonical ${info.canonical} 未注册`); } if (info.status === "keep" && !registered.has(id)) { dupIssues.push(`keep 项 ${id} 未注册(面板可见但运行会报未实现)`); } if (info.status === "hide" && !info.canonical && !registered.has(id)) { dupIssues.push(`hide 项 ${id} 无 canonical 且未注册(旧流程节点将无法运行)`); } } // 4) 字段契约抽查:select 枚举 defaultValue 必须在 options 里;hidden 字段不能 required const fieldIssues: string[] = []; for (const c of catalog) { for (const f of c.fields ?? []) { if (f.widget === "select" && Array.isArray(f.options) && f.options.length > 0) { const values = f.options.map((o) => String(o.value)); if (f.defaultValue !== undefined && f.defaultValue !== "" && !values.includes(String(f.defaultValue))) { fieldIssues.push(`${c.id}.${f.key} select 默认值 "${f.defaultValue}" 不在选项值 [${values.join(",")}] 中`); } } } } const report = { catalogTotal: catalog.length, registeredTotal: registered.size, unregisteredCount: unregistered.length, unregistered: unregistered.slice(0, 50), ghostCount: ghosts.length, ghosts: ghosts.slice(0, 40), dupIssues, fieldIssues: fieldIssues.slice(0, 40), fieldIssueCount: fieldIssues.length }; // 报告写系统临时目录:不得污染仓库工作树(对面 CI 门禁要求干净树) writeFileSync(path.join(os.tmpdir(), ".compat-report.txt"), JSON.stringify(report, null, 1), "utf8"); }, 120000); });