package context import ( "testing" "time" ) func TestComputeTrend_Growing(t *testing.T) { // Create growing data (10% per day) now := time.Now() points := make([]MetricPoint, 24) for i := 0; i < 24; i++ { // 10% per day = ~0.417% per hour points[i] = MetricPoint{ Value: 50 + float64(i)*0.417, Timestamp: now.Add(time.Duration(-24+i) * time.Hour), } } trend := ComputeTrend(points, "memory", 24*time.Hour) if trend.Direction != TrendGrowing { t.Errorf("Expected TrendGrowing, got %s", trend.Direction) } // Rate should be ~10% per day if trend.RatePerDay < 8 || trend.RatePerDay > 12 { t.Errorf("Expected rate ~10/day, got %.2f", trend.RatePerDay) } if trend.DataPoints != 24 { t.Errorf("Expected 24 data points, got %d", trend.DataPoints) } } func TestComputeTrend_Stable(t *testing.T) { // Create stable data with small fluctuations now := time.Now() points := make([]MetricPoint, 24) for i := 0; i < 24; i++ { // Small random-looking variation around 50%, but no trend offset := float64(i%3 - 1) * 0.2 points[i] = MetricPoint{ Value: 50 + offset, Timestamp: now.Add(time.Duration(-24+i) * time.Hour), } } trend := ComputeTrend(points, "cpu", 24*time.Hour) if trend.Direction != TrendStable { t.Errorf("Expected TrendStable, got %s (rate: %.4f/hr)", trend.Direction, trend.RatePerHour) } } func TestComputeTrend_Declining(t *testing.T) { // Create declining data now := time.Now() points := make([]MetricPoint, 24) for i := 0; i < 24; i++ { points[i] = MetricPoint{ Value: 80 - float64(i)*0.5, // -12% per day Timestamp: now.Add(time.Duration(-24+i) * time.Hour), } } trend := ComputeTrend(points, "disk", 24*time.Hour) if trend.Direction != TrendDeclining { t.Errorf("Expected TrendDeclining, got %s", trend.Direction) } } func TestComputeTrend_Volatile(t *testing.T) { // Create volatile data with high variance now := time.Now() points := make([]MetricPoint, 24) for i := 0; i < 24; i++ { // Alternating high/low values value := 50.0 if i%2 == 0 { value = 80.0 } else { value = 20.0 } points[i] = MetricPoint{ Value: value, Timestamp: now.Add(time.Duration(-24+i) * time.Hour), } } trend := ComputeTrend(points, "cpu", 24*time.Hour) if trend.Direction != TrendVolatile { t.Errorf("Expected TrendVolatile, got %s (stddev: %.2f, mean: %.2f)", trend.Direction, trend.StdDev, trend.Average) } } func TestComputeTrend_InsufficientData(t *testing.T) { // Only one data point points := []MetricPoint{ {Value: 50, Timestamp: time.Now()}, } trend := ComputeTrend(points, "memory", 24*time.Hour) if trend.Confidence != 0 { t.Errorf("Expected 0 confidence with insufficient data, got %.2f", trend.Confidence) } } func TestLinearRegression_Perfect(t *testing.T) { // Perfect linear data: y = 2x + 10 now := time.Now() points := make([]MetricPoint, 10) for i := 0; i < 10; i++ { points[i] = MetricPoint{ Value: 10 + float64(i)*2, Timestamp: now.Add(time.Duration(i) * time.Second), } } result := linearRegression(points) // Slope should be 2 per second if result.Slope < 1.9 || result.Slope > 2.1 { t.Errorf("Expected slope ~2, got %.4f", result.Slope) } // R² should be 1 (perfect fit) if result.R2 < 0.99 { t.Errorf("Expected R² ~1, got %.4f", result.R2) } } func TestComputePercentiles(t *testing.T) { now := time.Now() // Create 100 points with values 1-100 points := make([]MetricPoint, 100) for i := 0; i < 100; i++ { points[i] = MetricPoint{ Value: float64(i + 1), Timestamp: now.Add(time.Duration(i) * time.Second), } } percentiles := ComputePercentiles(points, 5, 50, 95) // P5 should be ~5 if percentiles[5] < 4 || percentiles[5] > 6 { t.Errorf("Expected P5 ~5, got %.2f", percentiles[5]) } // P50 should be ~50 if percentiles[50] < 49 || percentiles[50] > 51 { t.Errorf("Expected P50 ~50, got %.2f", percentiles[50]) } // P95 should be ~95 if percentiles[95] < 94 || percentiles[95] > 96 { t.Errorf("Expected P95 ~95, got %.2f", percentiles[95]) } } func TestTrendSummary(t *testing.T) { tests := []struct { name string trend Trend expected string }{ { name: "growing fast", trend: Trend{ Direction: TrendGrowing, RatePerDay: 5.5, RatePerHour: 0.23, DataPoints: 24, }, expected: "growing 5.5/day", }, { name: "growing slow", trend: Trend{ Direction: TrendGrowing, RatePerDay: 0.5, RatePerHour: 0.02, DataPoints: 24, }, expected: "growing 0.02/hr", }, { name: "stable", trend: Trend{ Direction: TrendStable, DataPoints: 24, }, expected: "stable", }, { name: "volatile", trend: Trend{ Direction: TrendVolatile, DataPoints: 24, }, expected: "volatile", }, { name: "insufficient data", trend: Trend{ DataPoints: 1, }, expected: "insufficient data", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := TrendSummary(tt.trend) if result != tt.expected { t.Errorf("Expected %q, got %q", tt.expected, result) } }) } } func TestComputeStats(t *testing.T) { points := []MetricPoint{ {Value: 10}, {Value: 20}, {Value: 30}, {Value: 40}, {Value: 50}, } stats := computeStats(points) if stats.Count != 5 { t.Errorf("Expected count 5, got %d", stats.Count) } if stats.Min != 10 { t.Errorf("Expected min 10, got %.2f", stats.Min) } if stats.Max != 50 { t.Errorf("Expected max 50, got %.2f", stats.Max) } if stats.Mean != 30 { t.Errorf("Expected mean 30, got %.2f", stats.Mean) } }