test: Add preserveAlertState, checkPMGQuarantineBacklog, LoadActiveAlerts tests
Add comprehensive tests for three low-coverage functions: - preserveAlertState: nil handling, state preservation from existing alerts, ackState fallback, new alert handling - checkPMGQuarantineBacklog: nil quarantine handling, warning/critical thresholds, growth rate alerts, alert updates, virus quarantine - LoadActiveAlerts: missing file, valid file loading, old alert filtering, old acknowledged alert filtering, ack state restoration, invalid JSON, duplicate alert handling Coverage improvements: - preserveAlertState: 63.6% → 100% - checkPMGQuarantineBacklog: 12.9% → 100% - checkQuarantineMetric: 0% → 93.1% - LoadActiveAlerts: 26.2% → 80.0% - Alerts package: 83.5% → 86.6%
This commit is contained in:
parent
2133b25bb7
commit
a4e3fe65b8
1 changed files with 663 additions and 0 deletions
|
|
@ -1,9 +1,11 @@
|
|||
package alerts
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -11,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/proxmox"
|
||||
)
|
||||
|
||||
|
|
@ -14276,3 +14279,663 @@ func TestDispatchAlert(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPreserveAlertState(t *testing.T) {
|
||||
t.Run("nil updated alert is handled", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
// Should not panic
|
||||
m.preserveAlertState("test-id", nil)
|
||||
})
|
||||
|
||||
t.Run("preserves state from existing alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
|
||||
ackTime := time.Now().Add(-30 * time.Minute)
|
||||
existing := &Alert{
|
||||
ID: "test-alert",
|
||||
Type: "cpu",
|
||||
StartTime: time.Now().Add(-1 * time.Hour),
|
||||
Acknowledged: true,
|
||||
AckUser: "testuser",
|
||||
AckTime: &ackTime,
|
||||
LastEscalation: 2,
|
||||
EscalationTimes: []time.Time{time.Now().Add(-25 * time.Minute)},
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.activeAlerts["test-alert"] = existing
|
||||
m.mu.Unlock()
|
||||
|
||||
updated := &Alert{
|
||||
ID: "test-alert",
|
||||
Type: "cpu",
|
||||
StartTime: time.Now(), // Different start time
|
||||
}
|
||||
|
||||
m.preserveAlertState("test-alert", updated)
|
||||
|
||||
if !updated.StartTime.Equal(existing.StartTime) {
|
||||
t.Error("StartTime should be preserved from existing alert")
|
||||
}
|
||||
if !updated.Acknowledged {
|
||||
t.Error("Acknowledged should be preserved")
|
||||
}
|
||||
if updated.AckUser != "testuser" {
|
||||
t.Errorf("AckUser should be preserved, got %s", updated.AckUser)
|
||||
}
|
||||
if updated.AckTime == nil || !updated.AckTime.Equal(ackTime) {
|
||||
t.Error("AckTime should be preserved")
|
||||
}
|
||||
if updated.LastEscalation != 2 {
|
||||
t.Error("LastEscalation should be preserved")
|
||||
}
|
||||
if len(updated.EscalationTimes) != 1 {
|
||||
t.Error("EscalationTimes should be preserved")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("falls back to ackState for new alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
|
||||
ackTime := time.Now().Add(-15 * time.Minute)
|
||||
m.mu.Lock()
|
||||
m.ackState["test-alert"] = ackRecord{
|
||||
acknowledged: true,
|
||||
user: "fallbackuser",
|
||||
time: ackTime,
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
updated := &Alert{
|
||||
ID: "test-alert",
|
||||
Type: "cpu",
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
|
||||
m.preserveAlertState("test-alert", updated)
|
||||
|
||||
if !updated.Acknowledged {
|
||||
t.Error("Acknowledged should be set from ackState")
|
||||
}
|
||||
if updated.AckUser != "fallbackuser" {
|
||||
t.Errorf("AckUser should be from ackState, got %s", updated.AckUser)
|
||||
}
|
||||
if updated.AckTime == nil || !updated.AckTime.Equal(ackTime) {
|
||||
t.Error("AckTime should be from ackState")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no state to preserve for new alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
|
||||
startTime := time.Now()
|
||||
updated := &Alert{
|
||||
ID: "new-alert",
|
||||
Type: "cpu",
|
||||
StartTime: startTime,
|
||||
}
|
||||
|
||||
m.preserveAlertState("new-alert", updated)
|
||||
|
||||
if !updated.StartTime.Equal(startTime) {
|
||||
t.Error("StartTime should remain unchanged for new alert")
|
||||
}
|
||||
if updated.Acknowledged {
|
||||
t.Error("Acknowledged should remain false for new alert")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckPMGQuarantineBacklog(t *testing.T) {
|
||||
t.Run("nil quarantine clears alerts", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
|
||||
// Create an existing quarantine alert
|
||||
m.mu.Lock()
|
||||
m.activeAlerts["pmg1-quarantine-spam"] = &Alert{
|
||||
ID: "pmg1-quarantine-spam",
|
||||
Type: "quarantine-spam",
|
||||
}
|
||||
m.activeAlerts["pmg1-quarantine-virus"] = &Alert{
|
||||
ID: "pmg1-quarantine-virus",
|
||||
Type: "quarantine-virus",
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
pmg := models.PMGInstance{
|
||||
ID: "pmg1",
|
||||
Name: "pmg-server",
|
||||
Host: "pmg.example.com",
|
||||
Quarantine: nil,
|
||||
}
|
||||
|
||||
m.checkPMGQuarantineBacklog(pmg, PMGThresholdConfig{})
|
||||
|
||||
m.mu.RLock()
|
||||
_, spamExists := m.activeAlerts["pmg1-quarantine-spam"]
|
||||
_, virusExists := m.activeAlerts["pmg1-quarantine-virus"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if spamExists {
|
||||
t.Error("spam alert should be cleared when quarantine is nil")
|
||||
}
|
||||
if virusExists {
|
||||
t.Error("virus alert should be cleared when quarantine is nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("warning threshold triggers alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
m.mu.Lock()
|
||||
m.pmgQuarantineHistory = make(map[string][]pmgQuarantineSnapshot)
|
||||
m.config.ActivationState = ActivationActive
|
||||
m.mu.Unlock()
|
||||
|
||||
pmg := models.PMGInstance{
|
||||
ID: "pmg1",
|
||||
Name: "pmg-server",
|
||||
Host: "pmg.example.com",
|
||||
Quarantine: &models.PMGQuarantineTotals{
|
||||
Spam: 2500, // Above warning threshold
|
||||
Virus: 100,
|
||||
},
|
||||
}
|
||||
|
||||
thresholds := PMGThresholdConfig{
|
||||
QuarantineSpamWarn: 2000,
|
||||
QuarantineSpamCritical: 5000,
|
||||
QuarantineVirusWarn: 2000,
|
||||
QuarantineVirusCritical: 5000,
|
||||
}
|
||||
|
||||
m.checkPMGQuarantineBacklog(pmg, thresholds)
|
||||
|
||||
m.mu.RLock()
|
||||
alert, exists := m.activeAlerts["pmg1-quarantine-spam"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Fatal("spam quarantine warning alert should be created")
|
||||
}
|
||||
if alert.Level != AlertLevelWarning {
|
||||
t.Errorf("alert level should be warning, got %s", alert.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("critical threshold triggers alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
m.mu.Lock()
|
||||
m.pmgQuarantineHistory = make(map[string][]pmgQuarantineSnapshot)
|
||||
m.config.ActivationState = ActivationActive
|
||||
m.mu.Unlock()
|
||||
|
||||
pmg := models.PMGInstance{
|
||||
ID: "pmg1",
|
||||
Name: "pmg-server",
|
||||
Host: "pmg.example.com",
|
||||
Quarantine: &models.PMGQuarantineTotals{
|
||||
Spam: 6000, // Above critical threshold
|
||||
Virus: 100,
|
||||
},
|
||||
}
|
||||
|
||||
thresholds := PMGThresholdConfig{
|
||||
QuarantineSpamWarn: 2000,
|
||||
QuarantineSpamCritical: 5000,
|
||||
QuarantineVirusWarn: 2000,
|
||||
QuarantineVirusCritical: 5000,
|
||||
}
|
||||
|
||||
m.checkPMGQuarantineBacklog(pmg, thresholds)
|
||||
|
||||
m.mu.RLock()
|
||||
alert, exists := m.activeAlerts["pmg1-quarantine-spam"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Fatal("spam quarantine critical alert should be created")
|
||||
}
|
||||
if alert.Level != AlertLevelCritical {
|
||||
t.Errorf("alert level should be critical, got %s", alert.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("below threshold clears alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
m.mu.Lock()
|
||||
m.pmgQuarantineHistory = make(map[string][]pmgQuarantineSnapshot)
|
||||
m.activeAlerts["pmg1-quarantine-spam"] = &Alert{
|
||||
ID: "pmg1-quarantine-spam",
|
||||
Type: "quarantine-spam",
|
||||
Level: AlertLevelWarning,
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
pmg := models.PMGInstance{
|
||||
ID: "pmg1",
|
||||
Name: "pmg-server",
|
||||
Host: "pmg.example.com",
|
||||
Quarantine: &models.PMGQuarantineTotals{
|
||||
Spam: 500, // Below warning threshold
|
||||
Virus: 100,
|
||||
},
|
||||
}
|
||||
|
||||
thresholds := PMGThresholdConfig{
|
||||
QuarantineSpamWarn: 2000,
|
||||
QuarantineSpamCritical: 5000,
|
||||
}
|
||||
|
||||
m.checkPMGQuarantineBacklog(pmg, thresholds)
|
||||
|
||||
m.mu.RLock()
|
||||
_, exists := m.activeAlerts["pmg1-quarantine-spam"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if exists {
|
||||
t.Error("spam quarantine alert should be cleared when below threshold")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("growth rate triggers warning alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
m.mu.Lock()
|
||||
m.config.ActivationState = ActivationActive
|
||||
// Set up history from ~2 hours ago
|
||||
m.pmgQuarantineHistory = map[string][]pmgQuarantineSnapshot{
|
||||
"pmg1": {
|
||||
{
|
||||
Spam: 1000,
|
||||
Virus: 100,
|
||||
Timestamp: time.Now().Add(-2 * time.Hour),
|
||||
},
|
||||
},
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
pmg := models.PMGInstance{
|
||||
ID: "pmg1",
|
||||
Name: "pmg-server",
|
||||
Host: "pmg.example.com",
|
||||
Quarantine: &models.PMGQuarantineTotals{
|
||||
Spam: 1500, // 50% growth (500 messages)
|
||||
Virus: 100,
|
||||
},
|
||||
}
|
||||
|
||||
thresholds := PMGThresholdConfig{
|
||||
QuarantineSpamWarn: 10000, // High absolute threshold (won't trigger)
|
||||
QuarantineSpamCritical: 20000,
|
||||
QuarantineGrowthWarnPct: 25, // 25% growth warning
|
||||
QuarantineGrowthWarnMin: 250, // Minimum 250 messages
|
||||
QuarantineGrowthCritPct: 50, // 50% growth critical
|
||||
QuarantineGrowthCritMin: 500, // Minimum 500 messages
|
||||
}
|
||||
|
||||
m.checkPMGQuarantineBacklog(pmg, thresholds)
|
||||
|
||||
m.mu.RLock()
|
||||
alert, exists := m.activeAlerts["pmg1-quarantine-spam"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Fatal("spam quarantine growth alert should be created")
|
||||
}
|
||||
if alert.Level != AlertLevelCritical {
|
||||
t.Errorf("alert level should be critical due to 50%% growth + 500 messages, got %s", alert.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("updates existing alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
m.mu.Lock()
|
||||
m.pmgQuarantineHistory = make(map[string][]pmgQuarantineSnapshot)
|
||||
m.config.ActivationState = ActivationActive
|
||||
m.activeAlerts["pmg1-quarantine-spam"] = &Alert{
|
||||
ID: "pmg1-quarantine-spam",
|
||||
Type: "quarantine-spam",
|
||||
Level: AlertLevelWarning,
|
||||
Value: 2500,
|
||||
Threshold: 2000,
|
||||
LastSeen: time.Now().Add(-5 * time.Minute),
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
pmg := models.PMGInstance{
|
||||
ID: "pmg1",
|
||||
Name: "pmg-server",
|
||||
Host: "pmg.example.com",
|
||||
Quarantine: &models.PMGQuarantineTotals{
|
||||
Spam: 3000, // Higher spam count
|
||||
Virus: 100,
|
||||
},
|
||||
}
|
||||
|
||||
thresholds := PMGThresholdConfig{
|
||||
QuarantineSpamWarn: 2000,
|
||||
QuarantineSpamCritical: 5000,
|
||||
}
|
||||
|
||||
m.checkPMGQuarantineBacklog(pmg, thresholds)
|
||||
|
||||
m.mu.RLock()
|
||||
alert, exists := m.activeAlerts["pmg1-quarantine-spam"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Fatal("spam quarantine alert should still exist")
|
||||
}
|
||||
if alert.Value != 3000 {
|
||||
t.Errorf("alert value should be updated to 3000, got %.0f", alert.Value)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("virus quarantine alert", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
m.mu.Lock()
|
||||
m.pmgQuarantineHistory = make(map[string][]pmgQuarantineSnapshot)
|
||||
m.config.ActivationState = ActivationActive
|
||||
m.mu.Unlock()
|
||||
|
||||
pmg := models.PMGInstance{
|
||||
ID: "pmg1",
|
||||
Name: "pmg-server",
|
||||
Host: "pmg.example.com",
|
||||
Quarantine: &models.PMGQuarantineTotals{
|
||||
Spam: 100,
|
||||
Virus: 3000, // Above virus warning threshold
|
||||
},
|
||||
}
|
||||
|
||||
thresholds := PMGThresholdConfig{
|
||||
QuarantineSpamWarn: 2000,
|
||||
QuarantineSpamCritical: 5000,
|
||||
QuarantineVirusWarn: 2000,
|
||||
QuarantineVirusCritical: 5000,
|
||||
}
|
||||
|
||||
m.checkPMGQuarantineBacklog(pmg, thresholds)
|
||||
|
||||
m.mu.RLock()
|
||||
alert, exists := m.activeAlerts["pmg1-quarantine-virus"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Fatal("virus quarantine warning alert should be created")
|
||||
}
|
||||
if alert.Level != AlertLevelWarning {
|
||||
t.Errorf("alert level should be warning, got %s", alert.Level)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadActiveAlerts(t *testing.T) {
|
||||
t.Run("no file returns nil error", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
err := m.LoadActiveAlerts()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error when file doesn't exist, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("loads alerts from valid file", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
// Create an alert and save it
|
||||
startTime := time.Now().Add(-30 * time.Minute)
|
||||
alert := &Alert{
|
||||
ID: "test-load-alert",
|
||||
Type: "cpu",
|
||||
Level: AlertLevelWarning,
|
||||
ResourceID: "test-resource",
|
||||
ResourceName: "test-vm",
|
||||
Node: "node1",
|
||||
Instance: "pve1",
|
||||
Message: "Test alert",
|
||||
Value: 85.0,
|
||||
Threshold: 80.0,
|
||||
StartTime: startTime,
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.activeAlerts[alert.ID] = alert
|
||||
m.mu.Unlock()
|
||||
|
||||
// Save to disk
|
||||
_ = m.SaveActiveAlerts()
|
||||
|
||||
// Clear and reload
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
err := m.LoadActiveAlerts()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load alerts: %v", err)
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
loaded, exists := m.activeAlerts["test-load-alert"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Fatal("alert should be loaded from file")
|
||||
}
|
||||
if loaded.Type != "cpu" {
|
||||
t.Errorf("loaded alert type should be cpu, got %s", loaded.Type)
|
||||
}
|
||||
if loaded.Value != 85.0 {
|
||||
t.Errorf("loaded alert value should be 85.0, got %.1f", loaded.Value)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("skips old alerts", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
// Create an old alert (>24 hours)
|
||||
startTime := time.Now().Add(-25 * time.Hour)
|
||||
alert := &Alert{
|
||||
ID: "old-alert",
|
||||
Type: "cpu",
|
||||
Level: AlertLevelWarning,
|
||||
ResourceID: "test-resource",
|
||||
ResourceName: "test-vm",
|
||||
StartTime: startTime,
|
||||
LastSeen: startTime,
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.activeAlerts[alert.ID] = alert
|
||||
m.mu.Unlock()
|
||||
|
||||
// Save to disk
|
||||
_ = m.SaveActiveAlerts()
|
||||
|
||||
// Clear and reload
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
err := m.LoadActiveAlerts()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load alerts: %v", err)
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
_, exists := m.activeAlerts["old-alert"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if exists {
|
||||
t.Error("old alert (>24h) should be skipped during load")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("skips old acknowledged alerts", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
// Create an alert acknowledged >1 hour ago
|
||||
startTime := time.Now().Add(-30 * time.Minute)
|
||||
ackTime := time.Now().Add(-2 * time.Hour)
|
||||
alert := &Alert{
|
||||
ID: "old-ack-alert",
|
||||
Type: "cpu",
|
||||
Level: AlertLevelWarning,
|
||||
ResourceID: "test-resource",
|
||||
ResourceName: "test-vm",
|
||||
StartTime: startTime,
|
||||
LastSeen: time.Now(),
|
||||
Acknowledged: true,
|
||||
AckTime: &ackTime,
|
||||
AckUser: "testuser",
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.activeAlerts[alert.ID] = alert
|
||||
m.mu.Unlock()
|
||||
|
||||
// Save to disk
|
||||
_ = m.SaveActiveAlerts()
|
||||
|
||||
// Clear and reload
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
err := m.LoadActiveAlerts()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load alerts: %v", err)
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
_, exists := m.activeAlerts["old-ack-alert"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if exists {
|
||||
t.Error("old acknowledged alert (>1h) should be skipped during load")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("restores acknowledgment state", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
// Create an acknowledged alert
|
||||
startTime := time.Now().Add(-10 * time.Minute)
|
||||
ackTime := time.Now().Add(-5 * time.Minute)
|
||||
alert := &Alert{
|
||||
ID: "ack-alert",
|
||||
Type: "cpu",
|
||||
Level: AlertLevelWarning,
|
||||
ResourceID: "test-resource",
|
||||
ResourceName: "test-vm",
|
||||
StartTime: startTime,
|
||||
LastSeen: time.Now(),
|
||||
Acknowledged: true,
|
||||
AckTime: &ackTime,
|
||||
AckUser: "testuser",
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.activeAlerts[alert.ID] = alert
|
||||
m.mu.Unlock()
|
||||
|
||||
// Save to disk
|
||||
_ = m.SaveActiveAlerts()
|
||||
|
||||
// Clear and reload
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
err := m.LoadActiveAlerts()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load alerts: %v", err)
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
loaded, exists := m.activeAlerts["ack-alert"]
|
||||
ackRecord, hasAckRecord := m.ackState["ack-alert"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Fatal("acknowledged alert should be loaded")
|
||||
}
|
||||
if !loaded.Acknowledged {
|
||||
t.Error("loaded alert should be acknowledged")
|
||||
}
|
||||
if loaded.AckUser != "testuser" {
|
||||
t.Errorf("loaded alert AckUser should be testuser, got %s", loaded.AckUser)
|
||||
}
|
||||
if !hasAckRecord {
|
||||
t.Error("ackState should be restored for acknowledged alert")
|
||||
}
|
||||
if !ackRecord.acknowledged {
|
||||
t.Error("ackState should show acknowledged=true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid JSON returns error", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
// Write invalid JSON to the alerts file
|
||||
alertsDir := filepath.Join(utils.GetDataDir(), "alerts")
|
||||
if err := os.MkdirAll(alertsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create alerts dir: %v", err)
|
||||
}
|
||||
|
||||
alertsFile := filepath.Join(alertsDir, "active-alerts.json")
|
||||
if err := os.WriteFile(alertsFile, []byte("invalid json"), 0644); err != nil {
|
||||
t.Fatalf("failed to write invalid json: %v", err)
|
||||
}
|
||||
|
||||
err := m.LoadActiveAlerts()
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid JSON")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("skips duplicate alerts", func(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
// Write JSON with duplicate alert IDs
|
||||
alertsDir := filepath.Join(utils.GetDataDir(), "alerts")
|
||||
if err := os.MkdirAll(alertsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create alerts dir: %v", err)
|
||||
}
|
||||
|
||||
startTime := time.Now().Add(-10 * time.Minute)
|
||||
alerts := []Alert{
|
||||
{ID: "dup-alert", Type: "cpu", StartTime: startTime, LastSeen: time.Now()},
|
||||
{ID: "dup-alert", Type: "memory", StartTime: startTime, LastSeen: time.Now()},
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(alerts)
|
||||
alertsFile := filepath.Join(alertsDir, "active-alerts.json")
|
||||
if err := os.WriteFile(alertsFile, data, 0644); err != nil {
|
||||
t.Fatalf("failed to write alerts json: %v", err)
|
||||
}
|
||||
|
||||
err := m.LoadActiveAlerts()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load alerts: %v", err)
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
alert, exists := m.activeAlerts["dup-alert"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
t.Fatal("alert should exist after load")
|
||||
}
|
||||
// First one wins
|
||||
if alert.Type != "cpu" {
|
||||
t.Errorf("first alert should win, got type %s", alert.Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue