package changes import ( "testing" "time" "gitea.maximumdirect.net/eric/weatherreporter/internal/briefing" "gitea.maximumdirect.net/eric/weatherreporter/internal/forecast" ) func TestCompareDailyNoMeaningfulChanges(t *testing.T) { previous := dailyBriefing(60, 70, 30, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{}) current := dailyBriefing(61, 71, 35, at("2026-05-29T08:30:00Z"), nil, forecast.Indicators{}) changes, err := CompareDaily(previous, current, testThresholds()) if err != nil { t.Fatalf("CompareDaily() error = %v", err) } if len(changes) != 0 { t.Fatalf("changes = %#v, want none", changes) } } func TestCompareDailyTemperatureThreshold(t *testing.T) { previous := dailyBriefing(50, 70, 10, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{}) current := dailyBriefing(58, 79, 10, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{}) changes, err := CompareDaily(previous, current, testThresholds()) if err != nil { t.Fatalf("CompareDaily() error = %v", err) } if countType(changes, "temperature_shift") != 2 { t.Fatalf("changes = %#v, want low and high temperature changes", changes) } } func TestCompareDailyPrecipTimingShift(t *testing.T) { previous := dailyBriefing(60, 70, 60, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{}) current := dailyBriefing(60, 70, 60, at("2026-05-29T11:00:00Z"), nil, forecast.Indicators{}) changes, err := CompareDaily(previous, current, testThresholds()) if err != nil { t.Fatalf("CompareDaily() error = %v", err) } if countType(changes, "precip_timing_shift") != 1 { t.Fatalf("changes = %#v, want timing shift", changes) } } func TestCompareDailyAlertAddedAndRemoved(t *testing.T) { previous := dailyBriefing(60, 70, 10, at("2026-05-29T08:00:00Z"), []string{"Wind Advisory"}, forecast.Indicators{}) current := dailyBriefing(60, 70, 10, at("2026-05-29T08:00:00Z"), []string{"Flood Watch"}, forecast.Indicators{}) changes, err := CompareDaily(previous, current, testThresholds()) if err != nil { t.Fatalf("CompareDaily() error = %v", err) } if countType(changes, "alert_added") != 1 || countType(changes, "alert_removed") != 1 { t.Fatalf("changes = %#v, want one alert added and one removed", changes) } } func TestCompareDailyIndicatorChange(t *testing.T) { previous := dailyBriefing(60, 70, 10, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{}) current := dailyBriefing(60, 70, 10, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{Thunder: true}) changes, err := CompareDaily(previous, current, testThresholds()) if err != nil { t.Fatalf("CompareDaily() error = %v", err) } if countType(changes, "thunder_risk_change") != 1 { t.Fatalf("changes = %#v, want thunder risk change", changes) } } func dailyBriefing(low float64, high float64, precip float64, precipTime time.Time, alerts []string, indicators forecast.Indicators) briefing.Package { alertOverlaps := make([]forecast.AlertOverlap, 0, len(alerts)) for _, alert := range alerts { alertOverlaps = append(alertOverlaps, forecast.AlertOverlap{Event: alert}) } return briefing.Package{ Daily: &briefing.Daily{ BottomLine: briefing.BottomLine{ Temperature: forecast.Range{Min: &low, Max: &high}, MaxPrecipProbability: &forecast.TimedValue{ Value: precip, Time: precipTime, }, }, RelevantAlerts: alertOverlaps, Dayparts: []forecast.DaypartSummary{ {Name: "morning", Indicators: indicators}, }, }, } } func testThresholds() Thresholds { return Thresholds{ TemperatureDegrees: 5, PrecipProbabilityPoints: 20, WindGustMilesPerHour: 10, PrecipTimingShiftMinutes: 120, } } func countType(changes []Change, changeType string) int { var count int for _, change := range changes { if change.Type == changeType { count++ } } return count } func at(value string) time.Time { parsed, err := time.Parse(time.RFC3339, value) if err != nil { panic(err) } return parsed }