import { describe, expect, it } from "vitest"; import { lintReport } from "../src/index.ts"; // 汇报格式核对(AG-5):反例(docs/design/agent-report-sample-bad.md)必须不过,期望样子必须过。 const BAD = `检查结果 结论:下载器与媒体服务器都无法确认为"正常"——本系统里没有它们的登记对象,也没有对应的健康检查能力。以下是有证据的部分。 1. 登记对象盘点(关键) 列出站点 只返回 1 个站点:ID/key/名称/adapter/健康/账号 …「憨憨」 hhanclub nexusphp healthy(2026-09-29T08:12:01Z,延迟 1201ms)active 这是一个 PT 站点(NexusPHP),不是下载器,也不是媒体服务器。 2. tracker 侧间接证据(不构成下载器健康证明) 查站点用户数据 …:upload 17008345370067、download 0、bonus 414799;seeding 24,seedingSize 79407286843(≈79.4 GB),leeching 0 解释:站点侧显示有客户端在做种 24 个任务,但这是 tracker 统计,不能证明下载器本身健康。 3. 未能完成 / 未确认的部分 检测站点 被拒:缺少持续授权或有效的单次审批 … 没有可用的下载任务 ID 或种子 hash,查看下载任务 无法调用(返回 参数不对)。 4. 需要你确认/补的东西 要判断下载器队列健康,请给一个下载任务 ID 或种子 hash。`; const GOOD = "下载器 qBittorrent 正常,24 个在做种、没有在下载的;媒体服务器还没接入。站点憨憨上次检查正常(昨天 16:12),实时检测需要你批准。订阅 5 条都已取消,没有待处理事项。"; describe("lintReport", () => { it("期望样子通过", () => { expect(lintReport(GOOD)).toEqual([]); }); it("反例不过:太长、原始字节数、ISO 时间、adapter、camelCase 字段、开头不是结论、向用户要 ID、说教", () => { const rules = new Set(lintReport(BAD).map((i) => i.rule)); for (const r of [ "too_long", "raw_bytes", "iso_time", "internal_field", "no_conclusion_first", "asks_user_for_id", "lecture", ]) expect(rules, r).toContain(r); }); it("UUID、hash、能力名、Markdown 标题各自报出", () => { expect(lintReport("已提交。计划 062e5c51-1a2b-4c3d-8e9f-0123456789ab").map((i) => i.rule)).toContain("uuid"); expect(lintReport(`已提交,hash ${"ab".repeat(20)}`).map((i) => i.rule)).toContain("hash"); expect(lintReport("查了 downloader.list,下载器正常").map((i) => i.rule)).toContain("capability_id"); expect(lintReport("## 巡检\n下载器正常").map((i) => i.rule)).toContain("heading"); }); it("表格分隔行不计行数;年份、百分比、GB 不算原始数字", () => { const t = [ "3 个下载有问题:", "| 名称 | 状态 | 原因 |", "|---|---|---|", "| 沙丘2 | 失败 | 文件缺失 |", "| 三体 | 无进展 | 6 小时没有速度 |", "| 繁花 | 失败 | 站点删种 |", "其余 12 个正常,共 1024.5 GB,2026 年 9 月下完的占 63%。", ].join("\n"); expect(lintReport(t)).toEqual([]); }); });