Cover outlook discussions in endpoint responses

This commit is contained in:
2026-06-12 13:02:12 +00:00
parent 1f2459923f
commit beeaeeaecb

View File

@@ -1314,10 +1314,9 @@ func TestOutlookRoutesJSONSuccess(t *testing.T) {
var payload struct {
Data struct {
LocationID string `json:"locationId"`
Outlooks []struct {
ID string `json:"id"`
} `json:"outlooks"`
LocationID string `json:"locationId"`
Outlooks []map[string]any `json:"outlooks"`
Discussions []outlookDiscussionCheck `json:"discussions"`
} `json:"data"`
}
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
@@ -1326,9 +1325,20 @@ func TestOutlookRoutesJSONSuccess(t *testing.T) {
if payload.Data.LocationID != "stl" {
t.Fatalf("expected locationId stl, got %q", payload.Data.LocationID)
}
if len(payload.Data.Outlooks) != 1 || payload.Data.Outlooks[0].ID != "cat-1" {
if len(payload.Data.Outlooks) != 1 || payload.Data.Outlooks[0]["id"] != "cat-1" {
t.Fatalf("unexpected outlooks payload: %+v", payload.Data.Outlooks)
}
for _, field := range []string{"headline", "summary", "discussion"} {
if _, ok := payload.Data.Outlooks[0][field]; ok {
t.Fatalf("expected outlook polygon to omit %s, got %+v", field, payload.Data.Outlooks[0])
}
}
if len(payload.Data.Discussions) != 1 {
t.Fatalf("expected one discussion, got %+v", payload.Data.Discussions)
}
if payload.Data.Discussions[0].Day != 1 || payload.Data.Discussions[0].Headline != "Day 1 headline" {
t.Fatalf("unexpected discussions payload: %+v", payload.Data.Discussions)
}
})
}
}
@@ -1369,7 +1379,8 @@ func TestOutlookTextResponseUsesTemplate(t *testing.T) {
t.Fatalf("expected text/plain content type, got %q", w.Header().Get("Content-Type"))
}
body := w.Body.String()
if !strings.Contains(body, "Convective Outlook") || !strings.Contains(body, "Outlooks: 1") {
if !strings.Contains(body, "Convective Outlook") || !strings.Contains(body, "Outlooks: 1") ||
!strings.Contains(body, "Discussions: 1") || !strings.Contains(body, "Day 1 discussion") {
t.Fatalf("expected outlook text template body, got %q", body)
}
}
@@ -1390,11 +1401,15 @@ func TestOutlookXMLResponseRenders(t *testing.T) {
if !strings.Contains(w.Body.String(), "<LocationID>stl</LocationID>") {
t.Fatalf("expected outlook XML payload, got %q", w.Body.String())
}
if !strings.Contains(w.Body.String(), "<Discussions>") || !strings.Contains(w.Body.String(), "<Headline>Day 1 headline</Headline>") {
t.Fatalf("expected outlook XML discussions, got %q", w.Body.String())
}
}
func TestOutlookFilteredNoMatchReturnsEmptyOutlooks(t *testing.T) {
run := testOutlookRun()
run.Outlooks = []model.WeatherOutlook{}
run.Discussions = []model.WeatherOutlookDiscussion{}
h := newHandler(t, &fakeService{outlookRun: run}, "/outlooks/convective")
w := httptest.NewRecorder()
@@ -1407,8 +1422,9 @@ func TestOutlookFilteredNoMatchReturnsEmptyOutlooks(t *testing.T) {
var payload struct {
Data struct {
LocationID string `json:"locationId"`
Outlooks []model.WeatherOutlook `json:"outlooks"`
LocationID string `json:"locationId"`
Outlooks []model.WeatherOutlook `json:"outlooks"`
Discussions []model.WeatherOutlookDiscussion `json:"discussions"`
} `json:"data"`
}
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
@@ -1420,6 +1436,39 @@ func TestOutlookFilteredNoMatchReturnsEmptyOutlooks(t *testing.T) {
if payload.Data.Outlooks == nil || len(payload.Data.Outlooks) != 0 {
t.Fatalf("expected empty outlooks slice, got %+v", payload.Data.Outlooks)
}
if payload.Data.Discussions == nil || len(payload.Data.Discussions) != 0 {
t.Fatalf("expected empty discussions slice, got %+v", payload.Data.Discussions)
}
}
func TestOutlookTimezoneQuery(t *testing.T) {
h := newHandler(t, &fakeService{outlookRun: testOutlookRun()}, "/outlooks/convective")
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/outlooks/convective?tz=CDT", nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var payload outlookTimePayload
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode outlook payload: %v", err)
}
assertOffsetSeconds(t, payload.Data.AsOf, -5*60*60)
assertOffsetSeconds(t, *payload.Data.IssuedAt, -5*60*60)
if len(payload.Data.Outlooks) != 1 {
t.Fatalf("expected one outlook, got %+v", payload.Data.Outlooks)
}
assertOffsetSeconds(t, payload.Data.Outlooks[0].ValidFrom, -5*60*60)
assertOffsetSeconds(t, payload.Data.Outlooks[0].ValidTo, -5*60*60)
assertOffsetSeconds(t, payload.Data.Outlooks[0].IssuedAt, -5*60*60)
assertOffsetSeconds(t, payload.Data.Outlooks[0].ExpiresAt, -5*60*60)
if len(payload.Data.Discussions) != 1 || payload.Data.Discussions[0].UpdatedAt == nil {
t.Fatalf("expected one discussion with updatedAt, got %+v", payload.Data.Discussions)
}
assertOffsetSeconds(t, *payload.Data.Discussions[0].UpdatedAt, -5*60*60)
}
func TestOutlookQueryParamsConstructFilter(t *testing.T) {
@@ -2211,7 +2260,7 @@ func testRenderers(t *testing.T) *render.Registry {
"discussion_long_term.txt.tmpl": "Forecast Discussion Long Term",
"forecast_hourly.txt.tmpl": "Forecast text",
"forecast_narrative.txt.tmpl": "Narrative Forecast",
"outlooks_convective.txt.tmpl": "Convective Outlook\n{{if .Data}}Outlooks: {{len .Data.Outlooks}}{{else}}No convective outlook data available.{{end}}",
"outlooks_convective.txt.tmpl": "Convective Outlook\n{{if .Data}}Outlooks: {{len .Data.Outlooks}}\nDiscussions: {{len .Data.Discussions}}{{range .Data.Discussions}}\nDiscussion: {{.Discussion}}{{end}}{{else}}No convective outlook data available.{{end}}",
"weatherstories.txt.tmpl": "Weather Stories",
"weatherstories_latest.txt.tmpl": "Latest Weather Story",
"alerts_active.txt.tmpl": "Alerts text",
@@ -2250,6 +2299,7 @@ func setOutlookNowForTest(t *testing.T, now time.Time) {
func testOutlookRun() *model.WeatherOutlookRun {
issuedAt := time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC)
discussionUpdatedAt := issuedAt.Add(30 * time.Minute)
return &model.WeatherOutlookRun{
LocationID: "stl",
LocationName: "St. Louis",
@@ -2270,6 +2320,13 @@ func testOutlookRun() *model.WeatherOutlookRun {
ContainsLocation: true,
Geometry: []byte(`{"type":"Point","coordinates":[-90.2,38.6]}`),
}},
Discussions: []model.WeatherOutlookDiscussion{{
Day: 1,
Headline: "Day 1 headline",
Summary: "Day 1 summary",
Discussion: "Day 1 discussion",
UpdatedAt: &discussionUpdatedAt,
}},
}
}
@@ -2343,6 +2400,27 @@ type discussionFocusedTimePayload struct {
} `json:"data"`
}
type outlookDiscussionCheck struct {
Day int `json:"day"`
Headline string `json:"headline"`
}
type outlookTimePayload struct {
Data struct {
AsOf time.Time `json:"asOf"`
IssuedAt *time.Time `json:"issuedAt"`
Outlooks []struct {
ValidFrom time.Time `json:"validFrom"`
ValidTo time.Time `json:"validTo"`
IssuedAt time.Time `json:"issuedAt"`
ExpiresAt time.Time `json:"expiresAt"`
} `json:"outlooks"`
Discussions []struct {
UpdatedAt *time.Time `json:"updatedAt"`
} `json:"discussions"`
} `json:"data"`
}
func decodeForecastTimePayload(t *testing.T, w *httptest.ResponseRecorder) forecastTimePayload {
t.Helper()