Commit Graph

1 Commits

Author SHA1 Message Date
c01f4d6350 test: 两个零测试包补黑箱判据 —— files(沙箱)与 tfidf
覆盖率分析显示这两个包**零测试文件**,而它们各自守着一条不能错的边界:

    internal/plugins/files   581 行  含沙箱逃逸防护
    internal/tfidf           306 行  记忆检索的 fallback 路径

## files:沙箱边界

581 行、0 判据。它是文件读写插件,`pathWithinSandbox` 写错一个字符就是
任意文件读写。没有判据意味着没人知道它现在是否还挡得住。

6 条判据全部走**真实装配**(调 `Start()` 而不是手搓 Plugin 结构),
通过注入的 registrar 捕获 handler,然后以「输入 {path, content} → 输出」断言:

    TestToolsAreRegistered           四个工具都在
    TestReadWriteRoundTrip           写进去能原样读回
    TestSandboxEscapeIsRejected      ★ 沙箱外路径必须被拒(3 种逃逸形态)
    TestSandboxAllowsInside          反向:沙箱内必须能读
    TestEmptyPathIsRejected          read/write 拒绝空 path
    TestLsEmptyPathMeansCurrentDir   ls 把空 path 当 "."
    TestLsListsDirectory             ls 列出目录内容

**沙箱那条的判据设计**:沙箱外造一个**真实存在**的文件。若只拦不存在的
路径,判据会假绿;文件真实存在而仍被拒,才证明是沙箱在起作用。

变异(`pathWithinSandbox` 加 `if true { return true }`)⇒ 实测红:

    ★ 沙箱外路径 ".../secret.txt" 被接受了,返回 {"content":"SECRET",...}
      这是任意文件读取。沙箱根: ...

## tfidf:检索判别力

306 行、0 判据。它是没有神经网络 embedder 时的文本相似度来源 ——
判据错 = 检索错,而检索错是静默的(不报错,只是答非所问)。

判据**只断言契约,不断言数值**:TF-IDF 的权重取决于语料统计,写死数值
等于把训练集也当成契约。6 条钉的是换了实现也必须成立的性质:

    TestVectorizeIsDeterministic            同输入同输出(map 遍历浮点累加
                                            会产生末位差异,让缓存/去重失效)
    TestVectorizeHasFixedDimAndNonNegative  维度恒定、非 NaN/Inf、非负
    TestEmptyTextGivesZeroVector            空文本不 panic 且给零向量
    TestSearchRanksRelatedAboveUnrelated    ★ 核心质量判据:只排序,不看数值
    TestAddRemoveChangesResults             增删后检索结果随之变化
    TestFingerprintAndDim                   元信息非空

`TestSearchRanksRelatedAboveUnrelated` 直接对治 engineering-lessons §1.1
记的那个坑:16 条判据全绿而检索结果完全无关(分数全挤在 0.75~0.79)。
所以它只问"billing invoice 的查询里,auth 文档有没有排到相关文档前面"。

变异(`Search` 提前 `return nil`)⇒ 实测红两条。

## 一处判据写错了(已按实际契约修正)

第一版把 `files_ls` 也纳入"空 path 必须拒绝",实测红:

    ★ files_ls 接受了空 path,返回 {"content":"(empty directory)"}

查代码(plugin.go:488)发现那是**明确设计**:`if path == "" { path = "." }`。
不是缺陷,是我把三条工具的输入契约当成同一条了。改成两条判据分开钉:

    TestEmptyPathIsRejected          read/write 拒绝空 path
    TestLsEmptyPathMeansCurrentDir   ls 把空 path 当 "."

把差异显式写下来,以后有人想"统一都拒绝空 path"时能看到这是有意的。

## 覆盖率

    internal/plugins/files   0.0% → 54.1%
    internal/tfidf           0.0% → 有判据(契约全覆盖)

## 验证

    go build ./...            PASS
    go vet ./...              PASS
    gofmt -l                  干净
    go test ./... -count=1    67 包全过,0 FAIL(新增 2 个 test 包)
2026-11-04 20:41:43 +08:00