package correlation import ( "testing" "time" ) func TestDetector_RecordEvent(t *testing.T) { d := NewDetector(DefaultConfig()) d.RecordEvent(Event{ ResourceID: "vm-100", ResourceName: "web-server", EventType: EventHighMem, Timestamp: time.Now(), }) if len(d.events) != 1 { t.Errorf("Expected 1 event, got %d", len(d.events)) } } func TestDetector_CorrelationDetection(t *testing.T) { d := NewDetector(Config{ MaxEvents: 1000, CorrelationWindow: 10 * time.Minute, MinOccurrences: 2, // Lower threshold for testing RetentionWindow: 30 * 24 * time.Hour, }) now := time.Now() // Simulate pattern: when storage has high usage, database VM restarts // Occurrence 1 d.RecordEvent(Event{ ResourceID: "storage-1", ResourceName: "local-zfs", ResourceType: "storage", EventType: EventDiskFull, Timestamp: now.Add(-2 * time.Hour), }) d.RecordEvent(Event{ ResourceID: "vm-100", ResourceName: "database", ResourceType: "vm", EventType: EventRestart, Timestamp: now.Add(-2*time.Hour + 5*time.Minute), }) // Occurrence 2 d.RecordEvent(Event{ ResourceID: "storage-1", ResourceName: "local-zfs", ResourceType: "storage", EventType: EventDiskFull, Timestamp: now.Add(-1 * time.Hour), }) d.RecordEvent(Event{ ResourceID: "vm-100", ResourceName: "database", ResourceType: "vm", EventType: EventRestart, Timestamp: now.Add(-1*time.Hour + 5*time.Minute), }) // Occurrence 3 d.RecordEvent(Event{ ResourceID: "storage-1", ResourceName: "local-zfs", ResourceType: "storage", EventType: EventDiskFull, Timestamp: now, }) d.RecordEvent(Event{ ResourceID: "vm-100", ResourceName: "database", ResourceType: "vm", EventType: EventRestart, Timestamp: now.Add(5 * time.Minute), }) // Check correlations correlations := d.GetCorrelations() found := false for _, c := range correlations { if c.SourceID == "storage-1" && c.TargetID == "vm-100" { found = true if c.Occurrences < 2 { t.Errorf("Expected at least 2 occurrences, got %d", c.Occurrences) } break } } if !found { t.Error("Expected correlation between storage-1 and vm-100") } } func TestDetector_GetCorrelationsForResource(t *testing.T) { d := NewDetector(Config{ MaxEvents: 1000, CorrelationWindow: 10 * time.Minute, MinOccurrences: 2, RetentionWindow: 30 * 24 * time.Hour, }) now := time.Now() // Create correlation pattern for i := 0; i < 3; i++ { offset := time.Duration(-i) * time.Hour d.RecordEvent(Event{ ResourceID: "node-1", EventType: EventHighCPU, Timestamp: now.Add(offset), }) d.RecordEvent(Event{ ResourceID: "vm-100", EventType: EventHighMem, Timestamp: now.Add(offset + 2*time.Minute), }) } // Get correlations for node-1 correlations := d.GetCorrelationsForResource("node-1") if len(correlations) == 0 { t.Error("Expected correlations for node-1") } } func TestDetector_GetDependencies(t *testing.T) { d := NewDetector(Config{ MaxEvents: 1000, CorrelationWindow: 10 * time.Minute, MinOccurrences: 3, RetentionWindow: 30 * 24 * time.Hour, }) now := time.Now() // When storage is full, both VM and container have issues for i := 0; i < 4; i++ { offset := time.Duration(-i) * time.Hour d.RecordEvent(Event{ ResourceID: "storage-1", EventType: EventDiskFull, Timestamp: now.Add(offset), }) d.RecordEvent(Event{ ResourceID: "vm-100", EventType: EventRestart, Timestamp: now.Add(offset + 3*time.Minute), }) d.RecordEvent(Event{ ResourceID: "ct-200", EventType: EventRestart, Timestamp: now.Add(offset + 5*time.Minute), }) } deps := d.GetDependencies("storage-1") if len(deps) < 2 { t.Errorf("Expected at least 2 dependencies, got %d", len(deps)) } } func TestDetector_PredictCascade(t *testing.T) { d := NewDetector(Config{ MaxEvents: 1000, CorrelationWindow: 10 * time.Minute, MinOccurrences: 3, RetentionWindow: 30 * 24 * time.Hour, }) now := time.Now() // Create strong correlation for i := 0; i < 5; i++ { offset := time.Duration(-i) * time.Hour d.RecordEvent(Event{ ResourceID: "node-1", ResourceName: "pve-main", EventType: EventHighMem, Timestamp: now.Add(offset), }) d.RecordEvent(Event{ ResourceID: "vm-100", ResourceName: "database", EventType: EventRestart, Timestamp: now.Add(offset + 5*time.Minute), }) } predictions := d.PredictCascade("node-1", EventHighMem) if len(predictions) == 0 { t.Error("Expected cascade predictions") } } func TestDetector_FormatForContext(t *testing.T) { d := NewDetector(Config{ MaxEvents: 1000, CorrelationWindow: 10 * time.Minute, MinOccurrences: 2, RetentionWindow: 30 * 24 * time.Hour, }) now := time.Now() // Create correlations for i := 0; i < 3; i++ { offset := time.Duration(-i) * time.Hour d.RecordEvent(Event{ ResourceID: "storage-1", ResourceName: "local-zfs", EventType: EventDiskFull, Timestamp: now.Add(offset), }) d.RecordEvent(Event{ ResourceID: "vm-100", ResourceName: "database", EventType: EventRestart, Timestamp: now.Add(offset + 5*time.Minute), }) } context := d.FormatForContext("") if context == "" { t.Error("Expected non-empty context") } if !contains(context, "Correlation") { t.Errorf("Expected context to mention correlations: %s", context) } } func contains(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }