package temporal import ( "context" "testing" "time" "chorus.services/bzzz/pkg/ucxl" slurpContext "chorus.services/bzzz/pkg/slurp/context" "chorus.services/bzzz/pkg/slurp/storage" ) // Mock storage for testing type mockStorage struct { data map[string]interface{} } func newMockStorage() *mockStorage { return &mockStorage{ data: make(map[string]interface{}), } } func (ms *mockStorage) StoreContext(ctx context.Context, node *slurpContext.ContextNode, roles []string) error { ms.data[node.UCXLAddress.String()] = node return nil } func (ms *mockStorage) RetrieveContext(ctx context.Context, address ucxl.Address, role string) (*slurpContext.ContextNode, error) { if data, exists := ms.data[address.String()]; exists { return data.(*slurpContext.ContextNode), nil } return nil, storage.ErrNotFound } func (ms *mockStorage) UpdateContext(ctx context.Context, node *slurpContext.ContextNode, roles []string) error { ms.data[node.UCXLAddress.String()] = node return nil } func (ms *mockStorage) DeleteContext(ctx context.Context, address ucxl.Address) error { delete(ms.data, address.String()) return nil } func (ms *mockStorage) ExistsContext(ctx context.Context, address ucxl.Address) (bool, error) { _, exists := ms.data[address.String()] return exists, nil } func (ms *mockStorage) ListContexts(ctx context.Context, criteria *storage.ListCriteria) ([]*slurpContext.ContextNode, error) { results := make([]*slurpContext.ContextNode, 0) for _, data := range ms.data { if node, ok := data.(*slurpContext.ContextNode); ok { results = append(results, node) } } return results, nil } func (ms *mockStorage) SearchContexts(ctx context.Context, query *storage.SearchQuery) (*storage.SearchResults, error) { return &storage.SearchResults{}, nil } func (ms *mockStorage) BatchStore(ctx context.Context, batch *storage.BatchStoreRequest) (*storage.BatchStoreResult, error) { return &storage.BatchStoreResult{}, nil } func (ms *mockStorage) BatchRetrieve(ctx context.Context, batch *storage.BatchRetrieveRequest) (*storage.BatchRetrieveResult, error) { return &storage.BatchRetrieveResult{}, nil } func (ms *mockStorage) GetStorageStats(ctx context.Context) (*storage.StorageStatistics, error) { return &storage.StorageStatistics{}, nil } func (ms *mockStorage) Sync(ctx context.Context) error { return nil } func (ms *mockStorage) Backup(ctx context.Context, destination string) error { return nil } func (ms *mockStorage) Restore(ctx context.Context, source string) error { return nil } // Test helpers func createTestAddress(path string) ucxl.Address { addr, _ := ucxl.ParseAddress(fmt.Sprintf("ucxl://test/%s", path)) return *addr } func createTestContext(path string, technologies []string) *slurpContext.ContextNode { return &slurpContext.ContextNode{ Path: path, UCXLAddress: createTestAddress(path), Summary: fmt.Sprintf("Test context for %s", path), Purpose: fmt.Sprintf("Test purpose for %s", path), Technologies: technologies, Tags: []string{"test"}, Insights: []string{"test insight"}, GeneratedAt: time.Now(), RAGConfidence: 0.8, } } func createTestDecision(id, maker, rationale string, scope ImpactScope) *DecisionMetadata { return &DecisionMetadata{ ID: id, Maker: maker, Rationale: rationale, Scope: scope, ConfidenceLevel: 0.8, ExternalRefs: []string{}, CreatedAt: time.Now(), ImplementationStatus: "complete", Metadata: make(map[string]interface{}), } } // Core temporal graph tests func TestTemporalGraph_CreateInitialContext(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() address := createTestAddress("test/component") contextData := createTestContext("test/component", []string{"go", "test"}) node, err := graph.CreateInitialContext(ctx, address, contextData, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } if node == nil { t.Fatal("Expected node to be created") } if node.Version != 1 { t.Errorf("Expected version 1, got %d", node.Version) } if node.ChangeReason != ReasonInitialCreation { t.Errorf("Expected initial creation reason, got %s", node.ChangeReason) } if node.ParentNode != nil { t.Error("Expected no parent node for initial context") } } func TestTemporalGraph_EvolveContext(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() address := createTestAddress("test/component") initialContext := createTestContext("test/component", []string{"go", "test"}) // Create initial context _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Evolve context updatedContext := createTestContext("test/component", []string{"go", "test", "updated"}) decision := createTestDecision("dec-001", "test_maker", "Adding new technology", ImpactModule) evolvedNode, err := graph.EvolveContext(ctx, address, updatedContext, ReasonCodeChange, decision) if err != nil { t.Fatalf("Failed to evolve context: %v", err) } if evolvedNode.Version != 2 { t.Errorf("Expected version 2, got %d", evolvedNode.Version) } if evolvedNode.ChangeReason != ReasonCodeChange { t.Errorf("Expected code change reason, got %s", evolvedNode.ChangeReason) } if evolvedNode.ParentNode == nil { t.Error("Expected parent node reference") } } func TestTemporalGraph_GetLatestVersion(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() address := createTestAddress("test/component") initialContext := createTestContext("test/component", []string{"go"}) // Create initial version _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Evolve multiple times for i := 2; i <= 5; 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) } } // Get latest version latest, err := graph.GetLatestVersion(ctx, address) if err != nil { t.Fatalf("Failed to get latest version: %v", err) } if latest.Version != 5 { t.Errorf("Expected latest version 5, got %d", latest.Version) } } func TestTemporalGraph_GetEvolutionHistory(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() address := createTestAddress("test/component") initialContext := createTestContext("test/component", []string{"go"}) // Create initial version _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Evolve multiple times for i := 2; i <= 3; 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) } } // Get evolution history history, err := graph.GetEvolutionHistory(ctx, address) if err != nil { t.Fatalf("Failed to get evolution history: %v", err) } if len(history) != 3 { t.Errorf("Expected 3 versions in history, got %d", len(history)) } // Verify ordering for i, node := range history { expectedVersion := i + 1 if node.Version != expectedVersion { t.Errorf("Expected version %d at index %d, got %d", expectedVersion, i, node.Version) } } } func TestTemporalGraph_InfluenceRelationships(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() // Create two contexts addr1 := createTestAddress("test/component1") addr2 := createTestAddress("test/component2") context1 := createTestContext("test/component1", []string{"go"}) context2 := createTestContext("test/component2", []string{"go"}) _, err := graph.CreateInitialContext(ctx, addr1, context1, "test_creator") if err != nil { t.Fatalf("Failed to create context 1: %v", err) } _, err = graph.CreateInitialContext(ctx, addr2, context2, "test_creator") if err != nil { t.Fatalf("Failed to create context 2: %v", err) } // Add influence relationship err = graph.AddInfluenceRelationship(ctx, addr1, addr2) if err != nil { t.Fatalf("Failed to add influence relationship: %v", err) } // Get influence relationships influences, influencedBy, err := graph.GetInfluenceRelationships(ctx, addr1) if err != nil { t.Fatalf("Failed to get influence relationships: %v", err) } if len(influences) != 1 { t.Errorf("Expected 1 influence, got %d", len(influences)) } if influences[0].String() != addr2.String() { t.Errorf("Expected influence to addr2, got %s", influences[0].String()) } if len(influencedBy) != 0 { t.Errorf("Expected 0 influenced by, got %d", len(influencedBy)) } // Check reverse relationship influences2, influencedBy2, err := graph.GetInfluenceRelationships(ctx, addr2) if err != nil { t.Fatalf("Failed to get influence relationships for addr2: %v", err) } if len(influences2) != 0 { t.Errorf("Expected 0 influences for addr2, got %d", len(influences2)) } if len(influencedBy2) != 1 { t.Errorf("Expected 1 influenced by for addr2, got %d", len(influencedBy2)) } } func TestTemporalGraph_FindRelatedDecisions(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() // Create a network of 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 chain: 0 -> 1 -> 2 -> 3 -> 4 for i := 0; i < 4; i++ { err := graph.AddInfluenceRelationship(ctx, addresses[i], addresses[i+1]) if err != nil { t.Fatalf("Failed to add influence relationship %d->%d: %v", i, i+1, err) } } // Find related decisions within 3 hops from address 0 relatedPaths, err := graph.FindRelatedDecisions(ctx, addresses[0], 3) if err != nil { t.Fatalf("Failed to find related decisions: %v", err) } // Should find addresses 1, 2, 3 (within 3 hops) if len(relatedPaths) < 3 { t.Errorf("Expected at least 3 related decisions, got %d", len(relatedPaths)) } // Verify hop distances foundAddresses := make(map[string]int) for _, path := range relatedPaths { foundAddresses[path.To.String()] = path.TotalHops } for i := 1; i <= 3; i++ { expectedAddr := addresses[i].String() if hops, found := foundAddresses[expectedAddr]; found { if hops != i { t.Errorf("Expected %d hops to address %d, got %d", i, i, hops) } } else { t.Errorf("Expected to find address %d in related decisions", i) } } } func TestTemporalGraph_FindDecisionPath(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() // Create contexts addr1 := createTestAddress("test/start") addr2 := createTestAddress("test/middle") addr3 := createTestAddress("test/end") contexts := []*slurpContext.ContextNode{ createTestContext("test/start", []string{"go"}), createTestContext("test/middle", []string{"go"}), createTestContext("test/end", []string{"go"}), } 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) } } // Create path: start -> middle -> end err := graph.AddInfluenceRelationship(ctx, addr1, addr2) if err != nil { t.Fatalf("Failed to add relationship start->middle: %v", err) } err = graph.AddInfluenceRelationship(ctx, addr2, addr3) if err != nil { t.Fatalf("Failed to add relationship middle->end: %v", err) } // Find path from start to end path, err := graph.FindDecisionPath(ctx, addr1, addr3) if err != nil { t.Fatalf("Failed to find decision path: %v", err) } if len(path) != 2 { t.Errorf("Expected path length 2, got %d", len(path)) } // Verify path steps if path[0].Address.String() != addr1.String() { t.Errorf("Expected first step to be start address, got %s", path[0].Address.String()) } if path[1].Address.String() != addr2.String() { t.Errorf("Expected second step to be middle address, got %s", path[1].Address.String()) } } func TestTemporalGraph_ValidateIntegrity(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() // Create valid contexts with proper relationships addr1 := createTestAddress("test/component1") addr2 := createTestAddress("test/component2") context1 := createTestContext("test/component1", []string{"go"}) context2 := createTestContext("test/component2", []string{"go"}) _, err := graph.CreateInitialContext(ctx, addr1, context1, "test_creator") if err != nil { t.Fatalf("Failed to create context 1: %v", err) } _, err = graph.CreateInitialContext(ctx, addr2, context2, "test_creator") if err != nil { t.Fatalf("Failed to create context 2: %v", err) } err = graph.AddInfluenceRelationship(ctx, addr1, addr2) if err != nil { t.Fatalf("Failed to add influence relationship: %v", err) } // Validate integrity - should pass err = graph.ValidateTemporalIntegrity(ctx) if err != nil { t.Errorf("Expected integrity validation to pass, got error: %v", err) } } func TestTemporalGraph_CompactHistory(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() address := createTestAddress("test/component") initialContext := createTestContext("test/component", []string{"go"}) // Create initial version (old) oldTime := time.Now().Add(-60 * 24 * time.Hour) // 60 days ago _, err := graph.CreateInitialContext(ctx, address, initialContext, "test_creator") if err != nil { t.Fatalf("Failed to create initial context: %v", err) } // Create several more versions for i := 2; i <= 10; i++ { updatedContext := createTestContext("test/component", []string{"go", fmt.Sprintf("tech%d", i)}) var reason ChangeReason if i%3 == 0 { reason = ReasonArchitectureChange // Major change - should be kept } else { reason = ReasonCodeChange // Minor change - may be compacted } decision := createTestDecision(fmt.Sprintf("dec-%03d", i), "test_maker", "Update", ImpactLocal) _, err := graph.EvolveContext(ctx, address, updatedContext, reason, decision) if err != nil { t.Fatalf("Failed to evolve context to version %d: %v", i, err) } } // Get history before compaction historyBefore, err := graph.GetEvolutionHistory(ctx, address) if err != nil { t.Fatalf("Failed to get history before compaction: %v", err) } // Compact history (keep recent changes within 30 days) cutoffTime := time.Now().Add(-30 * 24 * time.Hour) err = graph.CompactHistory(ctx, cutoffTime) if err != nil { t.Fatalf("Failed to compact history: %v", err) } // Get history after compaction historyAfter, err := graph.GetEvolutionHistory(ctx, address) if err != nil { t.Fatalf("Failed to get history after compaction: %v", err) } // History should be smaller but still contain recent changes if len(historyAfter) >= len(historyBefore) { t.Errorf("Expected history to be compacted, before: %d, after: %d", len(historyBefore), len(historyAfter)) } // Latest version should still exist latest, err := graph.GetLatestVersion(ctx, address) if err != nil { t.Fatalf("Failed to get latest version after compaction: %v", err) } if latest.Version != 10 { t.Errorf("Expected latest version 10 after compaction, got %d", latest.Version) } } // Performance tests func BenchmarkTemporalGraph_CreateInitialContext(b *testing.B) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { address := createTestAddress(fmt.Sprintf("test/component%d", i)) contextData := createTestContext(fmt.Sprintf("test/component%d", i), []string{"go", "test"}) _, err := graph.CreateInitialContext(ctx, address, contextData, "test_creator") if err != nil { b.Fatalf("Failed to create initial context: %v", err) } } } func BenchmarkTemporalGraph_EvolveContext(b *testing.B) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() // Setup: create initial context 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) } b.ResetTimer() for i := 0; i < b.N; 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 { b.Fatalf("Failed to evolve context: %v", err) } } } func BenchmarkTemporalGraph_FindRelatedDecisions(b *testing.B) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() // Setup: create network of 100 contexts addresses := make([]ucxl.Address, 100) for i := 0; i < 100; 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 relationships if i > 10 && i%10 == 0 { err = graph.AddInfluenceRelationship(ctx, addresses[i-10], addresses[i]) if err != nil { b.Fatalf("Failed to add random influence relationship: %v", err) } } } b.ResetTimer() for i := 0; i < b.N; i++ { startIdx := i % 50 // Use first 50 as starting points _, err := graph.FindRelatedDecisions(ctx, addresses[startIdx], 5) if err != nil { b.Fatalf("Failed to find related decisions: %v", err) } } } // Integration tests func TestTemporalGraphIntegration_ComplexScenario(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() // Scenario: Microservices architecture evolution services := []string{"user-service", "order-service", "payment-service", "notification-service"} addresses := make([]ucxl.Address, len(services)) // Create initial services for i, service := range services { addresses[i] = createTestAddress(fmt.Sprintf("microservices/%s", service)) context := createTestContext(fmt.Sprintf("microservices/%s", service), []string{"go", "microservice"}) _, err := graph.CreateInitialContext(ctx, addresses[i], context, "architect") if err != nil { t.Fatalf("Failed to create %s: %v", service, err) } } // Establish service dependencies // user-service -> order-service -> payment-service // order-service -> notification-service dependencies := [][]int{ {0, 1}, // user -> order {1, 2}, // order -> payment {1, 3}, // order -> notification } for _, dep := range dependencies { err := graph.AddInfluenceRelationship(ctx, addresses[dep[0]], addresses[dep[1]]) if err != nil { t.Fatalf("Failed to add dependency: %v", err) } } // Evolve payment service (add security features) paymentContext := createTestContext("microservices/payment-service", []string{"go", "microservice", "security", "encryption"}) decision := createTestDecision("sec-001", "security-team", "Add encryption for PCI compliance", ImpactProject) _, err := graph.EvolveContext(ctx, addresses[2], paymentContext, ReasonSecurityReview, decision) if err != nil { t.Fatalf("Failed to evolve payment service: %v", err) } // Evolve order service (performance improvements) orderContext := createTestContext("microservices/order-service", []string{"go", "microservice", "caching", "performance"}) decision2 := createTestDecision("perf-001", "performance-team", "Add Redis caching", ImpactModule) _, err = graph.EvolveContext(ctx, addresses[1], orderContext, ReasonPerformanceInsight, decision2) if err != nil { t.Fatalf("Failed to evolve order service: %v", err) } // Test: Find impact of payment service changes relatedPaths, err := graph.FindRelatedDecisions(ctx, addresses[2], 3) if err != nil { t.Fatalf("Failed to find related decisions: %v", err) } // Should find order-service as it depends on payment-service foundOrderService := false for _, path := range relatedPaths { if path.To.String() == addresses[1].String() { foundOrderService = true break } } if !foundOrderService { t.Error("Expected to find order-service in related decisions") } // Test: Get evolution history for order service history, err := graph.GetEvolutionHistory(ctx, addresses[1]) if err != nil { t.Fatalf("Failed to get order service history: %v", err) } if len(history) != 2 { t.Errorf("Expected 2 versions in order service history, got %d", len(history)) } // Test: Validate overall integrity err = graph.ValidateTemporalIntegrity(ctx) if err != nil { t.Errorf("Integrity validation failed: %v", err) } } // Error handling tests func TestTemporalGraph_ErrorHandling(t *testing.T) { storage := newMockStorage() graph := NewTemporalGraph(storage) ctx := context.Background() // Test: Get latest version for non-existent address nonExistentAddr := createTestAddress("non/existent") _, err := graph.GetLatestVersion(ctx, nonExistentAddr) if err == nil { t.Error("Expected error when getting latest version for non-existent address") } // Test: Evolve non-existent context context := createTestContext("non/existent", []string{"go"}) decision := createTestDecision("dec-001", "test", "Test", ImpactLocal) _, err = graph.EvolveContext(ctx, nonExistentAddr, context, ReasonCodeChange, decision) if err == nil { t.Error("Expected error when evolving non-existent context") } // Test: Add influence relationship with non-existent addresses addr1 := createTestAddress("test/addr1") addr2 := createTestAddress("test/addr2") err = graph.AddInfluenceRelationship(ctx, addr1, addr2) if err == nil { t.Error("Expected error when adding influence relationship with non-existent addresses") } // Test: Find decision path between non-existent addresses _, err = graph.FindDecisionPath(ctx, addr1, addr2) if err == nil { t.Error("Expected error when finding path between non-existent addresses") } }