mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-12 12:17:36 +00:00
test: 按模块导出接口重写黑盒判据(批次 13:sdk / skillmgr / remotedevice)
三个模块新增 test/ 黑盒契约,测试仅使用模块导出 API。
## internal/sdk
EffectiveProxyAuthNormalizesLegacyAndEmpty
ProxyHostNormalizationAndValidation
NewConfigGetPutRoundTrip
BuiltinProxyRegistrySnapshotsAndClear
覆盖鉴权归一化、DNS host 标签校验、ConfigAPI 往返、内置反代声明表快照与清理。
## internal/plugins/skillmgr
SkillDescriptionsListsScannedSkillsInStableOrder
SkillDescriptionsEmptyBeforeStart
通过公开 New/Start/SkillDescriptions/Name 验证技能目录扫描、稳定排序与空目录行为。
## internal/plugins/remotedevice
SaveResultThenGetResult
SupportsToolForUnknownAndUnknownCapability
SaveInlineMediaRequiresDirectoryAndPersistsBytes
覆盖结果存取、未知设备能力查询、媒体目录配置、base64 落盘、摘要和元数据。
## 变异验证
sdk 注册版本不递增 → registration did not advance public version
skillmgr 不写入技能表 → scanned descriptions missing
remotedevice 丢弃 SaveResult → GetResult=false
## 验证
go build ./... PASS
go vet ./... PASS
gofmt -l 干净
python3 .github/scripts/check-ci-gates.py PASS
go test ./... -count=1 48 个测试包 / 100 包无测试 / 0 FAIL
3 个新包 -race 全绿
源码目录 _test.go 0
This commit is contained in:
60
internal/plugins/remotedevice/test/registry_contract_test.go
Normal file
60
internal/plugins/remotedevice/test/registry_contract_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
package test
|
||||
|
||||
// remotedevice Registry 黑盒判据:通过公开方法观察结果保存、能力查询与媒体落盘。
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/plugins/remotedevice"
|
||||
)
|
||||
|
||||
func TestSaveResultThenGetResult(t *testing.T) {
|
||||
r := remotedevice.NewRegistry()
|
||||
want := map[string]interface{}{"status": "ok", "output": "done"}
|
||||
r.SaveResult("req-123", want)
|
||||
got, ok := r.GetResult("req-123")
|
||||
if !ok || got["status"] != "ok" || got["output"] != "done" {
|
||||
t.Fatalf("GetResult=%v,%v", got, ok)
|
||||
}
|
||||
if _, ok := r.GetResult("missing"); ok {
|
||||
t.Fatal("unknown request unexpectedly found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportsToolForUnknownAndUnknownCapability(t *testing.T) {
|
||||
r := remotedevice.NewRegistry()
|
||||
if r.SupportsTool("missing-device", "screensee") {
|
||||
t.Fatal("unknown device supports tool")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveInlineMediaRequiresDirectoryAndPersistsBytes(t *testing.T) {
|
||||
r := remotedevice.NewRegistry()
|
||||
payload := base64.StdEncoding.EncodeToString([]byte("media bytes"))
|
||||
if _, err := r.SaveInlineMedia("capture", payload, "application/octet-stream"); err == nil {
|
||||
t.Fatal("media saved without configured directory")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
r.SetMediaDir(dir)
|
||||
meta, err := r.SaveInlineMedia("capture", payload, "application/octet-stream")
|
||||
if err != nil {
|
||||
t.Fatalf("SaveInlineMedia: %v", err)
|
||||
}
|
||||
path, ok := meta["file"].(string)
|
||||
if !ok || filepath.Dir(path) != dir {
|
||||
t.Fatalf("file metadata=%v", meta)
|
||||
}
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("media file missing: %v", err)
|
||||
}
|
||||
if string(got) != "media bytes" || meta["size"] != len(got) || meta["mime"] != "application/octet-stream" {
|
||||
t.Fatalf("saved payload/metadata mismatch: %q / %v", got, meta)
|
||||
}
|
||||
if _, ok := meta["sha256"].(string); !ok {
|
||||
t.Fatalf("missing digest: %v", meta)
|
||||
}
|
||||
}
|
||||
54
internal/plugins/skillmgr/test/descriptions_contract_test.go
Normal file
54
internal/plugins/skillmgr/test/descriptions_contract_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package test
|
||||
|
||||
// skillmgr 黑盒判据:技能目录输入 → SkillDescriptions 可观察文本。
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/plugins/skillmgr"
|
||||
sdkpkg "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
)
|
||||
|
||||
func TestSkillDescriptionsListsScannedSkillsInStableOrder(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
for name, desc := range map[string]string{"zeta": "处理 zeta 的工作流。", "alpha": "处理 alpha 的工作流。"} {
|
||||
p := filepath.Join(dir, name)
|
||||
if err := os.MkdirAll(p, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body := "# " + name + "\n\n" + desc + "\n"
|
||||
if err := os.WriteFile(filepath.Join(p, "SKILL.md"), []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
p := skillmgr.New("contract-skillmgr", dir)
|
||||
// Start performs the public directory scan; provide only the two public
|
||||
// registrar callbacks required by the plugin SDK and no hidden test hooks.
|
||||
sdk := sdkpkg.New("contract-skillmgr", sdkpkg.SDKConfig{
|
||||
RegTool: func(string, sdkpkg.ToolDef, sdkpkg.ToolHandler) error { return nil },
|
||||
})
|
||||
if err := p.Start(sdk); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = p.Stop() })
|
||||
got := p.SkillDescriptions()
|
||||
if !strings.Contains(got, "alpha: 处理 alpha 的工作流。") || !strings.Contains(got, "zeta: 处理 zeta 的工作流。") {
|
||||
t.Fatalf("scanned descriptions missing: %q", got)
|
||||
}
|
||||
if strings.Index(got, "alpha:") > strings.Index(got, "zeta:") {
|
||||
t.Fatalf("descriptions not in stable name order: %q", got)
|
||||
}
|
||||
if p.Name() != "contract-skillmgr" {
|
||||
t.Fatalf("Name=%q", p.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillDescriptionsEmptyBeforeStart(t *testing.T) {
|
||||
p := skillmgr.New("empty", t.TempDir())
|
||||
if got := p.SkillDescriptions(); got != "" {
|
||||
t.Fatalf("unexpected descriptions: %q", got)
|
||||
}
|
||||
}
|
||||
68
internal/sdk/test/public_contract_test.go
Normal file
68
internal/sdk/test/public_contract_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package test
|
||||
|
||||
// internal/sdk 的模块级黑盒判据:公开 SDK 包装 API 的输入→输出。
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
"github.com/JianFeeeee/HologramAgent/pkg/types"
|
||||
)
|
||||
|
||||
func TestEffectiveProxyAuthNormalizesLegacyAndEmpty(t *testing.T) {
|
||||
for _, in := range []string{"", sdk.ProxyAuthAgentLegacy} {
|
||||
if got := sdk.EffectiveProxyAuth(in); got != sdk.ProxyAuthAgent {
|
||||
t.Errorf("EffectiveProxyAuth(%q)=%q", in, got)
|
||||
}
|
||||
}
|
||||
if got := sdk.EffectiveProxyAuth(sdk.ProxyAuthNone); got != sdk.ProxyAuthNone {
|
||||
t.Fatalf("explicit no-auth changed: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyHostNormalizationAndValidation(t *testing.T) {
|
||||
label := sdk.NormalizeProxyHost("My Plugin")
|
||||
if label == "" || !sdk.ValidProxyHostLabel(label) {
|
||||
t.Fatalf("normalized host %q is not a valid DNS label", label)
|
||||
}
|
||||
if sdk.ValidProxyHostLabel("bad..label") || sdk.ValidProxyHostLabel("-leading") {
|
||||
t.Fatal("invalid DNS host label accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConfigGetPutRoundTrip(t *testing.T) {
|
||||
first := &types.Config{LLM: types.LLMConfig{Provider: "first"}}
|
||||
api := sdk.NewConfig(first)
|
||||
if got := api.Get(); got != first {
|
||||
t.Fatal("Get did not return configured value")
|
||||
}
|
||||
second := &types.Config{LLM: types.LLMConfig{Provider: "second"}}
|
||||
api.Put(second)
|
||||
if got := api.Get(); got != second {
|
||||
t.Fatal("Put value not observable via Get")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinProxyRegistrySnapshotsAndClear(t *testing.T) {
|
||||
const name = "contract-sdk-proxy"
|
||||
sdk.ClearBuiltinProxyDefs(name)
|
||||
t.Cleanup(func() { sdk.ClearBuiltinProxyDefs(name) })
|
||||
before := sdk.BuiltinProxyVersion()
|
||||
sdk.RegisterBuiltinProxy(name, "web", sdk.ProxyDef{Target: "http://127.0.0.1:1234"})
|
||||
defs := sdk.BuiltinProxyDefs()
|
||||
if len(defs[name]) != 1 || defs[name][0].Name != "web" {
|
||||
t.Fatalf("registered proxy not observable: %+v", defs[name])
|
||||
}
|
||||
if sdk.BuiltinProxyVersion() <= before {
|
||||
t.Fatal("registration did not advance public version")
|
||||
}
|
||||
// Returned slice is a snapshot; mutating it must not mutate the registry.
|
||||
defs[name][0].Target = "mutated"
|
||||
if got := sdk.BuiltinProxyDefs()[name][0].Target; got != "http://127.0.0.1:1234" {
|
||||
t.Fatalf("snapshot mutation leaked into registry: %q", got)
|
||||
}
|
||||
sdk.ClearBuiltinProxyDefs(name)
|
||||
if _, ok := sdk.BuiltinProxyDefs()[name]; ok {
|
||||
t.Fatal("ClearBuiltinProxyDefs did not remove entry")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user