Pulse/internal/ai/findings_persistence_test.go

216 lines
5.8 KiB
Go

package ai
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
)
func TestNewFindingsPersistenceAdapter(t *testing.T) {
tmp := t.TempDir()
persistence := config.NewConfigPersistence(tmp)
adapter := NewFindingsPersistenceAdapter(persistence)
if adapter == nil {
t.Fatal("expected non-nil adapter")
}
if adapter.config != persistence {
t.Fatal("expected config to match persistence")
}
}
func TestFindingsPersistenceAdapter_SaveAndLoad(t *testing.T) {
tmp := t.TempDir()
persistence := config.NewConfigPersistence(tmp)
adapter := NewFindingsPersistenceAdapter(persistence)
now := time.Now()
findings := map[string]*Finding{
"finding-1": {
ID: "finding-1",
Key: "high-cpu",
Severity: FindingSeverityWarning,
Category: FindingCategoryPerformance,
ResourceID: "node1-100",
ResourceName: "test-vm",
ResourceType: "vm",
Node: "node1",
Title: "High CPU Usage",
Description: "CPU is at 95%",
Recommendation: "Check running processes",
Evidence: "CPU: 95%",
DetectedAt: now,
LastSeenAt: now,
},
"finding-2": {
ID: "finding-2",
Key: "high-memory",
Severity: FindingSeverityCritical,
Category: FindingCategoryCapacity,
ResourceID: "node1-101",
ResourceName: "test-container",
ResourceType: "container",
Node: "node1",
Title: "High Memory Usage",
Description: "Memory is at 90%",
Recommendation: "Increase memory allocation",
Evidence: "Memory: 90%",
DetectedAt: now.Add(-time.Hour),
LastSeenAt: now,
AlertID: "alert-123",
},
}
// Save findings
err := adapter.SaveFindings(findings)
if err != nil {
t.Fatalf("failed to save findings: %v", err)
}
// Load findings back
loaded, err := adapter.LoadFindings()
if err != nil {
t.Fatalf("failed to load findings: %v", err)
}
if len(loaded) != len(findings) {
t.Fatalf("expected %d findings, got %d", len(findings), len(loaded))
}
// Verify first finding
f1 := loaded["finding-1"]
if f1 == nil {
t.Fatal("finding-1 not found")
}
if f1.Key != "high-cpu" {
t.Errorf("expected key 'high-cpu', got %q", f1.Key)
}
if f1.Severity != FindingSeverityWarning {
t.Errorf("expected severity 'warning', got %q", f1.Severity)
}
if f1.Category != FindingCategoryPerformance {
t.Errorf("expected category 'performance', got %q", f1.Category)
}
if f1.ResourceID != "node1-100" {
t.Errorf("expected resource ID 'node1-100', got %q", f1.ResourceID)
}
// Verify second finding
f2 := loaded["finding-2"]
if f2 == nil {
t.Fatal("finding-2 not found")
}
if f2.AlertID != "alert-123" {
t.Errorf("expected alert ID 'alert-123', got %q", f2.AlertID)
}
}
func TestFindingsPersistenceAdapter_LoadEmpty(t *testing.T) {
tmp := t.TempDir()
persistence := config.NewConfigPersistence(tmp)
adapter := NewFindingsPersistenceAdapter(persistence)
// Load from empty persistence should return empty map, not error
loaded, err := adapter.LoadFindings()
if err != nil {
t.Fatalf("expected no error for empty load, got: %v", err)
}
if len(loaded) != 0 {
t.Fatalf("expected empty map, got %d findings", len(loaded))
}
}
func TestFindingsPersistenceAdapter_SaveEmpty(t *testing.T) {
tmp := t.TempDir()
persistence := config.NewConfigPersistence(tmp)
adapter := NewFindingsPersistenceAdapter(persistence)
// Save empty map should work
err := adapter.SaveFindings(map[string]*Finding{})
if err != nil {
t.Fatalf("failed to save empty findings: %v", err)
}
}
func TestFindingsPersistenceAdapter_PreservesAllFields(t *testing.T) {
tmp := t.TempDir()
persistence := config.NewConfigPersistence(tmp)
adapter := NewFindingsPersistenceAdapter(persistence)
now := time.Now()
resolved := now.Add(time.Hour)
acked := now.Add(30 * time.Minute)
snoozed := now.Add(24 * time.Hour)
originalFinding := &Finding{
ID: "test-finding",
Key: "test-key",
Severity: FindingSeverityCritical,
Category: FindingCategorySecurity,
ResourceID: "resource-123",
ResourceName: "Test Resource",
ResourceType: "vm",
Node: "node1",
Title: "Test Title",
Description: "Test Description",
Recommendation: "Test Recommendation",
Evidence: "Test Evidence",
DetectedAt: now,
LastSeenAt: now,
ResolvedAt: &resolved,
AutoResolved: true,
AcknowledgedAt: &acked,
SnoozedUntil: &snoozed,
AlertID: "alert-456",
}
findings := map[string]*Finding{"test-finding": originalFinding}
// Save and load
if err := adapter.SaveFindings(findings); err != nil {
t.Fatalf("save failed: %v", err)
}
loaded, err := adapter.LoadFindings()
if err != nil {
t.Fatalf("load failed: %v", err)
}
f := loaded["test-finding"]
if f == nil {
t.Fatal("finding not found after load")
}
// Verify all fields preserved
if f.ID != originalFinding.ID {
t.Errorf("ID mismatch: got %q", f.ID)
}
if f.Key != originalFinding.Key {
t.Errorf("Key mismatch: got %q", f.Key)
}
if f.Severity != originalFinding.Severity {
t.Errorf("Severity mismatch: got %q", f.Severity)
}
if f.Category != originalFinding.Category {
t.Errorf("Category mismatch: got %q", f.Category)
}
if f.ResourceID != originalFinding.ResourceID {
t.Errorf("ResourceID mismatch: got %q", f.ResourceID)
}
if f.AutoResolved != originalFinding.AutoResolved {
t.Errorf("AutoResolved mismatch: got %v", f.AutoResolved)
}
if f.AlertID != originalFinding.AlertID {
t.Errorf("AlertID mismatch: got %q", f.AlertID)
}
if f.ResolvedAt == nil {
t.Error("ResolvedAt should not be nil")
}
if f.AcknowledgedAt == nil {
t.Error("AcknowledgedAt should not be nil")
}
if f.SnoozedUntil == nil {
t.Error("SnoozedUntil should not be nil")
}
}