Cover outlook discussions in endpoint responses
This commit is contained in:
@@ -1314,10 +1314,9 @@ func TestOutlookRoutesJSONSuccess(t *testing.T) {
|
|||||||
|
|
||||||
var payload struct {
|
var payload struct {
|
||||||
Data struct {
|
Data struct {
|
||||||
LocationID string `json:"locationId"`
|
LocationID string `json:"locationId"`
|
||||||
Outlooks []struct {
|
Outlooks []map[string]any `json:"outlooks"`
|
||||||
ID string `json:"id"`
|
Discussions []outlookDiscussionCheck `json:"discussions"`
|
||||||
} `json:"outlooks"`
|
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||||||
@@ -1326,9 +1325,20 @@ func TestOutlookRoutesJSONSuccess(t *testing.T) {
|
|||||||
if payload.Data.LocationID != "stl" {
|
if payload.Data.LocationID != "stl" {
|
||||||
t.Fatalf("expected locationId stl, got %q", payload.Data.LocationID)
|
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)
|
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"))
|
t.Fatalf("expected text/plain content type, got %q", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
body := w.Body.String()
|
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)
|
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>") {
|
if !strings.Contains(w.Body.String(), "<LocationID>stl</LocationID>") {
|
||||||
t.Fatalf("expected outlook XML payload, got %q", w.Body.String())
|
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) {
|
func TestOutlookFilteredNoMatchReturnsEmptyOutlooks(t *testing.T) {
|
||||||
run := testOutlookRun()
|
run := testOutlookRun()
|
||||||
run.Outlooks = []model.WeatherOutlook{}
|
run.Outlooks = []model.WeatherOutlook{}
|
||||||
|
run.Discussions = []model.WeatherOutlookDiscussion{}
|
||||||
h := newHandler(t, &fakeService{outlookRun: run}, "/outlooks/convective")
|
h := newHandler(t, &fakeService{outlookRun: run}, "/outlooks/convective")
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@@ -1407,8 +1422,9 @@ func TestOutlookFilteredNoMatchReturnsEmptyOutlooks(t *testing.T) {
|
|||||||
|
|
||||||
var payload struct {
|
var payload struct {
|
||||||
Data struct {
|
Data struct {
|
||||||
LocationID string `json:"locationId"`
|
LocationID string `json:"locationId"`
|
||||||
Outlooks []model.WeatherOutlook `json:"outlooks"`
|
Outlooks []model.WeatherOutlook `json:"outlooks"`
|
||||||
|
Discussions []model.WeatherOutlookDiscussion `json:"discussions"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
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 {
|
if payload.Data.Outlooks == nil || len(payload.Data.Outlooks) != 0 {
|
||||||
t.Fatalf("expected empty outlooks slice, got %+v", payload.Data.Outlooks)
|
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) {
|
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",
|
"discussion_long_term.txt.tmpl": "Forecast Discussion Long Term",
|
||||||
"forecast_hourly.txt.tmpl": "Forecast text",
|
"forecast_hourly.txt.tmpl": "Forecast text",
|
||||||
"forecast_narrative.txt.tmpl": "Narrative Forecast",
|
"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.txt.tmpl": "Weather Stories",
|
||||||
"weatherstories_latest.txt.tmpl": "Latest Weather Story",
|
"weatherstories_latest.txt.tmpl": "Latest Weather Story",
|
||||||
"alerts_active.txt.tmpl": "Alerts text",
|
"alerts_active.txt.tmpl": "Alerts text",
|
||||||
@@ -2250,6 +2299,7 @@ func setOutlookNowForTest(t *testing.T, now time.Time) {
|
|||||||
|
|
||||||
func testOutlookRun() *model.WeatherOutlookRun {
|
func testOutlookRun() *model.WeatherOutlookRun {
|
||||||
issuedAt := time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC)
|
issuedAt := time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC)
|
||||||
|
discussionUpdatedAt := issuedAt.Add(30 * time.Minute)
|
||||||
return &model.WeatherOutlookRun{
|
return &model.WeatherOutlookRun{
|
||||||
LocationID: "stl",
|
LocationID: "stl",
|
||||||
LocationName: "St. Louis",
|
LocationName: "St. Louis",
|
||||||
@@ -2270,6 +2320,13 @@ func testOutlookRun() *model.WeatherOutlookRun {
|
|||||||
ContainsLocation: true,
|
ContainsLocation: true,
|
||||||
Geometry: []byte(`{"type":"Point","coordinates":[-90.2,38.6]}`),
|
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"`
|
} `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 {
|
func decodeForecastTimePayload(t *testing.T, w *httptest.ResponseRecorder) forecastTimePayload {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user