// PicoClaw - Ultra-lightweight personal AI agent // Inspired by and based on nanobot: https://github.com/HKUDS/nanobot // License: MIT // // Copyright (c) 2026 PicoClaw contributors package agent import ( "context" "errors" "strings" "testing" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/mcp" ) func boolPtr(b bool) *bool { return &b } func TestMCPRuntimeResetClearsState(t *testing.T) { var rt mcpRuntime manager := mcp.NewManager() rt.setManager(manager) rt.setInitErr(errors.New("stale init error")) rt.initOnce.Do(func() {}) got := rt.reset() if got != manager { t.Fatalf("reset() manager = %p, want %p", got, manager) } if rt.hasManager() { t.Fatal("expected manager to be cleared after reset") } if err := rt.getInitErr(); err != nil { t.Fatalf("getInitErr() = %v, want nil", err) } reran := false rt.initOnce.Do(func() { reran = true }) if !reran { t.Fatal("expected initOnce to be reset") } } func TestReloadProviderAndConfig_ResetsMCPRuntime(t *testing.T) { al, cfg, _, _, cleanup := newTestAgentLoop(t) defer cleanup() defer al.Close() manager := mcp.NewManager() al.mcp.setManager(manager) al.mcp.setInitErr(errors.New("stale init error")) al.mcp.initOnce.Do(func() {}) if !al.mcp.hasManager() { t.Fatal("expected MCP manager to exist before reload") } if err := al.ReloadProviderAndConfig(context.Background(), &mockProvider{}, cfg); err != nil { t.Fatalf("ReloadProviderAndConfig() error = %v", err) } if al.mcp.hasManager() { t.Fatal("expected MCP manager to be cleared when reloaded config has MCP disabled") } if err := al.mcp.getInitErr(); err != nil { t.Fatalf("getInitErr() = %v, want nil", err) } reran := false al.mcp.initOnce.Do(func() { reran = true }) if !reran { t.Fatal("expected MCP initOnce to be reset after reload") } } func TestServerIsDeferred(t *testing.T) { tests := []struct { name string discoveryEnabled bool serverDeferred *bool want bool }{ // --- global false always wins: per-server deferred is ignored --- { name: "global false: per-server deferred=true is ignored", discoveryEnabled: false, serverDeferred: boolPtr(true), want: false, }, { name: "global false: per-server deferred=false stays false", discoveryEnabled: false, serverDeferred: boolPtr(false), want: false, }, // --- global true: per-server override applies --- { name: "global true: per-server deferred=false opts out", discoveryEnabled: true, serverDeferred: boolPtr(false), want: false, }, { name: "global true: per-server deferred=true stays true", discoveryEnabled: true, serverDeferred: boolPtr(true), want: true, }, // --- no per-server override: fall back to global --- { name: "no per-server field, global discovery enabled", discoveryEnabled: true, serverDeferred: nil, want: true, }, { name: "no per-server field, global discovery disabled", discoveryEnabled: false, serverDeferred: nil, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { serverCfg := config.MCPServerConfig{Deferred: tt.serverDeferred} got := serverIsDeferred(tt.discoveryEnabled, serverCfg) if got != tt.want { t.Errorf("serverIsDeferred(discoveryEnabled=%v, deferred=%v) = %v, want %v", tt.discoveryEnabled, tt.serverDeferred, got, tt.want) } }) } } func TestEnsureMCPInitialized_LoadFailureSetsInitErr(t *testing.T) { al, cfg, _, _, cleanup := newTestAgentLoop(t) defer cleanup() defer al.Close() cfg.Tools = config.ToolsConfig{ MCP: config.MCPConfig{ ToolConfig: config.ToolConfig{Enabled: true}, Servers: map[string]config.MCPServerConfig{ "broken": { Enabled: true, Command: "picoclaw-command-that-does-not-exist-for-mcp-tests", }, }, }, } err := al.ensureMCPInitialized(context.Background()) if err == nil { t.Fatal("ensureMCPInitialized() error = nil, want load failure") } if !strings.Contains(err.Error(), "failed to load MCP servers") { t.Fatalf("ensureMCPInitialized() error = %q, want wrapped load failure", err.Error()) } initErr := al.mcp.getInitErr() if initErr == nil { t.Fatal("getInitErr() = nil, want cached load failure") } if !strings.Contains(initErr.Error(), "failed to load MCP servers") { t.Fatalf("getInitErr() = %q, want wrapped load failure", initErr.Error()) } if al.mcp.getManager() != nil { t.Fatal("expected MCP manager to remain nil after load failure") } err = al.ensureMCPInitialized(context.Background()) if err == nil { t.Fatal("second ensureMCPInitialized() error = nil, want cached load failure") } if !strings.Contains(err.Error(), "failed to load MCP servers") { t.Fatalf("second ensureMCPInitialized() error = %q, want wrapped load failure", err.Error()) } }