Add US unit support for weather observations and forecasts
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful

This commit is contained in:
2026-03-19 22:36:15 -05:00
parent 6e8adcc9cc
commit 26a52f8c44
5 changed files with 460 additions and 18 deletions

View File

@@ -3,6 +3,7 @@ package httpapi
import (
"context"
"encoding/json"
"math"
"net/http"
"net/http/httptest"
"strings"
@@ -110,12 +111,11 @@ func TestObservationsPopulatedJSONEnvelope(t *testing.T) {
}
}
func TestFormatNegotiationXMLAndText(t *testing.T) {
func TestFormatNegotiationCaseInsensitive(t *testing.T) {
hXML := newHandler(t, &fakeService{alerts: &model.WeatherAlertRun{AsOf: time.Now().UTC()}}, "/alerts/active")
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/alerts/active", nil)
req.Header.Set("Accept", "application/xml")
req := httptest.NewRequest(http.MethodGet, "/alerts/active?format=XML", nil)
hXML.ServeHTTP(w, req)
if w.Code != http.StatusOK {
@@ -127,7 +127,7 @@ func TestFormatNegotiationXMLAndText(t *testing.T) {
hText := newHandler(t, &fakeService{forecast: &model.WeatherForecastRun{Product: model.ForecastProductHourly}}, "/forecast/hourly")
w = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodGet, "/forecast/hourly?format=text", nil)
req = httptest.NewRequest(http.MethodGet, "/forecast/hourly?format=TEXT", nil)
hText.ServeHTTP(w, req)
if w.Code != http.StatusOK {
@@ -141,6 +141,81 @@ func TestFormatNegotiationXMLAndText(t *testing.T) {
}
}
func TestObservationUSUnitsChangesFieldNames(t *testing.T) {
h := newHandler(t, &fakeService{
observation: &model.WeatherObservation{
StationID: "KSTL",
Timestamp: time.Now().UTC(),
ConditionCode: 1,
TemperatureC: float64Ptr(20),
},
}, "/observations")
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/observations?units=US", nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var payload struct {
Data map[string]any `json:"data"`
}
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode envelope: %v", err)
}
if _, ok := payload.Data["temperatureC"]; ok {
t.Fatalf("expected temperatureC to be omitted in US payload")
}
v, ok := payload.Data["temperatureF"].(float64)
if !ok {
t.Fatalf("expected temperatureF in US payload, got %#v", payload.Data["temperatureF"])
}
if math.Abs(v-68.0) > 0.0001 {
t.Fatalf("expected temperatureF ~= 68, got %f", v)
}
}
func TestAlertsUSUnitsKeepSchema(t *testing.T) {
h := newHandler(t, &fakeService{
alerts: &model.WeatherAlertRun{
AsOf: time.Now().UTC(),
Alerts: []model.WeatherAlert{{
ID: "abc",
Headline: "A headline",
}},
},
}, "/alerts/active")
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/alerts/active?units=us", nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var payload struct {
Data map[string]any `json:"data"`
}
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode envelope: %v", err)
}
alerts, ok := payload.Data["alerts"].([]any)
if !ok || len(alerts) != 1 {
t.Fatalf("expected one alert in response, got %#v", payload.Data["alerts"])
}
first, ok := alerts[0].(map[string]any)
if !ok {
t.Fatalf("expected first alert object, got %#v", alerts[0])
}
if first["id"] != "abc" {
t.Fatalf("expected alert id abc, got %#v", first["id"])
}
}
func newHandler(t *testing.T, svc Service, path string) http.Handler {
t.Helper()
@@ -193,3 +268,7 @@ func testRenderers(t *testing.T) *render.Registry {
return reg
}
func float64Ptr(v float64) *float64 {
return &v
}