package temporal import ( "context" "testing" "time" "chorus.services/bzzz/pkg/ucxl" slurpContext "chorus.services/bzzz/pkg/slurp/context" ) func TestDecisionNavigator_NavigateDecisionHops(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) navigator := NewDecisionNavigator(graph) ctx := context.Background() // Create a chain of versions address := createTestAddress("test/component") initialContext := createTestContext("test/component", []string{"go"}) _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Create 3 more versions for i := 2; i <= 4; i++ { updatedContext := createTestContext("test/component", []string{"go", fmt.Sprintf("tech%d", i)}) decision := createTestDecision(fmt.Sprintf("dec-%03d", i), "test_maker", "Update", ImpactLocal) _, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision) if err != nil { t.Fatalf("Failed to evolve context to version %d: %v", i, err) } } // Test forward navigation from version 1 v1, err := graph.GetVersionAtDecision(ctx, address, 1) if err != nil { t.Fatalf("Failed to get version 1: %v", err) } // Navigate 2 hops forward from version 1 result, err := navigator.NavigateDecisionHops(ctx, address, 2, NavigationForward) if err != nil { t.Fatalf("Failed to navigate forward: %v", err) } if result.Version != 3 { t.Errorf("Expected to navigate to version 3, got version %d", result.Version) } // Test backward navigation from version 4 result2, err := navigator.NavigateDecisionHops(ctx, address, 2, NavigationBackward) if err != nil { t.Fatalf("Failed to navigate backward: %v", err) } if result2.Version != 2 { t.Errorf("Expected to navigate to version 2, got version %d", result2.Version) } } func TestDecisionNavigator_GetDecisionTimeline(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) navigator := NewDecisionNavigator(graph) ctx := context.Background() // Create main context with evolution address := createTestAddress("test/main") initialContext := createTestContext("test/main", []string{"go"}) _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Evolve main context for i := 2; i <= 3; i++ { updatedContext := createTestContext("test/main", []string{"go", fmt.Sprintf("feature%d", i)}) decision := createTestDecision(fmt.Sprintf("main-dec-%03d", i), fmt.Sprintf("dev%d", i), "Add feature", ImpactModule) _, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision) if err != nil { t.Fatalf("Failed to evolve main context to version %d: %v", i, err) } } // Create related context relatedAddr := createTestAddress("test/related") relatedContext := createTestContext("test/related", []string{"go"}) _, err = graph.CreateInitialContext(ctx, relatedAddr, relatedContext, "test_creator") if err != nil { t.Fatalf("Failed to create related context: %v", err) } // Add influence relationship err = graph.AddInfluenceRelationship(ctx, address, relatedAddr) if err != nil { t.Fatalf("Failed to add influence relationship: %v", err) } // Get decision timeline with related decisions timeline, err := navigator.GetDecisionTimeline(ctx, address, true, 5) if err != nil { t.Fatalf("Failed to get decision timeline: %v", err) } if len(timeline.DecisionSequence) != 3 { t.Errorf("Expected 3 decisions in timeline, got %d", len(timeline.DecisionSequence)) } // Check ordering for i, entry := range timeline.DecisionSequence { expectedVersion := i + 1 if entry.Version != expectedVersion { t.Errorf("Expected version %d at index %d, got %d", expectedVersion, i, entry.Version) } } // Should have related decisions if len(timeline.RelatedDecisions) == 0 { t.Error("Expected to find related decisions") } if timeline.AnalysisMetadata == nil { t.Error("Expected analysis metadata") } } func TestDecisionNavigator_FindStaleContexts(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) navigator := NewDecisionNavigator(graph) ctx := context.Background() // Create contexts with different staleness levels 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) } } // Manually set staleness scores for testing graph.mu.Lock() for _, nodes := range graph.addressToNodes { for j, node := range nodes { // Set different staleness levels node.Staleness = float64(j+1) * 0.3 } } graph.mu.Unlock() // Find stale contexts with threshold 0.5 staleContexts, err := navigator.FindStaleContexts(ctx, 0.5) if err != nil { t.Fatalf("Failed to find stale contexts: %v", err) } // Should find contexts with staleness >= 0.5 expectedStale := 0 graph.mu.RLock() for _, nodes := range graph.addressToNodes { for _, node := range nodes { if node.Staleness >= 0.5 { expectedStale++ } } } graph.mu.RUnlock() if len(staleContexts) != expectedStale { t.Errorf("Expected %d stale contexts, got %d", expectedStale, len(staleContexts)) } // Results should be sorted by staleness score (highest first) for i := 1; i < len(staleContexts); i++ { if staleContexts[i-1].StalenessScore < staleContexts[i].StalenessScore { t.Error("Results should be sorted by staleness score in descending order") } } } func TestDecisionNavigator_BookmarkManagement(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) navigator := NewDecisionNavigator(graph) ctx := context.Background() // Create context with multiple versions address := createTestAddress("test/component") initialContext := createTestContext("test/component", []string{"go"}) _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Create more versions for i := 2; i <= 5; i++ { updatedContext := createTestContext("test/component", []string{"go", fmt.Sprintf("feature%d", i)}) decision := createTestDecision(fmt.Sprintf("dec-%03d", i), "test_maker", "Update", ImpactLocal) _, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision) if err != nil { t.Fatalf("Failed to evolve context to version %d: %v", i, err) } } // Create bookmarks bookmarkNames := []string{"Initial Release", "Major Feature", "Bug Fix", "Performance Improvement"} for i, name := range bookmarkNames { err := navigator.BookmarkDecision(ctx, address, i+1, name) if err != nil { t.Fatalf("Failed to create bookmark %s: %v", name, err) } } // List bookmarks bookmarks, err := navigator.ListBookmarks(ctx) if err != nil { t.Fatalf("Failed to list bookmarks: %v", err) } if len(bookmarks) != len(bookmarkNames) { t.Errorf("Expected %d bookmarks, got %d", len(bookmarkNames), len(bookmarks)) } // Verify bookmark details for _, bookmark := range bookmarks { if bookmark.Address.String() != address.String() { t.Errorf("Expected bookmark address %s, got %s", address.String(), bookmark.Address.String()) } if bookmark.DecisionHop < 1 || bookmark.DecisionHop > 4 { t.Errorf("Expected decision hop between 1-4, got %d", bookmark.DecisionHop) } if bookmark.Metadata == nil { t.Error("Expected bookmark metadata") } } // Bookmarks should be sorted by creation time (newest first) for i := 1; i < len(bookmarks); i++ { if bookmarks[i-1].CreatedAt.Before(bookmarks[i].CreatedAt) { t.Error("Bookmarks should be sorted by creation time (newest first)") } } } func TestDecisionNavigator_ValidationAndErrorHandling(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) navigator := NewDecisionNavigator(graph) ctx := context.Background() // Test: Navigate decision hops on non-existent address nonExistentAddr := createTestAddress("non/existent") _, err := navigator.NavigateDecisionHops(ctx, nonExistentAddr, 1, NavigationForward) if err == nil { t.Error("Expected error when navigating on non-existent address") } // Test: Create bookmark for non-existent decision err = navigator.BookmarkDecision(ctx, nonExistentAddr, 1, "Test Bookmark") if err == nil { t.Error("Expected error when bookmarking non-existent decision") } // Create valid context for path validation tests address := createTestAddress("test/component") initialContext := createTestContext("test/component", []string{"go"}) _, err = graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Test: Validate empty decision path err = navigator.ValidateDecisionPath(ctx, []*DecisionStep{}) if err == nil { t.Error("Expected error when validating empty decision path") } // Test: Validate path with nil temporal node invalidPath := []*DecisionStep{ { Address: address, TemporalNode: nil, HopDistance: 0, Relationship: "test", }, } err = navigator.ValidateDecisionPath(ctx, invalidPath) if err == nil { t.Error("Expected error when validating path with nil temporal node") } // Test: Get navigation history for non-existent session _, err = navigator.GetNavigationHistory(ctx, "non-existent-session") if err == nil { t.Error("Expected error when getting history for non-existent session") } } func BenchmarkDecisionNavigator_GetDecisionTimeline(b *testing.B) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) navigator := NewDecisionNavigator(graph) ctx := context.Background() // Setup: Create context with many versions address := createTestAddress("test/component") initialContext := createTestContext("test/component", []string{"go"}) _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { b.Fatalf("Failed to create initial context: %v", err) } // Create 100 versions for i := 2; i <= 100; i++ { updatedContext := createTestContext("test/component", []string{"go", fmt.Sprintf("feature%d", i)}) decision := createTestDecision(fmt.Sprintf("dec-%03d", i), "test_maker", "Update", ImpactLocal) _, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision) if err != nil { b.Fatalf("Failed to evolve context to version %d: %v", i, err) } } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := navigator.GetDecisionTimeline(ctx, address, true, 10) if err != nil { b.Fatalf("Failed to get decision timeline: %v", err) } } } func BenchmarkDecisionNavigator_FindStaleContexts(b *testing.B) { storage := newMockStorage() graph := NewTemporalGraph(storage).(*temporalGraphImpl) navigator := NewDecisionNavigator(graph) ctx := context.Background() // Setup: Create many contexts for i := 0; i < 1000; i++ { address := createTestAddress(fmt.Sprintf("test/component%d", i)) context := createTestContext(fmt.Sprintf("test/component%d", i), []string{"go"}) _, err := graph.CreateInitialContext(ctx, address, context, "test_creator") if err != nil { b.Fatalf("Failed to create context %d: %v", i, err) } } // Set random staleness scores graph.mu.Lock() for _, nodes := range graph.addressToNodes { for _, node := range nodes { node.Staleness = 0.3 + (float64(node.Version)*0.1) // Varying staleness } } graph.mu.Unlock() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := navigator.FindStaleContexts(ctx, 0.5) if err != nil { b.Fatalf("Failed to find stale contexts: %v", err) } } }