mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 01:48:11 +00:00
- Six-phase plan complete: webui/cli/healthcheck/pluginmgr/clawhubadapter now interact with the kernel exclusively via internal/sdk interfaces; all Configure() calls and package-level global injection removed - buildSDK in internal/plugin/registry.go is the single assembly point - Add internal/sdk/events.go exporting event types/constants - Fix ProviderManager cooldown sharing: LuaAdaptedProvider.Name() now returns the source name instead of lua_<adapter>, so multiple sources sharing an adapter (single script load via shared VM AdapterCache) no longer share failure-cooldown state - Verified: build/vet/tests green, deployed to homeagent.service with full plugin capability testing via local OpenAI-compatible mock
334 lines
7.5 KiB
Go
334 lines
7.5 KiB
Go
package healthcheck
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"testing"
|
|
|
|
agentCore "gitcode.com/JianFeeeee/HomeAgent/internal/agent/core"
|
|
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
|
|
"gitcode.com/JianFeeeee/HomeAgent/internal/knowledge"
|
|
"gitcode.com/JianFeeeee/HomeAgent/internal/memory"
|
|
doc "gitcode.com/JianFeeeee/HomeAgent/internal/memory/document"
|
|
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
|
)
|
|
|
|
type toolCapture struct {
|
|
handlers map[string]sdk.ToolHandler
|
|
defs map[string]sdk.ToolDef
|
|
}
|
|
|
|
func newToolCapture() *toolCapture {
|
|
return &toolCapture{
|
|
handlers: make(map[string]sdk.ToolHandler),
|
|
defs: make(map[string]sdk.ToolDef),
|
|
}
|
|
}
|
|
|
|
func (tc *toolCapture) RegisterTool(name string, def sdk.ToolDef, handler sdk.ToolHandler) error {
|
|
tc.handlers[name] = handler
|
|
tc.defs[name] = def
|
|
return nil
|
|
}
|
|
func (tc *toolCapture) RegisterStage(stage sdk.Stage, handler sdk.StageHandler) {}
|
|
func (tc *toolCapture) RegisterAPI(name string) error { return nil }
|
|
|
|
func newTestSDK(cfg sdk.SDKConfig) *sdk.PluginSDK {
|
|
if cfg.Settings == nil {
|
|
cfg.Settings = sdk.NewSettings("healthcheck", nil)
|
|
}
|
|
if cfg.Tool == nil {
|
|
cfg.Tool = sdk.NewTool(agentCore.NewStageHost(), agentIO.NewIOManager())
|
|
}
|
|
return sdk.New("healthcheck", cfg)
|
|
}
|
|
|
|
func setupPlugin() (*Plugin, *toolCapture, error) {
|
|
return setupPluginWith(sdk.SDKConfig{})
|
|
}
|
|
|
|
func setupPluginWith(cfg sdk.SDKConfig) (*Plugin, *toolCapture, error) {
|
|
tc := newToolCapture()
|
|
cfg.RegTool = tc.RegisterTool
|
|
cfg.RegStage = tc.RegisterStage
|
|
cfg.RegAPI = tc.RegisterAPI
|
|
p := New("healthcheck")
|
|
s := newTestSDK(cfg)
|
|
if err := p.Start(s); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return p, tc, nil
|
|
}
|
|
|
|
func TestToolsRegistered(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := []string{
|
|
"healthcheck",
|
|
"healthcheck_plugins",
|
|
"healthcheck_tools",
|
|
"healthcheck_memory",
|
|
"healthcheck_report",
|
|
"healthcheck_perf",
|
|
}
|
|
for _, name := range expected {
|
|
if _, ok := tc.handlers[name]; !ok {
|
|
t.Errorf("tool %q not registered", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHealthcheckFull(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["healthcheck"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if resp["status"] != "ok" {
|
|
t.Fatalf("expected status ok, got %v", resp["status"])
|
|
}
|
|
|
|
checks := resp["checks"].([]interface{})
|
|
if len(checks) == 0 {
|
|
t.Fatal("expected at least some checks")
|
|
}
|
|
|
|
for _, c := range checks {
|
|
cr := c.(map[string]interface{})
|
|
name := cr["name"].(string)
|
|
pass := cr["pass"].(bool)
|
|
if !pass && cr["status"] != "skip" {
|
|
t.Errorf("check %q failed: %v (detail: %v)", name, cr["status"], cr["detail"])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHealthcheckPlugins(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["healthcheck_plugins"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if resp["status"] != "ok" {
|
|
t.Fatalf("expected status ok, got %v", resp["status"])
|
|
}
|
|
}
|
|
|
|
func TestHealthcheckToolsList(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["healthcheck_tools"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if resp["status"] != "ok" {
|
|
t.Fatalf("expected status ok, got %v", resp["status"])
|
|
}
|
|
}
|
|
|
|
func TestHealthcheckMemoryNotAvailable(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["healthcheck_memory"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
// Memory is nil in this setup, so it should skip gracefully
|
|
if _, ok := resp["pass"]; ok {
|
|
pass := resp["pass"].(bool)
|
|
if !pass {
|
|
t.Fatalf("expected pass=true when memory is nil, got false: %v", resp)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHealthcheckWithMemory(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "hc_test_*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
memDB, err := memory.NewGraphDB(tmpDir + "/test.db")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer memDB.Close()
|
|
|
|
_, tc, err := setupPluginWith(sdk.SDKConfig{Memory: sdk.NewGraphMemory(memDB)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["healthcheck_memory"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if resp["status"] != "ok" {
|
|
t.Fatalf("expected status ok, got %v", resp["status"])
|
|
}
|
|
if pass, ok := resp["pass"].(bool); !ok || !pass {
|
|
t.Fatalf("expected pass=true, got pass=%v status=%v detail=%v", pass, resp["status"], resp["detail"])
|
|
}
|
|
}
|
|
|
|
func TestHealthcheckWithKnowledge(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "hc_know_test_*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
ks := knowledge.NewStore(tmpDir)
|
|
if err := ks.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ks.Stop()
|
|
|
|
_, tc, err := setupPluginWith(sdk.SDKConfig{Knowledge: sdk.NewKnowledge(ks)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["healthcheck"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if resp["status"] != "ok" {
|
|
t.Fatalf("expected status ok, got %v", resp["status"])
|
|
}
|
|
|
|
checks := resp["checks"].([]interface{})
|
|
var knowledgeCheck map[string]interface{}
|
|
for _, c := range checks {
|
|
cr := c.(map[string]interface{})
|
|
if cr["name"] == "knowledge" {
|
|
knowledgeCheck = cr
|
|
break
|
|
}
|
|
}
|
|
|
|
if knowledgeCheck == nil {
|
|
t.Fatal("expected knowledge check in results")
|
|
}
|
|
}
|
|
|
|
func TestHealthcheckWithDocStore(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "hc_doc_test_*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
ds := doc.NewStore(tmpDir)
|
|
if err := ds.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ds.Stop()
|
|
|
|
_, tc, err := setupPluginWith(sdk.SDKConfig{DocMemory: sdk.NewDocMemory(ds)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["healthcheck"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if resp["status"] != "ok" {
|
|
t.Fatalf("expected status ok, got %v", resp["status"])
|
|
}
|
|
|
|
checks := resp["checks"].([]interface{})
|
|
var docCheck map[string]interface{}
|
|
for _, c := range checks {
|
|
cr := c.(map[string]interface{})
|
|
if cr["name"] == "documents" {
|
|
docCheck = cr
|
|
break
|
|
}
|
|
}
|
|
|
|
if docCheck == nil {
|
|
t.Fatal("expected documents check in results")
|
|
}
|
|
}
|
|
|
|
func TestLLMReportCollection(t *testing.T) {
|
|
p := &Plugin{name: "healthcheck"}
|
|
if len(p.reports) != 0 {
|
|
t.Fatal("expected empty reports")
|
|
}
|
|
p.mu.Lock()
|
|
p.reports = append(p.reports, llmReport{ToolName: "test_tool", Status: "ok", Detail: "test passed"})
|
|
count := len(p.reports)
|
|
p.mu.Unlock()
|
|
if count != 1 {
|
|
t.Fatalf("expected 1 report, got %d", count)
|
|
}
|
|
if p.reports[0].ToolName != "test_tool" {
|
|
t.Fatalf("expected tool_name=test_tool, got %s", p.reports[0].ToolName)
|
|
}
|
|
}
|
|
|
|
|