任务 ID:RPA-MIG-0(自仓库收尾) 目标:测试运行不得在仓库根产生 .compat-report.txt,保证工作树干净 代码逻辑:报告路径改为 path.join(os.tmpdir(), ".compat-report.txt"),与对面 feat/rpa-core-migration 已迁入版本一致 验证:对面 48/48 全绿用同一份修复代码;本仓库测试布局与对面一致 风险与回滚:低——仅测试报告输出路径,回滚即恢复原路径
78 lines
3.5 KiB
TypeScript
78 lines
3.5 KiB
TypeScript
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<string, { status: string; canonical?: string }>(
|
||
(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);
|
||
});
|