test: Add tests for calculateTrimmedBaseline and createOrUpdateNodeAlert
- calculateTrimmedBaseline: 8 test cases covering sample count thresholds, trimmed mean calculation, median fallback logic, odd-length arrays - createOrUpdateNodeAlert: 2 test cases for new alert creation and existing alert updates Coverage: calculateTrimmedBaseline 0%→96.9%, createOrUpdateNodeAlert 0%→100% Package coverage: 59.6%→61.3%
This commit is contained in:
parent
34b4a0d50c
commit
b7c08ca6e1
1 changed files with 244 additions and 0 deletions
|
|
@ -6602,3 +6602,247 @@ func TestCheckPMGOffline(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCalculateTrimmedBaseline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("less than 12 samples returns untrustworthy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
samples := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
|
||||
baseline, trustworthy := calculateTrimmedBaseline(samples)
|
||||
if trustworthy {
|
||||
t.Error("expected untrustworthy with less than 12 samples")
|
||||
}
|
||||
if baseline != 0 {
|
||||
t.Errorf("expected baseline 0, got %f", baseline)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty samples returns untrustworthy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
samples := []float64{}
|
||||
baseline, trustworthy := calculateTrimmedBaseline(samples)
|
||||
if trustworthy {
|
||||
t.Error("expected untrustworthy with empty samples")
|
||||
}
|
||||
if baseline != 0 {
|
||||
t.Errorf("expected baseline 0, got %f", baseline)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("12-23 samples uses simple mean", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// 12 samples summing to 78
|
||||
samples := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
||||
baseline, trustworthy := calculateTrimmedBaseline(samples)
|
||||
if !trustworthy {
|
||||
t.Error("expected trustworthy with 12 samples")
|
||||
}
|
||||
// Mean of 1-12 is (1+2+...+12)/12 = 78/12 = 6.5
|
||||
if baseline != 6.5 {
|
||||
t.Errorf("expected baseline 6.5, got %f", baseline)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("24+ samples uses trimmed mean", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// 24 identical values - trimmed mean should equal value
|
||||
samples := make([]float64, 24)
|
||||
for i := range samples {
|
||||
samples[i] = 10.0
|
||||
}
|
||||
baseline, trustworthy := calculateTrimmedBaseline(samples)
|
||||
if !trustworthy {
|
||||
t.Error("expected trustworthy with 24 samples")
|
||||
}
|
||||
if baseline != 10.0 {
|
||||
t.Errorf("expected baseline 10.0, got %f", baseline)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("24+ samples falls back to median when diff > 40%", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Create samples where trimmed mean differs significantly from median
|
||||
// Mostly 10s with some extreme outliers that survive trimming
|
||||
samples := make([]float64, 24)
|
||||
for i := range samples {
|
||||
if i < 4 {
|
||||
samples[i] = 100.0 // Extreme high values
|
||||
} else {
|
||||
samples[i] = 10.0 // Normal values
|
||||
}
|
||||
}
|
||||
// After sorting: 10,10,...,10,100,100,100,100
|
||||
// Median is 10 (middle values are 10s)
|
||||
// Trimmed mean (drop 2 highest and 2 lowest): still has 2 100s
|
||||
// So trimmed mean > median * 1.4, should fall back to median
|
||||
baseline, trustworthy := calculateTrimmedBaseline(samples)
|
||||
if !trustworthy {
|
||||
t.Error("expected trustworthy")
|
||||
}
|
||||
// Should use median (10) due to large diff
|
||||
if baseline != 10.0 {
|
||||
t.Errorf("expected baseline 10.0 (median fallback), got %f", baseline)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("24+ samples uses trimmed mean when diff <= 40%", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Sequential values with minimal outlier effect
|
||||
samples := make([]float64, 24)
|
||||
for i := range samples {
|
||||
samples[i] = float64(i + 1) // 1,2,3,...,24
|
||||
}
|
||||
baseline, trustworthy := calculateTrimmedBaseline(samples)
|
||||
if !trustworthy {
|
||||
t.Error("expected trustworthy")
|
||||
}
|
||||
// Median of 1-24 is (12+13)/2 = 12.5
|
||||
// Trimmed mean of 3-22 is (3+4+...+22)/20 = 250/20 = 12.5
|
||||
// Both are close, should use trimmed mean
|
||||
if baseline != 12.5 {
|
||||
t.Errorf("expected baseline 12.5, got %f", baseline)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("odd length array uses middle element for median", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// 25 samples: an odd-length array
|
||||
samples := make([]float64, 25)
|
||||
for i := range samples {
|
||||
samples[i] = float64(i + 1) // 1,2,3,...,25
|
||||
}
|
||||
baseline, trustworthy := calculateTrimmedBaseline(samples)
|
||||
if !trustworthy {
|
||||
t.Error("expected trustworthy")
|
||||
}
|
||||
// Median of sorted 1-25 is the 13th element = 13
|
||||
// Trimmed mean excludes top/bottom 2: 3..23 = 21 elements, sum = (3+23)*21/2 = 273, mean = 13
|
||||
// Both are 13, diff is 0%, should use trimmed mean = 13
|
||||
if baseline != 13.0 {
|
||||
t.Errorf("expected baseline 13.0, got %f", baseline)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("trimmed mean less than median triggers diff calculation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Create samples where trimmed mean < median but within 40%
|
||||
// High outliers at top (excluded by trim), low values in middle
|
||||
samples := make([]float64, 24)
|
||||
// First 2 (will be trimmed): very low
|
||||
samples[0], samples[1] = 1, 2
|
||||
// Middle 20: mostly 50 but some variance
|
||||
for i := 2; i < 22; i++ {
|
||||
samples[i] = 50.0
|
||||
}
|
||||
// Last 2 (will be trimmed): very high
|
||||
samples[22], samples[23] = 100, 200
|
||||
|
||||
baseline, trustworthy := calculateTrimmedBaseline(samples)
|
||||
if !trustworthy {
|
||||
t.Error("expected trustworthy")
|
||||
}
|
||||
// After sorting: 1, 2, 50x20, 100, 200
|
||||
// Median of even array: (50+50)/2 = 50
|
||||
// Trimmed mean: 50x20/20 = 50
|
||||
// Should return 50
|
||||
if baseline != 50.0 {
|
||||
t.Errorf("expected baseline 50.0, got %f", baseline)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateOrUpdateNodeAlert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("creates new alert", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager()
|
||||
|
||||
pmg := models.PMGInstance{ID: "pmg1", Name: "PMG 1"}
|
||||
m.createOrUpdateNodeAlert(
|
||||
"pmg1-node-queue",
|
||||
pmg,
|
||||
"mail-node1",
|
||||
"pmg-node-queue",
|
||||
AlertLevelWarning,
|
||||
100,
|
||||
50,
|
||||
"Queue depth high",
|
||||
)
|
||||
|
||||
m.mu.RLock()
|
||||
alert := m.activeAlerts["pmg1-node-queue"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if alert == nil {
|
||||
t.Fatal("expected alert to be created")
|
||||
}
|
||||
if alert.Type != "pmg-node-queue" {
|
||||
t.Errorf("expected type pmg-node-queue, got %s", alert.Type)
|
||||
}
|
||||
if alert.Level != AlertLevelWarning {
|
||||
t.Errorf("expected warning level, got %s", alert.Level)
|
||||
}
|
||||
if alert.Value != 100 {
|
||||
t.Errorf("expected value 100, got %f", alert.Value)
|
||||
}
|
||||
if alert.Threshold != 50 {
|
||||
t.Errorf("expected threshold 50, got %f", alert.Threshold)
|
||||
}
|
||||
if alert.Node != "mail-node1" {
|
||||
t.Errorf("expected node mail-node1, got %s", alert.Node)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("updates existing alert", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager()
|
||||
oldTime := time.Now().Add(-1 * time.Hour)
|
||||
m.mu.Lock()
|
||||
m.activeAlerts["pmg1-node-queue"] = &Alert{
|
||||
ID: "pmg1-node-queue",
|
||||
Value: 50,
|
||||
Threshold: 40,
|
||||
Level: AlertLevelWarning,
|
||||
Message: "Old message",
|
||||
LastSeen: oldTime,
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
pmg := models.PMGInstance{ID: "pmg1", Name: "PMG 1"}
|
||||
m.createOrUpdateNodeAlert(
|
||||
"pmg1-node-queue",
|
||||
pmg,
|
||||
"mail-node1",
|
||||
"pmg-node-queue",
|
||||
AlertLevelCritical,
|
||||
200,
|
||||
100,
|
||||
"New message",
|
||||
)
|
||||
|
||||
m.mu.RLock()
|
||||
alert := m.activeAlerts["pmg1-node-queue"]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if alert == nil {
|
||||
t.Fatal("expected alert to exist")
|
||||
}
|
||||
if alert.Value != 200 {
|
||||
t.Errorf("expected value 200, got %f", alert.Value)
|
||||
}
|
||||
if alert.Threshold != 100 {
|
||||
t.Errorf("expected threshold 100, got %f", alert.Threshold)
|
||||
}
|
||||
if alert.Level != AlertLevelCritical {
|
||||
t.Errorf("expected critical level, got %s", alert.Level)
|
||||
}
|
||||
if alert.Message != "New message" {
|
||||
t.Errorf("expected 'New message', got %s", alert.Message)
|
||||
}
|
||||
if !alert.LastSeen.After(oldTime) {
|
||||
t.Error("expected LastSeen to be updated")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue