package nws import ( "os" "path/filepath" "strings" "testing" "time" ) func TestParseForecastDiscussionHTMLParsesExpectedFields(t *testing.T) { raw := loadForecastDiscussionSampleHTML(t) got, err := ParseForecastDiscussionHTML(raw) if err != nil { t.Fatalf("ParseForecastDiscussionHTML() error = %v", err) } if got.OfficeID != "LSX" { t.Fatalf("OfficeID = %q, want LSX", got.OfficeID) } if got.OfficeName != "National Weather Service Saint Louis MO" { t.Fatalf("OfficeName = %q", got.OfficeName) } if got.Product != "afd" { t.Fatalf("Product = %q, want afd", got.Product) } wantIssuedAt := time.Date(2026, 3, 28, 19, 24, 0, 0, time.UTC) if !got.IssuedAt.Equal(wantIssuedAt) { t.Fatalf("IssuedAt = %s, want %s", got.IssuedAt.Format(time.RFC3339), wantIssuedAt.Format(time.RFC3339)) } wantUpdatedAt := time.Date(2026, 3, 28, 20, 29, 47, 0, time.UTC) if got.UpdatedAt == nil || !got.UpdatedAt.Equal(wantUpdatedAt) { t.Fatalf("UpdatedAt = %v, want %s", got.UpdatedAt, wantUpdatedAt.Format(time.RFC3339)) } wantMessages := []string{ "Elevated fire danger conditions are expected across a broad area tomorrow afternoon due to breezy southwest winds and low humidity.", "Very warm temperatures are expected once again Monday and Tuesday, with highs well into the 80s.", "A cold front late Tuesday or early Wednesday brings our next chance of thunderstorms, followed by a cooldown and possibly more chances for rain later in the week.", } if len(got.KeyMessages) != len(wantMessages) { t.Fatalf("KeyMessages len = %d, want %d", len(got.KeyMessages), len(wantMessages)) } for i := range wantMessages { if got.KeyMessages[i] != wantMessages[i] { t.Fatalf("KeyMessages[%d] = %q, want %q", i, got.KeyMessages[i], wantMessages[i]) } } if got.ShortTerm == nil { t.Fatalf("ShortTerm is nil") } if got.ShortTerm.Qualifier != "(Through Late Sunday Night)" { t.Fatalf("ShortTerm.Qualifier = %q", got.ShortTerm.Qualifier) } if got.ShortTerm.IssuedAt == nil || !got.ShortTerm.IssuedAt.Equal(time.Date(2026, 3, 28, 19, 19, 0, 0, time.UTC)) { t.Fatalf("ShortTerm.IssuedAt = %v", got.ShortTerm.IssuedAt) } if !strings.Contains(got.ShortTerm.Text, "After a chilly morning") { t.Fatalf("ShortTerm.Text missing expected prose: %q", got.ShortTerm.Text) } if strings.Contains(got.ShortTerm.Text, "BRC") { t.Fatalf("ShortTerm.Text should not include signature: %q", got.ShortTerm.Text) } if strings.Contains(got.ShortTerm.Text, "\n\n\n") { t.Fatalf("ShortTerm.Text contains unexpected paragraph breaks: %q", got.ShortTerm.Text) } if got.LongTerm == nil { t.Fatalf("LongTerm is nil") } if got.LongTerm.Qualifier != "(Monday through Next Saturday)" { t.Fatalf("LongTerm.Qualifier = %q", got.LongTerm.Qualifier) } if got.LongTerm.IssuedAt == nil || !got.LongTerm.IssuedAt.Equal(time.Date(2026, 3, 28, 19, 19, 0, 0, time.UTC)) { t.Fatalf("LongTerm.IssuedAt = %v", got.LongTerm.IssuedAt) } if !strings.Contains(got.LongTerm.Text, "The peak of the warmth arrives Monday and Tuesday") { t.Fatalf("LongTerm.Text missing expected prose: %q", got.LongTerm.Text) } if strings.Contains(got.LongTerm.Text, "AVIATION") || strings.Contains(got.LongTerm.Text, "WATCHES/WARNINGS/ADVISORIES") { t.Fatalf("LongTerm.Text includes content from other sections: %q", got.LongTerm.Text) } } func TestParseForecastDiscussionHTMLMissingPreBlock(t *testing.T) { _, err := ParseForecastDiscussionHTML("