package protocol import ( "context" "testing" "time" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peerstore" "github.com/libp2p/go-libp2p/core/test" ) func TestNewResolver(t *testing.T) { // Create a mock peerstore mockPeerstore := &mockPeerstore{} resolver := NewResolver(mockPeerstore) if resolver == nil { t.Fatal("resolver is nil") } if resolver.peerstore != mockPeerstore { t.Error("peerstore not set correctly") } if resolver.defaultStrategy != StrategyBestMatch { t.Errorf("expected default strategy %v, got %v", StrategyBestMatch, resolver.defaultStrategy) } if resolver.maxPeersPerResult != 5 { t.Errorf("expected max peers per result 5, got %d", resolver.maxPeersPerResult) } } func TestResolverWithOptions(t *testing.T) { mockPeerstore := &mockPeerstore{} resolver := NewResolver(mockPeerstore, WithCacheTTL(10*time.Minute), WithDefaultStrategy(StrategyPriority), WithMaxPeersPerResult(10), ) if resolver.cacheTTL != 10*time.Minute { t.Errorf("expected cache TTL 10m, got %v", resolver.cacheTTL) } if resolver.defaultStrategy != StrategyPriority { t.Errorf("expected strategy %v, got %v", StrategyPriority, resolver.defaultStrategy) } if resolver.maxPeersPerResult != 10 { t.Errorf("expected max peers 10, got %d", resolver.maxPeersPerResult) } } func TestRegisterPeer(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) peerID := test.RandPeerIDFatal(t) capability := &PeerCapability{ Agent: "claude", Role: "frontend", Capabilities: []string{"react", "javascript"}, Models: []string{"claude-3"}, Specialization: "frontend", Status: "ready", Metadata: make(map[string]string), } resolver.RegisterPeer(peerID, capability) // Verify peer was registered caps := resolver.GetPeerCapabilities() if len(caps) != 1 { t.Errorf("expected 1 peer, got %d", len(caps)) } registeredCap, exists := caps[peerID] if !exists { t.Error("peer not found in capabilities") } if registeredCap.Agent != capability.Agent { t.Errorf("expected agent %s, got %s", capability.Agent, registeredCap.Agent) } if registeredCap.PeerID != peerID { t.Error("peer ID not set correctly") } } func TestUnregisterPeer(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) peerID := test.RandPeerIDFatal(t) capability := &PeerCapability{ Agent: "claude", Role: "frontend", } // Register then unregister resolver.RegisterPeer(peerID, capability) resolver.UnregisterPeer(peerID) caps := resolver.GetPeerCapabilities() if len(caps) != 0 { t.Errorf("expected 0 peers after unregister, got %d", len(caps)) } } func TestUpdatePeerStatus(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) peerID := test.RandPeerIDFatal(t) capability := &PeerCapability{ Agent: "claude", Role: "frontend", Status: "ready", } resolver.RegisterPeer(peerID, capability) resolver.UpdatePeerStatus(peerID, "busy") caps := resolver.GetPeerCapabilities() updatedCap := caps[peerID] if updatedCap.Status != "busy" { t.Errorf("expected status 'busy', got '%s'", updatedCap.Status) } } func TestResolveURI(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) // Register some test peers peerID1 := test.RandPeerIDFatal(t) peerID2 := test.RandPeerIDFatal(t) resolver.RegisterPeer(peerID1, &PeerCapability{ Agent: "claude", Role: "frontend", Capabilities: []string{"react", "javascript"}, Status: "ready", Metadata: map[string]string{"project": "chorus"}, }) resolver.RegisterPeer(peerID2, &PeerCapability{ Agent: "claude", Role: "backend", Capabilities: []string{"go", "api"}, Status: "ready", Metadata: map[string]string{"project": "chorus"}, }) // Test exact match uri, err := ParseBzzzURI("CHORUS://claude:frontend@chorus:react") if err != nil { t.Fatalf("failed to parse URI: %v", err) } ctx := context.Background() result, err := resolver.Resolve(ctx, uri) if err != nil { t.Fatalf("failed to resolve URI: %v", err) } if len(result.Peers) != 1 { t.Errorf("expected 1 peer in result, got %d", len(result.Peers)) } if result.Peers[0].PeerID != peerID1 { t.Error("wrong peer returned") } } func TestResolveURIWithWildcards(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) peerID1 := test.RandPeerIDFatal(t) peerID2 := test.RandPeerIDFatal(t) resolver.RegisterPeer(peerID1, &PeerCapability{ Agent: "claude", Role: "frontend", Capabilities: []string{"react"}, Status: "ready", }) resolver.RegisterPeer(peerID2, &PeerCapability{ Agent: "claude", Role: "backend", Capabilities: []string{"go"}, Status: "ready", }) // Test wildcard match uri, err := ParseBzzzURI("CHORUS://claude:*@*:*") if err != nil { t.Fatalf("failed to parse URI: %v", err) } ctx := context.Background() result, err := resolver.Resolve(ctx, uri) if err != nil { t.Fatalf("failed to resolve URI: %v", err) } if len(result.Peers) != 2 { t.Errorf("expected 2 peers in result, got %d", len(result.Peers)) } } func TestResolveURIWithOfflinePeers(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) peerID := test.RandPeerIDFatal(t) resolver.RegisterPeer(peerID, &PeerCapability{ Agent: "claude", Role: "frontend", Status: "offline", // This peer should be filtered out }) uri, err := ParseBzzzURI("CHORUS://claude:frontend@*:*") if err != nil { t.Fatalf("failed to parse URI: %v", err) } ctx := context.Background() result, err := resolver.Resolve(ctx, uri) if err != nil { t.Fatalf("failed to resolve URI: %v", err) } if len(result.Peers) != 0 { t.Errorf("expected 0 peers (offline filtered), got %d", len(result.Peers)) } } func TestResolveString(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) peerID := test.RandPeerIDFatal(t) resolver.RegisterPeer(peerID, &PeerCapability{ Agent: "claude", Role: "frontend", Status: "ready", }) ctx := context.Background() result, err := resolver.ResolveString(ctx, "CHORUS://claude:frontend@*:*") if err != nil { t.Fatalf("failed to resolve string: %v", err) } if len(result.Peers) != 1 { t.Errorf("expected 1 peer, got %d", len(result.Peers)) } } func TestResolverCaching(t *testing.T) { resolver := NewResolver(&mockPeerstore{}, WithCacheTTL(1*time.Second)) peerID := test.RandPeerIDFatal(t) resolver.RegisterPeer(peerID, &PeerCapability{ Agent: "claude", Role: "frontend", Status: "ready", }) ctx := context.Background() uri := "CHORUS://claude:frontend@*:*" // First resolution should hit the resolver result1, err := resolver.ResolveString(ctx, uri) if err != nil { t.Fatalf("failed to resolve: %v", err) } // Second resolution should hit the cache result2, err := resolver.ResolveString(ctx, uri) if err != nil { t.Fatalf("failed to resolve: %v", err) } // Results should be identical (from cache) if result1.ResolvedAt != result2.ResolvedAt { // This is expected behavior - cache should return same timestamp } // Wait for cache to expire time.Sleep(2 * time.Second) // Third resolution should miss cache and create new result result3, err := resolver.ResolveString(ctx, uri) if err != nil { t.Fatalf("failed to resolve: %v", err) } if result3.ResolvedAt.Before(result1.ResolvedAt.Add(1 * time.Second)) { t.Error("cache should have expired and created new result") } } func TestResolutionStrategies(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) // Register peers with different priorities peerID1 := test.RandPeerIDFatal(t) peerID2 := test.RandPeerIDFatal(t) resolver.RegisterPeer(peerID1, &PeerCapability{ Agent: "claude", Role: "frontend", Status: "ready", }) resolver.RegisterPeer(peerID2, &PeerCapability{ Agent: "claude", Role: "frontend", Status: "busy", }) ctx := context.Background() uri, _ := ParseBzzzURI("CHORUS://claude:frontend@*:*") // Test different strategies strategies := []ResolutionStrategy{ StrategyBestMatch, StrategyPriority, StrategyLoadBalance, StrategyExact, } for _, strategy := range strategies { result, err := resolver.Resolve(ctx, uri, strategy) if err != nil { t.Errorf("failed to resolve with strategy %s: %v", strategy, err) } if len(result.Peers) == 0 { t.Errorf("no peers found with strategy %s", strategy) } if result.Strategy != string(strategy) { t.Errorf("strategy not recorded correctly: expected %s, got %s", strategy, result.Strategy) } } } func TestPeerMatching(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) capability := &PeerCapability{ Agent: "claude", Role: "frontend", Capabilities: []string{"react", "javascript"}, Status: "ready", Metadata: map[string]string{"project": "chorus"}, } tests := []struct { name string uri *BzzzURI expected bool }{ { name: "exact match", uri: &BzzzURI{Agent: "claude", Role: "frontend", Project: "chorus", Task: "react"}, expected: true, }, { name: "wildcard agent", uri: &BzzzURI{Agent: "*", Role: "frontend", Project: "chorus", Task: "react"}, expected: true, }, { name: "capability match", uri: &BzzzURI{Agent: "claude", Role: "frontend", Project: "*", Task: "javascript"}, expected: true, }, { name: "no match - wrong agent", uri: &BzzzURI{Agent: "gpt", Role: "frontend", Project: "chorus", Task: "react"}, expected: false, }, { name: "no match - wrong role", uri: &BzzzURI{Agent: "claude", Role: "backend", Project: "chorus", Task: "react"}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := resolver.peerMatches(capability, tt.uri) if result != tt.expected { t.Errorf("expected %v, got %v", tt.expected, result) } }) } } func TestGetPeerCapability(t *testing.T) { resolver := NewResolver(&mockPeerstore{}) peerID := test.RandPeerIDFatal(t) capability := &PeerCapability{ Agent: "claude", Role: "frontend", } // Test before registration _, exists := resolver.GetPeerCapability(peerID) if exists { t.Error("peer should not exist before registration") } // Register and test resolver.RegisterPeer(peerID, capability) retrieved, exists := resolver.GetPeerCapability(peerID) if !exists { t.Error("peer should exist after registration") } if retrieved.Agent != capability.Agent { t.Errorf("expected agent %s, got %s", capability.Agent, retrieved.Agent) } } // Mock peerstore implementation for testing type mockPeerstore struct{} func (m *mockPeerstore) PeerInfo(peer.ID) peer.AddrInfo { return peer.AddrInfo{} } func (m *mockPeerstore) Peers() peer.IDSlice { return nil } func (m *mockPeerstore) Addrs(peer.ID) []peerstore.Multiaddr { return nil } func (m *mockPeerstore) AddrStream(context.Context, peer.ID) <-chan peerstore.Multiaddr { return nil } func (m *mockPeerstore) SetAddr(peer.ID, peerstore.Multiaddr, time.Duration) {} func (m *mockPeerstore) SetAddrs(peer.ID, []peerstore.Multiaddr, time.Duration) {} func (m *mockPeerstore) UpdateAddrs(peer.ID, time.Duration, time.Duration) {} func (m *mockPeerstore) ClearAddrs(peer.ID) {} func (m *mockPeerstore) PeersWithAddrs() peer.IDSlice { return nil } func (m *mockPeerstore) PubKey(peer.ID) peerstore.PubKey { return nil } func (m *mockPeerstore) SetPubKey(peer.ID, peerstore.PubKey) error { return nil } func (m *mockPeerstore) PrivKey(peer.ID) peerstore.PrivKey { return nil } func (m *mockPeerstore) SetPrivKey(peer.ID, peerstore.PrivKey) error { return nil } func (m *mockPeerstore) Get(peer.ID, string) (interface{}, error) { return nil, nil } func (m *mockPeerstore) Put(peer.ID, string, interface{}) error { return nil } func (m *mockPeerstore) GetProtocols(peer.ID) ([]peerstore.Protocol, error) { return nil, nil } func (m *mockPeerstore) SetProtocols(peer.ID, ...peerstore.Protocol) error { return nil } func (m *mockPeerstore) SupportsProtocols(peer.ID, ...peerstore.Protocol) ([]peerstore.Protocol, error) { return nil, nil } func (m *mockPeerstore) RemovePeer(peer.ID) {} func (m *mockPeerstore) Close() error { return nil }