gomcp/internal/domain/soc/anomaly_test.go

101 lines
2.4 KiB
Go

package soc
import (
"testing"
)
func TestAnomalyDetector_NoAlertDuringWarmup(t *testing.T) {
d := NewAnomalyDetector()
// First 10 observations are warmup — should never alert
for i := 0; i < 10; i++ {
alert := d.Observe("cpu", 50.0)
if alert != nil {
t.Fatalf("should not alert during warmup, got alert at observation %d", i)
}
}
}
func TestAnomalyDetector_NormalValues(t *testing.T) {
d := NewAnomalyDetector()
// Build baseline with consistent values
for i := 0; i < 20; i++ {
d.Observe("rps", 100.0+float64(i%3)) // values: 100, 101, 102
}
// Normal value should not trigger
alert := d.Observe("rps", 103.0)
if alert != nil {
t.Fatal("normal value should not trigger anomaly")
}
}
func TestAnomalyDetector_ExtremeValue(t *testing.T) {
d := NewAnomalyDetector()
// Build tight baseline
for i := 0; i < 30; i++ {
d.Observe("latency_ms", 10.0)
}
// Extreme spike should trigger
alert := d.Observe("latency_ms", 1000.0)
if alert == nil {
t.Fatal("extreme value should trigger anomaly")
}
if alert.Severity != "CRITICAL" {
t.Fatalf("extreme deviation should be CRITICAL, got %s", alert.Severity)
}
if alert.ZScore < 3.0 {
t.Fatalf("Z-score should be >= 3.0, got %f", alert.ZScore)
}
}
func TestAnomalyDetector_CustomThreshold(t *testing.T) {
d := NewAnomalyDetector()
d.SetThreshold(2.0) // More sensitive
for i := 0; i < 30; i++ {
d.Observe("mem", 50.0)
}
// Moderate deviation should trigger with lower threshold
alert := d.Observe("mem", 80.0)
if alert == nil {
t.Fatal("moderate deviation should trigger with Z=2.0 threshold")
}
}
func TestAnomalyDetector_Baselines(t *testing.T) {
d := NewAnomalyDetector()
d.Observe("metric_a", 10.0)
d.Observe("metric_b", 20.0)
baselines := d.Baselines()
if len(baselines) != 2 {
t.Fatalf("expected 2 baselines, got %d", len(baselines))
}
}
func TestAnomalyDetector_Alerts(t *testing.T) {
d := NewAnomalyDetector()
for i := 0; i < 30; i++ {
d.Observe("test", 10.0)
}
d.Observe("test", 10000.0) // trigger alert
alerts := d.Alerts(10)
if len(alerts) != 1 {
t.Fatalf("expected 1 alert, got %d", len(alerts))
}
}
func TestAnomalyDetector_Stats(t *testing.T) {
d := NewAnomalyDetector()
d.Observe("x", 1.0)
stats := d.Stats()
if stats["metrics_tracked"].(int) != 1 {
t.Fatal("should track 1 metric")
}
if stats["z_threshold"].(float64) != 3.0 {
t.Fatal("default threshold should be 3.0")
}
}