package ucxi import ( "context" "fmt" "testing" "time" "chorus/pkg/ucxl" ) func TestNewBasicAddressResolver(t *testing.T) { nodeID := "test-node-123" resolver := NewBasicAddressResolver(nodeID) if resolver == nil { t.Error("NewBasicAddressResolver should not return nil") } if resolver.nodeID != nodeID { t.Errorf("Node ID = %s, want %s", resolver.nodeID, nodeID) } if resolver.registry == nil { t.Error("Registry should be initialized") } if resolver.defaultTTL == 0 { t.Error("Default TTL should be set") } } func TestResolverAnnounceAndResolve(t *testing.T) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() addr, err := ucxl.Parse("ucxl://agent1:developer@project1:task1/*^") if err != nil { t.Fatalf("Failed to parse address: %v", err) } content := &Content{ Data: []byte("test content"), ContentType: "text/plain", Metadata: map[string]string{"version": "1.0"}, CreatedAt: time.Now(), } // Test announce err = resolver.Announce(ctx, addr, content) if err != nil { t.Errorf("Announce failed: %v", err) } // Test resolve resolved, err := resolver.Resolve(ctx, addr) if err != nil { t.Errorf("Resolve failed: %v", err) } if resolved == nil { t.Error("Resolved content should not be nil") } if string(resolved.Content.Data) != "test content" { t.Errorf("Content data = %s, want 'test content'", string(resolved.Content.Data)) } if resolved.Source != "test-node" { t.Errorf("Source = %s, want 'test-node'", resolved.Source) } if resolved.Address.String() != addr.String() { t.Errorf("Address mismatch: got %s, want %s", resolved.Address.String(), addr.String()) } } func TestResolverTTLExpiration(t *testing.T) { resolver := NewBasicAddressResolver("test-node") resolver.SetDefaultTTL(50 * time.Millisecond) // Very short TTL for testing ctx := context.Background() addr, _ := ucxl.Parse("ucxl://agent1:developer@project1:task1/*^") content := &Content{Data: []byte("test")} // Announce content resolver.Announce(ctx, addr, content) // Should resolve immediately resolved, err := resolver.Resolve(ctx, addr) if err != nil { t.Errorf("Immediate resolve failed: %v", err) } if resolved == nil { t.Error("Content should be found immediately after announce") } // Wait for TTL expiration time.Sleep(100 * time.Millisecond) // Should fail to resolve after TTL expiration resolved, err = resolver.Resolve(ctx, addr) if err == nil { t.Error("Resolve should fail after TTL expiration") } if resolved != nil { t.Error("Resolved content should be nil after TTL expiration") } } func TestResolverWildcardMatching(t *testing.T) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() // Announce content with wildcard address wildcardAddr, _ := ucxl.Parse("ucxl://any:any@project1:task1/*^") content := &Content{Data: []byte("wildcard content")} resolver.Announce(ctx, wildcardAddr, content) // Try to resolve with specific address specificAddr, _ := ucxl.Parse("ucxl://agent1:developer@project1:task1/*^") resolved, err := resolver.Resolve(ctx, specificAddr) if err != nil { t.Errorf("Wildcard resolve failed: %v", err) } if resolved == nil { t.Error("Should resolve specific address against wildcard pattern") } if string(resolved.Content.Data) != "wildcard content" { t.Error("Should return wildcard content") } } func TestResolverDiscover(t *testing.T) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() // Announce several pieces of content addresses := []string{ "ucxl://agent1:developer@project1:task1/*^", "ucxl://agent2:developer@project1:task2/*^", "ucxl://agent1:tester@project2:task1/*^", "ucxl://agent3:admin@project1:task3/*^", } for i, addrStr := range addresses { addr, _ := ucxl.Parse(addrStr) content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))} resolver.Announce(ctx, addr, content) } tests := []struct { name string pattern string expectedCount int minCount int }{ { name: "find all project1 tasks", pattern: "ucxl://any:any@project1:any/*^", minCount: 3, // Should match 3 project1 addresses }, { name: "find all developer roles", pattern: "ucxl://any:developer@any:any/*^", minCount: 2, // Should match 2 developer addresses }, { name: "find specific address", pattern: "ucxl://agent1:developer@project1:task1/*^", minCount: 1, // Should match exactly 1 }, { name: "find non-existent pattern", pattern: "ucxl://nonexistent:role@project:task/*^", minCount: 0, // Should match none }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pattern, _ := ucxl.Parse(tt.pattern) results, err := resolver.Discover(ctx, pattern) if err != nil { t.Errorf("Discover failed: %v", err) } if len(results) < tt.minCount { t.Errorf("Results count = %d, want at least %d", len(results), tt.minCount) } // Verify all results match the pattern for _, result := range results { if !result.Address.Matches(pattern) { t.Errorf("Result address %s does not match pattern %s", result.Address.String(), pattern.String()) } } }) } } func TestResolverHooks(t *testing.T) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() var announceHookCalled bool var discoverHookCalled bool // Set announce hook resolver.SetAnnounceHook(func(ctx context.Context, addr *ucxl.Address, content *Content) error { announceHookCalled = true return nil }) // Set discover hook resolver.SetDiscoverHook(func(ctx context.Context, pattern *ucxl.Address) ([]*ResolvedContent, error) { discoverHookCalled = true return []*ResolvedContent{}, nil }) addr, _ := ucxl.Parse("ucxl://agent1:developer@project1:task1/*^") content := &Content{Data: []byte("test")} // Test announce hook resolver.Announce(ctx, addr, content) if !announceHookCalled { t.Error("Announce hook should be called") } // Test discover hook (when address not found locally) nonExistentAddr, _ := ucxl.Parse("ucxl://nonexistent:agent@project:task/*^") resolver.Discover(ctx, nonExistentAddr) if !discoverHookCalled { t.Error("Discover hook should be called") } } func TestResolverCleanupExpired(t *testing.T) { resolver := NewBasicAddressResolver("test-node") resolver.SetDefaultTTL(50 * time.Millisecond) // Short TTL for testing ctx := context.Background() // Add several entries for i := 0; i < 5; i++ { addr, _ := ucxl.Parse(fmt.Sprintf("ucxl://agent%d:developer@project:task/*^", i)) content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))} resolver.Announce(ctx, addr, content) } // Wait for TTL expiration time.Sleep(100 * time.Millisecond) // Cleanup expired entries removed := resolver.CleanupExpired() if removed != 5 { t.Errorf("Cleanup removed %d entries, want 5", removed) } // Verify all entries are gone stats := resolver.GetRegistryStats() activeEntries := stats["active_entries"].(int) if activeEntries != 0 { t.Errorf("Active entries = %d, want 0 after cleanup", activeEntries) } } func TestResolverGetRegistryStats(t *testing.T) { resolver := NewBasicAddressResolver("test-node-123") ctx := context.Background() // Initially should have no entries stats := resolver.GetRegistryStats() if stats["total_entries"].(int) != 0 { t.Error("Should start with 0 entries") } if stats["node_id"].(string) != "test-node-123" { t.Error("Node ID should match") } // Add some entries for i := 0; i < 3; i++ { addr, _ := ucxl.Parse(fmt.Sprintf("ucxl://agent%d:developer@project:task/*^", i)) content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))} resolver.Announce(ctx, addr, content) } stats = resolver.GetRegistryStats() if stats["total_entries"].(int) != 3 { t.Errorf("Total entries = %d, want 3", stats["total_entries"]) } if stats["active_entries"].(int) != 3 { t.Errorf("Active entries = %d, want 3", stats["active_entries"]) } if stats["expired_entries"].(int) != 0 { t.Errorf("Expired entries = %d, want 0", stats["expired_entries"]) } } func TestResolverErrorCases(t *testing.T) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() // Test nil address in Resolve _, err := resolver.Resolve(ctx, nil) if err == nil { t.Error("Resolve with nil address should return error") } // Test nil address in Announce content := &Content{Data: []byte("test")} err = resolver.Announce(ctx, nil, content) if err == nil { t.Error("Announce with nil address should return error") } // Test nil content in Announce addr, _ := ucxl.Parse("ucxl://agent:role@project:task/*^") err = resolver.Announce(ctx, addr, nil) if err == nil { t.Error("Announce with nil content should return error") } // Test nil pattern in Discover _, err = resolver.Discover(ctx, nil) if err == nil { t.Error("Discover with nil pattern should return error") } // Test resolve non-existent address nonExistentAddr, _ := ucxl.Parse("ucxl://nonexistent:agent@project:task/*^") _, err = resolver.Resolve(ctx, nonExistentAddr) if err == nil { t.Error("Resolve non-existent address should return error") } } func TestResolverSetDefaultTTL(t *testing.T) { resolver := NewBasicAddressResolver("test-node") newTTL := 10 * time.Minute resolver.SetDefaultTTL(newTTL) if resolver.defaultTTL != newTTL { t.Errorf("Default TTL = %v, want %v", resolver.defaultTTL, newTTL) } // Test that new content uses the new TTL ctx := context.Background() addr, _ := ucxl.Parse("ucxl://agent:role@project:task/*^") content := &Content{Data: []byte("test")} resolver.Announce(ctx, addr, content) resolved, _ := resolver.Resolve(ctx, addr) if resolved.TTL != newTTL { t.Errorf("Resolved content TTL = %v, want %v", resolved.TTL, newTTL) } } // Test concurrent access to resolver func TestResolverConcurrency(t *testing.T) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() // Run multiple goroutines that announce and resolve content done := make(chan bool, 10) for i := 0; i < 10; i++ { go func(id int) { defer func() { done <- true }() addr, _ := ucxl.Parse(fmt.Sprintf("ucxl://agent%d:developer@project:task/*^", id)) content := &Content{Data: []byte(fmt.Sprintf("content-%d", id))} // Announce if err := resolver.Announce(ctx, addr, content); err != nil { t.Errorf("Goroutine %d announce failed: %v", id, err) return } // Resolve if _, err := resolver.Resolve(ctx, addr); err != nil { t.Errorf("Goroutine %d resolve failed: %v", id, err) return } // Discover pattern, _ := ucxl.Parse("ucxl://any:any@project:task/*^") if _, err := resolver.Discover(ctx, pattern); err != nil { t.Errorf("Goroutine %d discover failed: %v", id, err) return } }(i) } // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } // Verify final state stats := resolver.GetRegistryStats() if stats["total_entries"].(int) != 10 { t.Errorf("Expected 10 total entries, got %d", stats["total_entries"]) } } // Benchmark tests func BenchmarkResolverAnnounce(b *testing.B) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() addr, _ := ucxl.Parse("ucxl://agent:developer@project:task/*^") content := &Content{Data: []byte("test content")} b.ResetTimer() for i := 0; i < b.N; i++ { resolver.Announce(ctx, addr, content) } } func BenchmarkResolverResolve(b *testing.B) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() addr, _ := ucxl.Parse("ucxl://agent:developer@project:task/*^") content := &Content{Data: []byte("test content")} resolver.Announce(ctx, addr, content) b.ResetTimer() for i := 0; i < b.N; i++ { resolver.Resolve(ctx, addr) } } func BenchmarkResolverDiscover(b *testing.B) { resolver := NewBasicAddressResolver("test-node") ctx := context.Background() // Setup test data for i := 0; i < 100; i++ { addr, _ := ucxl.Parse(fmt.Sprintf("ucxl://agent%d:developer@project:task/*^", i)) content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))} resolver.Announce(ctx, addr, content) } pattern, _ := ucxl.Parse("ucxl://any:developer@project:task/*^") b.ResetTimer() for i := 0; i < b.N; i++ { resolver.Discover(ctx, pattern) } }