159 lines
6.9 KiB
Go
159 lines
6.9 KiB
Go
package briefing
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
|
)
|
|
|
|
func TestStormBriefingWithActiveAlert(t *testing.T) {
|
|
location := mustLocation(t)
|
|
resolved, err := report.Resolve(report.Storm, report.ResolveRequest{
|
|
Now: mustParse("2026-05-29T05:00:00-05:00"),
|
|
Location: location,
|
|
StormStart: mustParse("2026-05-29T06:00:00-05:00"),
|
|
StormEnd: mustParse("2026-05-29T12:00:00-05:00"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolve storm: %v", err)
|
|
}
|
|
precip := 80.0
|
|
gust := 42.0
|
|
bundle := &forecast.Bundle{
|
|
Hourly: &forecast.ForecastRun{Periods: []forecast.ForecastPeriod{{
|
|
StartTime: mustParse("2026-05-29T07:00:00-05:00"),
|
|
EndTime: mustParse("2026-05-29T08:00:00-05:00"),
|
|
TextDescription: "Severe thunderstorms and gusty wind",
|
|
ProbabilityOfPrecipitationPercent: &precip,
|
|
WindGustMph: &gust,
|
|
}}},
|
|
Daily: &forecast.ForecastRun{Periods: []forecast.ForecastPeriod{{
|
|
StartTime: mustParse("2026-05-29T06:00:00-05:00"),
|
|
EndTime: mustParse("2026-05-29T18:00:00-05:00"),
|
|
TextDescription: "Storms likely.",
|
|
}}},
|
|
Narrative: &forecast.ForecastRun{Periods: []forecast.ForecastPeriod{{
|
|
StartTime: mustParse("2026-05-29T06:00:00-05:00"),
|
|
EndTime: mustParse("2026-05-29T18:00:00-05:00"),
|
|
TextDescription: "Damaging wind possible in stronger storms.",
|
|
}}},
|
|
Alerts: &forecast.AlertRun{Alerts: []json.RawMessage{
|
|
json.RawMessage(`{"event":"Severe Thunderstorm Warning","headline":"Severe storms near Testville","severity":"Severe","effective":"2026-05-29T06:30:00-05:00","expires":"2026-05-29T08:30:00-05:00"}`),
|
|
}},
|
|
Discussion: &forecast.Discussion{
|
|
Product: "discussion",
|
|
KeyMessages: []string{"Storms may intensify quickly."},
|
|
ShortTerm: &forecast.DiscussionSection{Text: "Short-term storm coverage peaks this morning."},
|
|
LongTerm: &forecast.DiscussionSection{Text: "Long-term pattern stays unsettled after the event."},
|
|
},
|
|
WeatherStory: &forecast.WeatherStory{Raw: json.RawMessage(`{"headline":"Storm risk"}`)},
|
|
Sources: []forecast.Source{{Name: "hourly", FetchedAt: time.Now()}},
|
|
}
|
|
|
|
pkg, err := BuildStorm(BuildContext{Resolved: resolved, Bundle: bundle, Units: "us", Timezone: "America/Chicago"})
|
|
if err != nil {
|
|
t.Fatalf("BuildStorm() error = %v", err)
|
|
}
|
|
|
|
if pkg.Metadata.ReportID != report.Storm {
|
|
t.Fatalf("ReportID = %q, want storm", pkg.Metadata.ReportID)
|
|
}
|
|
if pkg.Storm == nil {
|
|
t.Fatal("Storm = nil")
|
|
}
|
|
if len(pkg.Storm.RelevantAlerts) != 1 || len(pkg.Storm.EventHeadlines) != 1 {
|
|
t.Fatalf("alerts/headlines = %#v/%#v, want alert inputs", pkg.Storm.RelevantAlerts, pkg.Storm.EventHeadlines)
|
|
}
|
|
if !pkg.Storm.TimingWindow.Start.Equal(resolved.ValidPeriod.Start) || !pkg.Storm.TimingWindow.End.Equal(resolved.ValidPeriod.End) {
|
|
t.Fatalf("TimingWindow = %#v, want resolved valid period %#v", pkg.Storm.TimingWindow, resolved.ValidPeriod)
|
|
}
|
|
if !strings.Contains(strings.Join(pkg.Storm.Hazards, ","), "Severe Thunderstorm Warning") {
|
|
t.Fatalf("Hazards = %#v, want alert event", pkg.Storm.Hazards)
|
|
}
|
|
if len(pkg.Storm.HourlyPeriods) != 1 || len(pkg.Storm.DailyPeriods) != 1 || len(pkg.Storm.NarrativePeriods) != 1 {
|
|
t.Fatalf("selected periods hourly/daily/narrative = %d/%d/%d, want selected source periods", len(pkg.Storm.HourlyPeriods), len(pkg.Storm.DailyPeriods), len(pkg.Storm.NarrativePeriods))
|
|
}
|
|
if pkg.Storm.WeatherStory == nil {
|
|
t.Fatal("WeatherStory = nil, want available story context")
|
|
}
|
|
if pkg.Storm.Discussion.ShortTerm != "Short-term storm coverage peaks this morning." {
|
|
t.Fatalf("Discussion.ShortTerm = %q, want short-term AFD narrative", pkg.Storm.Discussion.ShortTerm)
|
|
}
|
|
if pkg.Storm.Discussion.LongTerm != "Long-term pattern stays unsettled after the event." {
|
|
t.Fatalf("Discussion.LongTerm = %q, want long-term AFD narrative", pkg.Storm.Discussion.LongTerm)
|
|
}
|
|
if len(pkg.Storm.WhatToWatchNext) == 0 {
|
|
t.Fatal("WhatToWatchNext length = 0, want watch inputs")
|
|
}
|
|
}
|
|
|
|
func TestStormBriefingWithDiscussionButNoAlert(t *testing.T) {
|
|
location := mustLocation(t)
|
|
resolved, err := report.Resolve(report.Storm, report.ResolveRequest{
|
|
Now: mustParse("2026-05-29T05:00:00-05:00"),
|
|
Location: location,
|
|
StormStart: mustParse("2026-05-29T06:00:00-05:00"),
|
|
StormEnd: mustParse("2026-05-29T12:00:00-05:00"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolve storm: %v", err)
|
|
}
|
|
bundle := &forecast.Bundle{
|
|
Hourly: &forecast.ForecastRun{Periods: []forecast.ForecastPeriod{{StartTime: mustParse("2026-05-29T07:00:00-05:00"), EndTime: mustParse("2026-05-29T08:00:00-05:00"), TextDescription: "Showers"}}},
|
|
Alerts: &forecast.AlertRun{},
|
|
Discussion: &forecast.Discussion{Product: "discussion", KeyMessages: []string{"Confidence is moderate."}},
|
|
Sources: []forecast.Source{{Name: "hourly", FetchedAt: time.Now()}},
|
|
}
|
|
|
|
pkg, err := BuildStorm(BuildContext{Resolved: resolved, Bundle: bundle, Units: "us", Timezone: "America/Chicago"})
|
|
if err != nil {
|
|
t.Fatalf("BuildStorm() error = %v", err)
|
|
}
|
|
|
|
if len(pkg.Storm.RelevantAlerts) != 0 {
|
|
t.Fatalf("RelevantAlerts length = %d, want 0", len(pkg.Storm.RelevantAlerts))
|
|
}
|
|
if !strings.Contains(strings.Join(pkg.Storm.EventHeadlines, " "), "No active alert") {
|
|
t.Fatalf("EventHeadlines = %#v, want no-alert fallback", pkg.Storm.EventHeadlines)
|
|
}
|
|
if !strings.Contains(strings.Join(pkg.Storm.ConfidenceInputs, " "), "Confidence is moderate") {
|
|
t.Fatalf("ConfidenceInputs = %#v, want discussion key message", pkg.Storm.ConfidenceInputs)
|
|
}
|
|
if len(pkg.Storm.MostLikelyScenario) == 0 {
|
|
t.Fatal("MostLikelyScenario length = 0, want forecast scenario inputs")
|
|
}
|
|
}
|
|
|
|
func TestStormBriefingQuietWindow(t *testing.T) {
|
|
location := mustLocation(t)
|
|
resolved, err := report.Resolve(report.Storm, report.ResolveRequest{
|
|
Now: mustParse("2026-05-29T05:00:00-05:00"),
|
|
Location: location,
|
|
StormStart: mustParse("2026-05-29T06:00:00-05:00"),
|
|
StormEnd: mustParse("2026-05-29T12:00:00-05:00"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolve storm: %v", err)
|
|
}
|
|
bundle := &forecast.Bundle{
|
|
Hourly: &forecast.ForecastRun{Periods: []forecast.ForecastPeriod{{StartTime: mustParse("2026-05-29T07:00:00-05:00"), EndTime: mustParse("2026-05-29T08:00:00-05:00"), TextDescription: "Clear"}}},
|
|
Sources: []forecast.Source{{Name: "hourly", FetchedAt: time.Now()}},
|
|
}
|
|
|
|
pkg, err := BuildStorm(BuildContext{Resolved: resolved, Bundle: bundle, Units: "us", Timezone: "America/Chicago"})
|
|
if err != nil {
|
|
t.Fatalf("BuildStorm() error = %v", err)
|
|
}
|
|
|
|
if len(pkg.Storm.Hazards) != 1 || !strings.Contains(pkg.Storm.Hazards[0], "No storm-specific") {
|
|
t.Fatalf("Hazards = %#v, want quiet hazard fallback", pkg.Storm.Hazards)
|
|
}
|
|
if !strings.Contains(strings.Join(pkg.Storm.WhatToWatchNext, " "), "new alerts") {
|
|
t.Fatalf("WhatToWatchNext = %#v, want watch fallback", pkg.Storm.WhatToWatchNext)
|
|
}
|
|
}
|