Files
HomeAgent/internal/nlp/test/extractor_contract_test.go
JianFeeeee b2a5fad95f test: 按模块导出接口重写黑盒判据(批次 9:seq / nlp / pipeline)
3 个文件全部在 `<module>/test/`,源码目录仍零 `_test.go`。
共 17 条判据,只调导出接口,输入 → 可观察输出。

`internal/kernelmod/pluginhost` 本批跳过:它的 PluginCatalog 只有
`newCatalog` 这个未导出构造,没有对外入口。按「不为测试导出内部」的
规矩,不为它造壳。

## internal/plugins/seq —— Parse / Store

    TestParseValidSequence              合法 JSON → 名字/Boot/Groups
    TestParseRejectsUnknownField        拼错字段必须报 unknown field
    TestParseRejectsInvalidJSON         坏 JSON 报错
    TestStoreRoundTrip                  Save → List/Load → Delete
    TestLoadMissingReportsError
    TestCheckNewRejectsSelfRecursion    #self 调用被拒
    TestCheckNewAcceptsValidCall        合法 group 调用通过

**判据写错过三次,都按真实输入契约修正**:
① group 的工具字段叫 `tools` 不叫 `steps`;
② `tools` 在 JSON 里是字符串(若干以 `;` 分隔的 JSON 对象),不是数组;
③ 最后一个工具末尾必须有 `;`。
`CheckNew` 守的是 seq_call 的 target 校验,不是"同名重复",判据按此改。

**还发现一条假绿并修掉**:`TestParseRejectsUnknownField` 第一版只断言
`err != nil`,但输入没带 group —— "没有任何 group"同样会报错,
去掉 `DisallowUnknownFields` 后判据仍绿。改成带合法 group
且断言错误里含 `unknown field`,变异才真红。

## internal/nlp —— 配置 / Extractor

    TestDefaultExtractorConfig          默认权重可用
    TestSetFusionWeightsRoundTrip       设入 → FusionConfig 读回
    TestSetDefaultParserRoundTrip       SetDefaultParser → GetDefaultParser
    TestExtractEmptyTextReturnsEmptySet
    TestExtractNonEmptyTextDoesNotPanic

不带 ONNX 模型时默认 parser 为 nil、`NewExtractor(nil)` 走 fallback,
这是真实契约;判据不要求它非 nil,而是用 stub 验证 Set/Get 往返。

## internal/memory/pipeline —— Distiller

    TestAppendThenRecentRecords          Append → GetRecentRecords
    TestGetRecentRecordsRespectsLimit    limit + 取最新 N 条
    TestPauseResumeStopped               暂停/恢复可观察
    TestStatsIsUsable
    TestRawPathIsUnderDataDir

## 变异验证(改回旧实现必须红)

    seq      去掉 DisallowUnknownFields → ★ 未知字段被静默接受
    nlp      SetFusionWeights 丢 alpha → ★ FusionConfig 不符
    pipeline Append 不记录              → ★ GetRecentRecords = 0 条

全部还原后复跑全绿。

## 验证

    go build ./...                            PASS
    go vet ./...                              PASS
    gofmt -l                                  干净
    python3 .github/scripts/check-ci-gates.py PASS
    go test ./... -count=1                    35 个测试包 / 100 包无测试 / 0 FAIL
    3 个新包 -race                            全绿
    源码目录 _test.go                         0
2026-11-09 14:20:59 +08:00

66 lines
2.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package test
// internal/nlp 的黑盒判据:NewExtractor / 配置 / Extract 的输入 → 输出。
import (
"testing"
"github.com/JianFeeeee/HologramAgent/internal/nlp"
)
// TestDefaultExtractorConfig:默认融合权重是可用常量。
func TestDefaultExtractorConfig(t *testing.T) {
c := nlp.DefaultExtractorConfig()
if c.FusionAlpha <= 0 || c.FusionBeta <= 0 || c.FusionThreshold <= 0 {
t.Fatalf("★ 默认配置不可用:%+v", c)
}
}
// TestSetFusionWeightsRoundTrip:设入的权重能从 FusionConfig 读回。
func TestSetFusionWeightsRoundTrip(t *testing.T) {
e := nlp.NewExtractor(nil)
e.SetFusionWeights(0.25, 0.75, 0.42)
got := e.FusionConfig()
if got.FusionAlpha != 0.25 || got.FusionBeta != 0.75 || got.FusionThreshold != 0.42 {
t.Fatalf("★ FusionConfig = %+v", got)
}
}
// stubParser 满足 Parser 接口,作为 SetDefaultParser 的输入。
type stubParser struct{}
func (p *stubParser) Parse(text string) (*nlp.ParseResult, error) {
return &nlp.ParseResult{}, nil
}
// TestSetDefaultParserRoundTrip:设入的 parser 能从 GetDefaultParser 读回。
func TestSetDefaultParserRoundTrip(t *testing.T) {
want := &stubParser{}
nlp.SetDefaultParser(want)
t.Cleanup(func() { nlp.SetDefaultParser(nil) })
if got := nlp.GetDefaultParser(); got != want {
t.Fatalf("★ GetDefaultParser = %v,期望刚设入的 parser", got)
}
}
// TestExtractEmptyTextReturnsEmptySet:空文本返回空结果,不 panic。
func TestExtractEmptyTextReturnsEmptySet(t *testing.T) {
e := nlp.NewExtractor(nil)
got := e.Extract("")
if got == nil {
t.Fatal("★ Extract 返回 nil")
}
if len(got.Triples) != 0 {
t.Fatalf("★ 空文本提取出 %d 条三元组", len(got.Triples))
}
}
// TestExtractNonEmptyTextDoesNotPanic:非空文本输出可用(不钉死内容)。
func TestExtractNonEmptyTextDoesNotPanic(t *testing.T) {
e := nlp.NewExtractor(nil)
got := e.Extract("billing服务 的 端口 是 8499")
if got == nil {
t.Fatal("★ Extract 返回 nil")
}
}