package temporal import ( "context" "testing" "time" "chorus.services/bzzz/pkg/ucxl" slurpContext "chorus.services/bzzz/pkg/slurp/context" ) func TestInfluenceAnalyzer_AnalyzeInfluenceNetwork(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph) ctx := context.Background() // Create a network of 5 contexts addresses := make([]ucxl.Address, 5) for i := 0; i < 5; i++ { addresses[i] = createTestAddress(fmt.Sprintf("test/component%d", i)) context := createTestContext(fmt.Sprintf("test/component%d", i), []string{"go"}) _, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator") if err != nil { t.Fatalf("Failed to create context %d: %v", i, err) } } // Create influence relationships // 0 -> 1, 0 -> 2, 1 -> 3, 2 -> 3, 3 -> 4 relationships := [][]int{ {0, 1}, {0, 2}, {1, 3}, {2, 3}, {3, 4}, } for _, rel := range relationships { err := graph.AddInfluenceRelationship(ctx, addresses[rel[0]], addresses[rel[1]]) if err != nil { t.Fatalf("Failed to add relationship %d->%d: %v", rel[0], rel[1], err) } } // Analyze influence network analysis, err := analyzer.AnalyzeInfluenceNetwork(ctx) if err != nil { t.Fatalf("Failed to analyze influence network: %v", err) } if analysis.TotalNodes != 5 { t.Errorf("Expected 5 total nodes, got %d", analysis.TotalNodes) } if analysis.TotalEdges != 5 { t.Errorf("Expected 5 total edges, got %d", analysis.TotalEdges) } // Network density should be calculated correctly // Density = edges / (nodes * (nodes-1)) = 5 / (5 * 4) = 0.25 expectedDensity := 5.0 / (5.0 * 4.0) if abs(analysis.NetworkDensity-expectedDensity) > 0.01 { t.Errorf("Expected network density %.2f, got %.2f", expectedDensity, analysis.NetworkDensity) } if analysis.CentralNodes == nil { t.Error("Expected central nodes to be identified") } if analysis.AnalyzedAt.IsZero() { t.Error("Expected analyzed timestamp to be set") } } func TestInfluenceAnalyzer_GetInfluenceStrength(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph) ctx := context.Background() // Create two contexts addr1 := createTestAddress("test/influencer") addr2 := createTestAddress("test/influenced") context1 := createTestContext("test/influencer", []string{"go", "core"}) context1.RAGConfidence = 0.9 // High confidence context2 := createTestContext("test/influenced", []string{"go", "feature"}) node1, err := graph.CreateInitialContext(ctx, addr1, context1, "test_creator") if err != nil { t.Fatalf("Failed to create influencer context: %v", err) } _, err = graph.CreateInitialContext(ctx, addr2, context2, "test_creator") if err != nil { t.Fatalf("Failed to create influenced context: %v", err) } // Set impact scope for higher influence node1.ImpactScope = ImpactProject // Add influence relationship err = graph.AddInfluenceRelationship(ctx, addr1, addr2) if err != nil { t.Fatalf("Failed to add influence relationship: %v", err) } // Calculate influence strength strength, err := analyzer.GetInfluenceStrength(ctx, addr1, addr2) if err != nil { t.Fatalf("Failed to get influence strength: %v", err) } if strength <= 0 { t.Error("Expected positive influence strength") } if strength > 1 { t.Error("Influence strength should not exceed 1") } // Test non-existent relationship addr3 := createTestAddress("test/unrelated") context3 := createTestContext("test/unrelated", []string{"go"}) _, err = graph.CreateInitialContext(ctx, addr3, context3, "test_creator") if err != nil { t.Fatalf("Failed to create unrelated context: %v", err) } strength2, err := analyzer.GetInfluenceStrength(ctx, addr1, addr3) if err != nil { t.Fatalf("Failed to get influence strength for unrelated: %v", err) } if strength2 != 0 { t.Errorf("Expected 0 influence strength for unrelated contexts, got %f", strength2) } } func TestInfluenceAnalyzer_FindInfluentialDecisions(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph) ctx := context.Background() // Create contexts with varying influence levels addresses := make([]ucxl.Address, 4) contexts := make([]*slurpContext.ContextNode, 4) for i := 0; i < 4; i++ { addresses[i] = createTestAddress(fmt.Sprintf("test/component%d", i)) contexts[i] = createTestContext(fmt.Sprintf("test/component%d", i), []string{"go"}) // Vary confidence levels contexts[i].RAGConfidence = 0.6 + float64(i)*0.1 _, err := graph.CreateInitialContext(ctx, addresses[i], contexts[i], "test_creator") if err != nil { t.Fatalf("Failed to create context %d: %v", i, err) } } // Create influence network with component 1 as most influential // 1 -> 0, 1 -> 2, 1 -> 3 (component 1 influences all others) for i := 0; i < 4; i++ { if i != 1 { err := graph.AddInfluenceRelationship(ctx, addresses[1], addresses[i]) if err != nil { t.Fatalf("Failed to add influence from 1 to %d: %v", i, err) } } } // Also add 0 -> 2 (component 0 influences component 2) err := graph.AddInfluenceRelationship(ctx, addresses[0], addresses[2]) if err != nil { t.Fatalf("Failed to add influence from 0 to 2: %v", err) } // Find influential decisions influential, err := analyzer.FindInfluentialDecisions(ctx, 3) if err != nil { t.Fatalf("Failed to find influential decisions: %v", err) } if len(influential) == 0 { t.Fatal("Expected to find influential decisions") } // Results should be sorted by influence score (highest first) for i := 1; i < len(influential); i++ { if influential[i-1].InfluenceScore < influential[i].InfluenceScore { t.Error("Results should be sorted by influence score in descending order") } } // Component 1 should be most influential (influences 3 others) mostInfluential := influential[0] if mostInfluential.Address.String() != addresses[1].String() { t.Errorf("Expected component 1 to be most influential, got %s", mostInfluential.Address.String()) } // Check that influence reasons are provided if len(mostInfluential.InfluenceReasons) == 0 { t.Error("Expected influence reasons to be provided") } // Check that impact analysis is provided if mostInfluential.ImpactAnalysis == nil { t.Error("Expected impact analysis to be provided") } } func TestInfluenceAnalyzer_AnalyzeDecisionImpact(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph) ctx := context.Background() // Create a context and evolve it address := createTestAddress("test/core-service") initialContext := createTestContext("test/core-service", []string{"go", "core"}) _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Create dependent contexts dependentAddrs := make([]ucxl.Address, 3) for i := 0; i < 3; i++ { dependentAddrs[i] = createTestAddress(fmt.Sprintf("test/dependent%d", i)) dependentContext := createTestContext(fmt.Sprintf("test/dependent%d", i), []string{"go"}) _, err := graph.CreateInitialContext(ctx, dependentAddrs[i], dependentContext, "test_creator") if err != nil { t.Fatalf("Failed to create dependent context %d: %v", i, err) } // Add influence relationship err = graph.AddInfluenceRelationship(ctx, address, dependentAddrs[i]) if err != nil { t.Fatalf("Failed to add influence to dependent %d: %v", i, err) } } // Evolve the core service with an architectural change updatedContext := createTestContext("test/core-service", []string{"go", "core", "microservice"}) decision := createTestDecision("arch-001", "architect", "Split into microservices", ImpactSystem) evolvedNode, err := graph.EvolveContext(ctx, address, updatedContext, ReasonArchitectureChange, decision) if err != nil { t.Fatalf("Failed to evolve core service: %v", err) } // Analyze decision impact impact, err := analyzer.AnalyzeDecisionImpact(ctx, address, evolvedNode.Version) if err != nil { t.Fatalf("Failed to analyze decision impact: %v", err) } if impact.Address.String() != address.String() { t.Errorf("Expected impact address %s, got %s", address.String(), impact.Address.String()) } if impact.DecisionHop != evolvedNode.Version { t.Errorf("Expected decision hop %d, got %d", evolvedNode.Version, impact.DecisionHop) } // Should have direct impact on dependent services if len(impact.DirectImpact) != 3 { t.Errorf("Expected 3 direct impacts, got %d", len(impact.DirectImpact)) } // Impact strength should be positive if impact.ImpactStrength <= 0 { t.Error("Expected positive impact strength") } // Should have impact categories if len(impact.ImpactCategories) == 0 { t.Error("Expected impact categories to be identified") } // Should have mitigation actions if len(impact.MitigationActions) == 0 { t.Error("Expected mitigation actions to be suggested") } } func TestInfluenceAnalyzer_PredictInfluence(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph) ctx := context.Background() // Create contexts with similar technologies addr1 := createTestAddress("test/service1") addr2 := createTestAddress("test/service2") addr3 := createTestAddress("test/service3") // Services 1 and 2 share technologies (higher prediction probability) context1 := createTestContext("test/service1", []string{"go", "grpc", "postgres"}) context2 := createTestContext("test/service2", []string{"go", "grpc", "redis"}) context3 := createTestContext("test/service3", []string{"python", "flask"}) // Different tech stack contexts := []*slurpContext.ContextNode{context1, context2, context3} addresses := []ucxl.Address{addr1, addr2, addr3} for i, context := range contexts { _, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator") if err != nil { t.Fatalf("Failed to create context %d: %v", i, err) } } // Predict influence from service1 predictions, err := analyzer.PredictInfluence(ctx, addr1) if err != nil { t.Fatalf("Failed to predict influence: %v", err) } // Should predict influence to service2 (similar tech stack) foundService2 := false foundService3 := false for _, prediction := range predictions { if prediction.To.String() == addr2.String() { foundService2 = true // Should have higher probability due to technology similarity if prediction.Probability <= 0.3 { t.Errorf("Expected higher prediction probability for similar service, got %f", prediction.Probability) } } if prediction.To.String() == addr3.String() { foundService3 = true } } if !foundService2 && len(predictions) > 0 { t.Error("Expected to predict influence to service with similar technology stack") } // Predictions should include reasons for _, prediction := range predictions { if len(prediction.Reasons) == 0 { t.Error("Expected prediction reasons to be provided") } if prediction.Confidence <= 0 || prediction.Confidence > 1 { t.Errorf("Expected confidence between 0 and 1, got %f", prediction.Confidence) } if prediction.EstimatedDelay <= 0 { t.Error("Expected positive estimated delay") } } } func TestInfluenceAnalyzer_GetCentralityMetrics(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph) ctx := context.Background() // Create a small network for centrality testing addresses := make([]ucxl.Address, 4) for i := 0; i < 4; i++ { addresses[i] = createTestAddress(fmt.Sprintf("test/node%d", i)) context := createTestContext(fmt.Sprintf("test/node%d", i), []string{"go"}) _, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator") if err != nil { t.Fatalf("Failed to create context %d: %v", i, err) } } // Create star topology with node 0 at center // 0 -> 1, 0 -> 2, 0 -> 3 for i := 1; i < 4; i++ { err := graph.AddInfluenceRelationship(ctx, addresses[0], addresses[i]) if err != nil { t.Fatalf("Failed to add influence 0->%d: %v", i, err) } } // Calculate centrality metrics metrics, err := analyzer.GetCentralityMetrics(ctx) if err != nil { t.Fatalf("Failed to get centrality metrics: %v", err) } if len(metrics.DegreeCentrality) != 4 { t.Errorf("Expected degree centrality for 4 nodes, got %d", len(metrics.DegreeCentrality)) } if len(metrics.BetweennessCentrality) != 4 { t.Errorf("Expected betweenness centrality for 4 nodes, got %d", len(metrics.BetweennessCentrality)) } if len(metrics.ClosenessCentrality) != 4 { t.Errorf("Expected closeness centrality for 4 nodes, got %d", len(metrics.ClosenessCentrality)) } if len(metrics.PageRank) != 4 { t.Errorf("Expected PageRank for 4 nodes, got %d", len(metrics.PageRank)) } // Node 0 should have highest degree centrality (connected to all others) node0ID := "" graph.mu.RLock() for _, nodes := range graph.addressToNodes { for _, node := range nodes { if node.UCXLAddress.String() == addresses[0].String() { node0ID = node.ID break } } } graph.mu.RUnlock() if node0ID != "" { node0Centrality := metrics.DegreeCentrality[node0ID] // Check that other nodes have lower centrality for nodeID, centrality := range metrics.DegreeCentrality { if nodeID != node0ID && centrality >= node0Centrality { t.Error("Expected central node to have highest degree centrality") } } } if metrics.CalculatedAt.IsZero() { t.Error("Expected calculated timestamp to be set") } } func TestInfluenceAnalyzer_CachingAndPerformance(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph).(*influenceAnalyzerImpl) ctx := context.Background() // Create small network addresses := make([]ucxl.Address, 3) for i := 0; i < 3; i++ { addresses[i] = createTestAddress(fmt.Sprintf("test/component%d", i)) context := createTestContext(fmt.Sprintf("test/component%d", i), []string{"go"}) _, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator") if err != nil { t.Fatalf("Failed to create context %d: %v", i, err) } } err := graph.AddInfluenceRelationship(ctx, addresses[0], addresses[1]) if err != nil { t.Fatalf("Failed to add influence relationship: %v", err) } // First call should populate cache start1 := time.Now() analysis1, err := analyzer.AnalyzeInfluenceNetwork(ctx) if err != nil { t.Fatalf("Failed to analyze influence network (first call): %v", err) } duration1 := time.Since(start1) // Second call should use cache and be faster start2 := time.Now() analysis2, err := analyzer.AnalyzeInfluenceNetwork(ctx) if err != nil { t.Fatalf("Failed to analyze influence network (second call): %v", err) } duration2 := time.Since(start2) // Results should be identical if analysis1.TotalNodes != analysis2.TotalNodes { t.Error("Cached results should be identical to original") } if analysis1.TotalEdges != analysis2.TotalEdges { t.Error("Cached results should be identical to original") } // Second call should be faster (cached) // Note: In practice, this test might be flaky due to small network size // and timing variations, but it demonstrates the caching concept if duration2 > duration1 { t.Logf("Warning: Second call took longer (%.2fms vs %.2fms), cache may not be working optimally", duration2.Seconds()*1000, duration1.Seconds()*1000) } } func BenchmarkInfluenceAnalyzer_AnalyzeInfluenceNetwork(b *testing.B) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph) ctx := context.Background() // Setup: Create network of 50 contexts addresses := make([]ucxl.Address, 50) for i := 0; i < 50; i++ { addresses[i] = createTestAddress(fmt.Sprintf("test/component%d", i)) context := createTestContext(fmt.Sprintf("test/component%d", i), []string{"go"}) _, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator") if err != nil { b.Fatalf("Failed to create context %d: %v", i, err) } // Add some influence relationships if i > 0 { err = graph.AddInfluenceRelationship(ctx, addresses[i-1], addresses[i]) if err != nil { b.Fatalf("Failed to add influence relationship: %v", err) } } // Add some random cross-connections if i > 10 && i%5 == 0 { err = graph.AddInfluenceRelationship(ctx, addresses[i-10], addresses[i]) if err != nil { b.Fatalf("Failed to add cross-connection: %v", err) } } } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := analyzer.AnalyzeInfluenceNetwork(ctx) if err != nil { b.Fatalf("Failed to analyze influence network: %v", err) } } } func BenchmarkInfluenceAnalyzer_GetCentralityMetrics(b *testing.B) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) analyzer := NewInfluenceAnalyzer(graph) ctx := context.Background() // Setup: Create dense network addresses := make([]ucxl.Address, 20) for i := 0; i < 20; i++ { addresses[i] = createTestAddress(fmt.Sprintf("test/node%d", i)) context := createTestContext(fmt.Sprintf("test/node%d", i), []string{"go"}) _, err := graph.CreateInitialContext(ctx, addresses[i], context, "test_creator") if err != nil { b.Fatalf("Failed to create context %d: %v", i, err) } } // Create dense connections for i := 0; i < 20; i++ { for j := i + 1; j < 20; j++ { if j-i <= 3 { // Connect to next 3 nodes err := graph.AddInfluenceRelationship(ctx, addresses[i], addresses[j]) if err != nil { b.Fatalf("Failed to add influence %d->%d: %v", i, j, err) } } } } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := analyzer.GetCentralityMetrics(ctx) if err != nil { b.Fatalf("Failed to get centrality metrics: %v", err) } } } // Helper function for float comparison func abs(x float64) float64 { if x < 0 { return -x } return x }