Files
CHORUS/pkg/slurp/temporal/influence_analyzer_test.go
anthonyrawlins 9bdcbe0447 Integrate BACKBEAT SDK and resolve KACHING license validation
Major integrations and fixes:
- Added BACKBEAT SDK integration for P2P operation timing
- Implemented beat-aware status tracking for distributed operations
- Added Docker secrets support for secure license management
- Resolved KACHING license validation via HTTPS/TLS
- Updated docker-compose configuration for clean stack deployment
- Disabled rollback policies to prevent deployment failures
- Added license credential storage (CHORUS-DEV-MULTI-001)

Technical improvements:
- BACKBEAT P2P operation tracking with phase management
- Enhanced configuration system with file-based secrets
- Improved error handling for license validation
- Clean separation of KACHING and CHORUS deployment stacks

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-06 07:56:26 +10:00

585 lines
18 KiB
Go

package temporal
import (
"context"
"testing"
"time"
"chorus/pkg/ucxl"
slurpContext "chorus/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
}