Pulse/internal/ai/memory/incidents_test.go
rcourtman 82e5b28840 feat: enhance AI baseline context visibility and incident timeline improvements
Backend:
- Enhanced buildEnrichedResourceContext to ALWAYS show learned baselines with
  status indicators (normal/elevated/anomaly) instead of only when anomalous
- This makes Pulse Pro's 'moat' visible - users can see the AI understands
  their infrastructure's normal behavior patterns
- Added baseline import to service.go

Frontend (user changes):
- Added incident event type filtering with toggle buttons
- Added resource incident panel to view all incidents for a resource
- Added timeline expand/collapse functionality in alert history
- Added incident note saving with proper incidentId tracking
- Added startedAt parameter for proper incident timeline loading
2025-12-21 00:14:20 +00:00

108 lines
2.9 KiB
Go

package memory
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
)
func TestIncidentStore_RecordTimeline(t *testing.T) {
store := NewIncidentStore(IncidentStoreConfig{
DataDir: t.TempDir(),
MaxIncidents: 10,
MaxEventsPerIncident: 10,
MaxAgeDays: 30,
})
alert := &alerts.Alert{
ID: "alert-1",
Type: "cpu",
Level: alerts.AlertLevelWarning,
ResourceID: "res-1",
ResourceName: "vm-1",
StartTime: time.Now().Add(-5 * time.Minute),
Value: 92,
Threshold: 85,
}
store.RecordAlertFired(alert)
store.RecordAlertAcknowledged(alert, "admin")
store.RecordAnalysis(alert.ID, "analysis complete", map[string]interface{}{
"findings": 1,
})
store.RecordCommand(alert.ID, "systemctl restart nginx", true, "ok", nil)
store.RecordAlertResolved(alert, time.Now())
timeline := store.GetTimelineByAlertID(alert.ID)
if timeline == nil {
t.Fatalf("expected timeline, got nil")
}
if timeline.Status != IncidentStatusResolved {
t.Fatalf("expected status %q, got %q", IncidentStatusResolved, timeline.Status)
}
if timeline.AckUser != "admin" {
t.Fatalf("expected ack user admin, got %q", timeline.AckUser)
}
if len(timeline.Events) < 4 {
t.Fatalf("expected events recorded, got %d", len(timeline.Events))
}
if ok := store.RecordNote(alert.ID, "", "note text", ""); !ok {
t.Fatalf("expected note to be saved")
}
}
func TestIncidentStore_GetTimelineByAlertAt(t *testing.T) {
store := NewIncidentStore(IncidentStoreConfig{
DataDir: t.TempDir(),
MaxIncidents: 10,
MaxEventsPerIncident: 10,
MaxAgeDays: 30,
})
base := time.Now().UTC()
first := &alerts.Alert{
ID: "alert-2",
Type: "cpu",
Level: alerts.AlertLevelWarning,
ResourceID: "res-2",
ResourceName: "vm-2",
StartTime: base.Add(-2 * time.Hour),
}
second := &alerts.Alert{
ID: "alert-2",
Type: "cpu",
Level: alerts.AlertLevelWarning,
ResourceID: "res-2",
ResourceName: "vm-2",
StartTime: base.Add(-10 * time.Minute),
}
store.RecordAlertFired(first)
store.RecordAlertResolved(first, base.Add(-90*time.Minute))
store.RecordAlertFired(second)
timeline := store.GetTimelineByAlertAt(first.ID, first.StartTime)
if timeline == nil {
t.Fatalf("expected timeline for first incident, got nil")
}
if !timeline.OpenedAt.Equal(first.StartTime) {
t.Fatalf("expected openedAt %s, got %s", first.StartTime, timeline.OpenedAt)
}
timeline = store.GetTimelineByAlertAt(second.ID, second.StartTime)
if timeline == nil {
t.Fatalf("expected timeline for second incident, got nil")
}
if !timeline.OpenedAt.Equal(second.StartTime) {
t.Fatalf("expected openedAt %s, got %s", second.StartTime, timeline.OpenedAt)
}
timeline = store.GetTimelineByAlertAt(second.ID, base.Add(-45*time.Minute))
if timeline != nil {
t.Fatalf("expected no timeline for mismatched start time")
}
}