From 8d0bc90f9d2e0295c1acd1f5e6a0f27b6d93d78f Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 12 Jun 2026 14:08:19 +0000 Subject: [PATCH] Cover active alert text rendering --- .../inbound/httpapi/endpoints_test.go | 113 +++++++++++++++++- .../inbound/httpapi/presenter/payload_test.go | 16 +++ 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/internal/adapters/inbound/httpapi/endpoints_test.go b/internal/adapters/inbound/httpapi/endpoints_test.go index 4aca30f..034fa84 100644 --- a/internal/adapters/inbound/httpapi/endpoints_test.go +++ b/internal/adapters/inbound/httpapi/endpoints_test.go @@ -79,6 +79,46 @@ func (s *fakeService) CurrentConditions(context.Context) (*app.CurrentConditions return s.conditions, s.err } +type alertRepository struct { + alerts *model.WeatherAlertRun +} + +func (r *alertRepository) LatestObservation(context.Context) (*model.WeatherObservation, error) { + return nil, nil +} + +func (r *alertRepository) LatestHourlyForecast(context.Context) (*model.WeatherForecastRun, error) { + return nil, nil +} + +func (r *alertRepository) LatestNarrativeForecast(context.Context) (*model.WeatherForecastRun, error) { + return nil, nil +} + +func (r *alertRepository) LatestForecastDiscussion(context.Context) (*model.WeatherForecastDiscussion, error) { + return nil, nil +} + +func (r *alertRepository) LatestWeatherStoryRun(context.Context) (*model.WeatherStoryRun, error) { + return nil, nil +} + +func (r *alertRepository) LatestWeatherStory(context.Context) (*model.WeatherStory, error) { + return nil, nil +} + +func (r *alertRepository) LatestAlertRun(context.Context) (*model.WeatherAlertRun, error) { + return r.alerts, nil +} + +func (r *alertRepository) LatestConvectiveOutlookRun(context.Context) (*model.WeatherOutlookRun, error) { + return nil, nil +} + +func (r *alertRepository) CurrentConditions(context.Context, int) (*app.CurrentConditions, error) { + return nil, nil +} + func TestObservationsRejectUnknownQueryParameter(t *testing.T) { h := newHandler(t, &fakeService{}, "/observations") @@ -377,6 +417,21 @@ func TestAlertsNoDataReturnsNullEnvelopeData(t *testing.T) { } } +func TestAlertsTextRendersNoData(t *testing.T) { + h := newHandler(t, &fakeService{}, "/alerts/active") + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/alerts/active?format=text", nil) + h.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", w.Code) + } + if !strings.Contains(w.Body.String(), "No active alerts data available.") { + t.Fatalf("expected no-data alert text, got %q", w.Body.String()) + } +} + func TestAlertsTextRendersEmptyActiveSet(t *testing.T) { h := newHandler(t, &fakeService{ alerts: &model.WeatherAlertRun{ @@ -397,6 +452,62 @@ func TestAlertsTextRendersEmptyActiveSet(t *testing.T) { } } +func TestAlertsTextOmitsInactiveAlertsAfterServiceFiltering(t *testing.T) { + activeAt := time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC) + setAlertNowForTest(t, activeAt) + effective := activeAt.Add(-1 * time.Hour) + activeExpires := activeAt.Add(1 * time.Hour) + expiredAtBoundary := activeAt + repo := &alertRepository{ + alerts: &model.WeatherAlertRun{ + AsOf: activeAt, + Alerts: []model.WeatherAlert{ + { + ID: "active-alert", + Headline: "Active warning", + MessageType: "Alert", + Effective: &effective, + Expires: &activeExpires, + }, + { + ID: "expired-alert", + Headline: "Expired warning", + MessageType: "Alert", + Effective: &effective, + Expires: &expiredAtBoundary, + }, + { + ID: "canceled-alert", + Headline: "Canceled warning", + MessageType: " cancel ", + Effective: &effective, + Expires: &activeExpires, + }, + }, + }, + } + h := newHandler(t, app.NewService(repo), "/alerts/active") + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/alerts/active?format=text", nil) + h.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", w.Code) + } + body := w.Body.String() + for _, want := range []string{"Alerts: 1", "active-alert", "Active warning"} { + if !strings.Contains(body, want) { + t.Fatalf("expected %q in text body, got %q", want, body) + } + } + for _, notWant := range []string{"expired-alert", "Expired warning", "canceled-alert", "Canceled warning"} { + if strings.Contains(body, notWant) { + t.Fatalf("did not expect %q in text body, got %q", notWant, body) + } + } +} + func TestObservationUSUnitsWithXMLFormat(t *testing.T) { h := newHandler(t, &fakeService{ observation: &model.WeatherObservation{ @@ -2352,7 +2463,7 @@ func testRenderers(t *testing.T) *render.Registry { "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": "{{if .Data}}Active Alerts\nAlerts: {{len .Data.Alerts}}{{else}}No active alerts data available.{{end}}", + "alerts_active.txt.tmpl": "{{if .Data}}Active Alerts\nAlerts: {{len .Data.Alerts}}{{range .Data.Alerts}}\n{{.ID}}{{if .Headline}}\nHeadline: {{.Headline}}{{end}}{{end}}{{else}}No active alerts data available.{{end}}", "conditions_current.txt.tmpl": "Conditions text", } { tmpl, err := template.New(name).Parse(body) diff --git a/internal/adapters/inbound/httpapi/presenter/payload_test.go b/internal/adapters/inbound/httpapi/presenter/payload_test.go index d4e875b..6e01597 100644 --- a/internal/adapters/inbound/httpapi/presenter/payload_test.go +++ b/internal/adapters/inbound/httpapi/presenter/payload_test.go @@ -337,6 +337,22 @@ func TestMetricCopyAndNilHandling(t *testing.T) { } } +func TestAlertsPayloadPassThrough(t *testing.T) { + run := &model.WeatherAlertRun{ + LocationID: "stl", + AsOf: time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC), + Alerts: []model.WeatherAlert{{ + ID: "alert-1", + Headline: "Storm warning", + }}, + } + + payload := AlertsPayload(run, UnitsUS) + if payload != run { + t.Fatalf("expected alerts payload to pass through input run") + } +} + func TestCurrentConditionsPayloadMetricAndUS(t *testing.T) { conditions := &app.CurrentConditions{ TemperatureC: float64Ptr(20),