package codeagent import ( "context" "testing" "github.com/orchard9/rdev/internal/domain" "github.com/orchard9/rdev/internal/port" ) // mockAgent is a test implementation of port.CodeAgent. type mockAgent struct { provider domain.AgentProvider name string available bool } func (m *mockAgent) Name() string { return m.name } func (m *mockAgent) Provider() domain.AgentProvider { return m.provider } func (m *mockAgent) Available(ctx context.Context) bool { return m.available } func (m *mockAgent) Execute(ctx context.Context, req *domain.AgentRequest, handler domain.AgentEventHandler) (*domain.AgentResult, error) { return &domain.AgentResult{ExitCode: 0}, nil } func (m *mockAgent) Cancel(ctx context.Context, sessionID string) error { return nil } func (m *mockAgent) Capabilities() domain.AgentCapabilities { return domain.AgentCapabilities{Provider: m.provider} } var _ port.CodeAgent = (*mockAgent)(nil) func TestRegistry_RegisterAndGet(t *testing.T) { reg := NewRegistry() agent := &mockAgent{ provider: domain.AgentProviderClaudeCode, name: "Claude Code", available: true, } reg.Register(agent) got := reg.Get(domain.AgentProviderClaudeCode) if got == nil { t.Fatal("expected agent, got nil") } if got.Name() != "Claude Code" { t.Errorf("expected name 'Claude Code', got %q", got.Name()) } } func TestRegistry_GetUnregistered(t *testing.T) { reg := NewRegistry() got := reg.Get(domain.AgentProviderOpenCode) if got != nil { t.Errorf("expected nil for unregistered provider, got %v", got) } } func TestRegistry_DefaultFirstRegistered(t *testing.T) { reg := NewRegistry() claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true} opencode := &mockAgent{provider: domain.AgentProviderOpenCode, name: "OpenCode", available: true} reg.Register(claude) reg.Register(opencode) def := reg.Default() if def == nil { t.Fatal("expected default agent, got nil") } if def.Provider() != domain.AgentProviderClaudeCode { t.Errorf("expected first registered (claudecode) as default, got %v", def.Provider()) } } func TestRegistry_SetDefault(t *testing.T) { reg := NewRegistry() claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true} opencode := &mockAgent{provider: domain.AgentProviderOpenCode, name: "OpenCode", available: true} reg.Register(claude) reg.Register(opencode) err := reg.SetDefault(domain.AgentProviderOpenCode) if err != nil { t.Fatalf("unexpected error: %v", err) } def := reg.Default() if def.Provider() != domain.AgentProviderOpenCode { t.Errorf("expected opencode as default, got %v", def.Provider()) } } func TestRegistry_SetDefaultUnregistered(t *testing.T) { reg := NewRegistry() claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true} reg.Register(claude) err := reg.SetDefault(domain.AgentProviderOpenCode) if err == nil { t.Error("expected error setting unregistered default") } } func TestRegistry_Available(t *testing.T) { reg := NewRegistry() claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true} opencode := &mockAgent{provider: domain.AgentProviderOpenCode, name: "OpenCode", available: true} reg.Register(claude) reg.Register(opencode) providers := reg.Available() if len(providers) != 2 { t.Errorf("expected 2 providers, got %d", len(providers)) } // Check both are present (order not guaranteed) found := make(map[domain.AgentProvider]bool) for _, p := range providers { found[p] = true } if !found[domain.AgentProviderClaudeCode] || !found[domain.AgentProviderOpenCode] { t.Errorf("expected both providers in list, got %v", providers) } } func TestRegistry_AvailableAgents(t *testing.T) { reg := NewRegistry() claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true} opencode := &mockAgent{provider: domain.AgentProviderOpenCode, name: "OpenCode", available: false} reg.Register(claude) reg.Register(opencode) ctx := context.Background() agents := reg.AvailableAgents(ctx) if len(agents) != 1 { t.Errorf("expected 1 available agent, got %d", len(agents)) } if agents[0].Provider() != domain.AgentProviderClaudeCode { t.Errorf("expected claude as available, got %v", agents[0].Provider()) } } func TestRegistry_DefaultEmpty(t *testing.T) { reg := NewRegistry() def := reg.Default() if def != nil { t.Errorf("expected nil default for empty registry, got %v", def) } } func TestRegistry_Count(t *testing.T) { reg := NewRegistry() if reg.Count() != 0 { t.Errorf("expected 0 count, got %d", reg.Count()) } reg.Register(&mockAgent{provider: domain.AgentProviderClaudeCode, available: true}) if reg.Count() != 1 { t.Errorf("expected 1 count, got %d", reg.Count()) } reg.Register(&mockAgent{provider: domain.AgentProviderOpenCode, available: true}) if reg.Count() != 2 { t.Errorf("expected 2 count, got %d", reg.Count()) } } func TestRegistry_DefaultProvider(t *testing.T) { reg := NewRegistry() // Empty registry if p := reg.DefaultProvider(); p != "" { t.Errorf("expected empty default provider, got %q", p) } reg.Register(&mockAgent{provider: domain.AgentProviderClaudeCode, available: true}) if p := reg.DefaultProvider(); p != domain.AgentProviderClaudeCode { t.Errorf("expected claudecode, got %q", p) } } func TestRegistry_ConcurrentAccess(t *testing.T) { reg := NewRegistry() // Pre-register one agent reg.Register(&mockAgent{provider: domain.AgentProviderClaudeCode, available: true}) done := make(chan struct{}) const goroutines = 10 const iterations = 100 // Concurrent reads and writes for i := 0; i < goroutines; i++ { go func(id int) { defer func() { done <- struct{}{} }() for j := 0; j < iterations; j++ { // Mix of operations switch j % 5 { case 0: reg.Get(domain.AgentProviderClaudeCode) case 1: reg.Default() case 2: reg.Available() case 3: reg.Count() case 4: reg.AvailableAgents(context.Background()) } } }(i) } // Wait for all goroutines for i := 0; i < goroutines; i++ { <-done } // Verify state is consistent if reg.Count() != 1 { t.Errorf("expected count 1 after concurrent access, got %d", reg.Count()) } } func TestRegistry_ReRegisterOverwrites(t *testing.T) { reg := NewRegistry() agent1 := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude v1", available: true} agent2 := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude v2", available: true} reg.Register(agent1) reg.Register(agent2) // Should overwrite got := reg.Get(domain.AgentProviderClaudeCode) if got.Name() != "Claude v2" { t.Errorf("expected 'Claude v2' after re-register, got %q", got.Name()) } // Count should still be 1 if reg.Count() != 1 { t.Errorf("expected count 1 after re-register, got %d", reg.Count()) } }