Complete BZZZ functionality port to CHORUS
🎭 CHORUS now contains full BZZZ functionality adapted for containers Core systems ported: - P2P networking (libp2p with DHT and PubSub) - Task coordination (COOEE protocol) - HMMM collaborative reasoning - SHHH encryption and security - SLURP admin election system - UCXL content addressing - UCXI server integration - Hypercore logging system - Health monitoring and graceful shutdown - License validation with KACHING Container adaptations: - Environment variable configuration (no YAML files) - Container-optimized logging to stdout/stderr - Auto-generated agent IDs for container deployments - Docker-first architecture All proven BZZZ P2P protocols, AI integration, and collaboration features are now available in containerized form. Next: Build and test container deployment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
459
pkg/ucxi/resolver_test.go
Normal file
459
pkg/ucxi/resolver_test.go
Normal file
@@ -0,0 +1,459 @@
|
||||
package ucxi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"chorus.services/bzzz/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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user