Files
HomeAgent/internal/plugin/proc/test/public_contract_test.go
JianFeeeee 3d05dcac71 test: 按模块导出接口重写黑盒判据(批次 14:mcp / proc / cfgmgr)
三个模块新增 test/ 黑盒契约,测试仅通过导出 API 驱动。

## internal/plugins/mcp

    TestListToolsThroughHTTP
    TestCallToolSuccessAndEmptyContentHandling
    TestCallToolSurfacesIsErrorAsFailure
    TestCallToolSurfacesRPCError
    TestCallToolSurfacesHTTPError
    TestListToolsParseSSEStreamFrame

使用 httptest + NewSSETransport + NewServer,覆盖 JSON-RPC 工具列表、
成功调用、空内容失败、MCP isError、RPC 错误、HTTP 错误和 SSE data 帧。

## internal/plugin/proc

    TestMajorVersionParsing
    TestKnownCapabilitiesExcludesCoreAndIsStable
    TestSharedRefSliceBoundary
    TestPublicCapabilityConstantsStable

覆盖版本主段解析、能力清单排序/去重/core 排除、共享引用切片边界和能力常量。

## internal/plugins/cfgmgr

    TestStartRegistersConfigToolsWithConcurrencyMarkers
    TestStopIsSafe

通过公开 Start 注入 SDK 工具注册回调,观察七个配置工具及其
Serial/ParallelSafe 并发契约;未访问私有 handler。

## 变异验证

    mcp       忽略 MCP isError         → isError 未被判失败
    proc      MajorVersion 不截断       → 多组版本断言红
    cfgmgr    写工具取消 Serial 标记    → 写工具未声明 Serial

## 验证

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

88 lines
2.3 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/plugin/proc 的黑盒判据:公开纯函数与值对象语义。
import (
"testing"
"github.com/JianFeeeee/HologramAgent/internal/plugin/proc"
)
func TestMajorVersionParsing(t *testing.T) {
cases := []struct{ in, want string }{
{"1.4.0", "1"},
{"v2.10.3", "2"},
{"0.9", "0"},
{"17", "17"},
{"", ""},
{" v3.1 ", "3"},
}
for _, c := range cases {
if got := proc.MajorVersion(c.in); got != c.want {
t.Errorf("MajorVersion(%q)=%q,期望 %q", c.in, got, c.want)
}
}
}
func TestKnownCapabilitiesExcludesCoreAndIsStable(t *testing.T) {
first := proc.KnownCapabilities()
second := proc.KnownCapabilities()
if len(first) == 0 {
t.Fatal("KnownCapabilities 为空")
}
for i := range first {
if first[i] == "core" {
t.Fatal("core 能力出现在声明清单里(无需声明)")
}
if i > 0 && first[i-1] >= first[i] {
t.Fatalf("能力清单未按升序:%v", first)
}
}
for i := range first {
if first[i] != second[i] {
t.Fatal("两次调用结果不一致(无确定性)")
}
}
}
func TestSharedRefSliceBoundary(t *testing.T) {
data := []byte("0123456789")
ok := proc.SharedRef{Offset: 2, Length: 4}
if got := ok.Slice(data); string(got) != "2345" {
t.Fatalf("Slice=%q", got)
}
if ok.IsZero() {
t.Fatal("非零 ref 被判为零值")
}
if !(proc.SharedRef{}).IsZero() {
t.Fatal("零值 ref 未判零")
}
// 越界: offset+length 超出 data 时返回 nil
oob := proc.SharedRef{Offset: 8, Length: 8}
if oob.Slice(data) != nil {
t.Fatalf("越界 Slice 未返回 nil:%q", oob.Slice(data))
}
// 零值: 长度 0
if zero := (proc.SharedRef{}).Slice(data); zero != nil {
t.Fatalf("零值 ref Slice 应 nil:%q", zero)
}
}
func TestPublicCapabilityConstantsStable(t *testing.T) {
want := map[string]proc.Capability{
"core": proc.CapCore, "io": proc.CapIO, "memory": proc.CapMemory,
"doc_memory": proc.CapDocMemory, "knowledge": proc.CapKnowledge,
"text_memory": proc.CapTextMemory, "llm": proc.CapLLM,
"social": proc.CapSocial, "events": proc.CapEvents,
"plugin_mgr": proc.CapPluginMgr, "kernel_control": proc.CapKernelControl,
"settings_cross": proc.CapCrossPluginSettings,
}
for name, cap := range want {
if string(cap) != name {
t.Errorf("常量 %q 值与名不符:%q", name, cap)
}
}
_ = proc.MethodHandshake
_ = proc.DefaultHandshakeTimeout
}