Added support for rounding of values by default in API responses
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful

This commit is contained in:
2026-03-20 11:30:11 -05:00
parent bb18bcfb15
commit 6806de3c0a
10 changed files with 369 additions and 68 deletions

View File

@@ -119,6 +119,70 @@ func TestObservationsPopulatedJSONEnvelope(t *testing.T) {
}
}
func TestObservationsDefaultPrecisionRoundsToInteger(t *testing.T) {
h := newHandler(t, &fakeService{
observation: &model.WeatherObservation{
Timestamp: time.Now().UTC(),
ConditionCode: model.WMOUnknown,
TemperatureC: float64Ptr(20.6),
},
}, "/observations")
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/observations", 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)
}
got, ok := payload.Data["temperatureC"].(float64)
if !ok {
t.Fatalf("expected temperatureC float, got %#v", payload.Data["temperatureC"])
}
if got != 21 {
t.Fatalf("expected rounded temperatureC 21, got %v", got)
}
}
func TestObservationsPrecisionTwo(t *testing.T) {
h := newHandler(t, &fakeService{
observation: &model.WeatherObservation{
Timestamp: time.Now().UTC(),
ConditionCode: model.WMOUnknown,
TemperatureC: float64Ptr(20.678),
},
}, "/observations")
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/observations?precision=2", 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)
}
got, ok := payload.Data["temperatureC"].(float64)
if !ok {
t.Fatalf("expected temperatureC float, got %#v", payload.Data["temperatureC"])
}
if math.Abs(got-20.68) > 0.00001 {
t.Fatalf("expected rounded temperatureC 20.68, got %v", got)
}
}
func TestFormatNegotiationCaseInsensitive(t *testing.T) {
hXML := newHandler(t, &fakeService{alerts: &model.WeatherAlertRun{AsOf: time.Now().UTC()}}, "/alerts/active")
@@ -285,6 +349,51 @@ func TestForecastUSUnitsWithXMLFormatUppercaseQuery(t *testing.T) {
}
}
func TestForecastPrecisionTwo(t *testing.T) {
h := newHandler(t, &fakeService{
forecast: &model.WeatherForecastRun{
Product: model.ForecastProductHourly,
IssuedAt: time.Now().UTC(),
Periods: []model.WeatherForecastPeriod{{
StartTime: time.Now().UTC(),
EndTime: time.Now().UTC().Add(time.Hour),
ConditionCode: model.WMOUnknown,
TemperatureC: float64Ptr(12.345),
}},
},
}, "/forecast/hourly")
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/forecast/hourly?precision=2", 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)
}
periods, ok := payload.Data["periods"].([]any)
if !ok || len(periods) == 0 {
t.Fatalf("expected non-empty periods, got %#v", payload.Data["periods"])
}
first, ok := periods[0].(map[string]any)
if !ok {
t.Fatalf("expected first period object, got %#v", periods[0])
}
got, ok := first["temperatureC"].(float64)
if !ok {
t.Fatalf("expected temperatureC float, got %#v", first["temperatureC"])
}
if math.Abs(got-12.35) > 0.00001 {
t.Fatalf("expected rounded temperatureC 12.35, got %v", got)
}
}
func TestCurrentConditionsNoDataReturnsNullEnvelopeData(t *testing.T) {
h := newHandler(t, &fakeService{}, "/conditions/current")
@@ -436,6 +545,33 @@ func TestCurrentConditionsRejectUnknownQueryParameter(t *testing.T) {
}
}
func TestPrecisionValidationRange(t *testing.T) {
h := newHandler(t, &fakeService{}, "/conditions/current")
for _, raw := range []string{"-1", "3", "abc"} {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/conditions/current?precision="+raw, nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400 for precision=%s, got %d", raw, w.Code)
}
}
}
func TestAlertsRejectPrecisionQueryParameter(t *testing.T) {
h := newHandler(t, &fakeService{
alerts: &model.WeatherAlertRun{AsOf: time.Now().UTC()},
}, "/alerts/active")
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/alerts/active?precision=1", nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", w.Code)
}
}
func newHandler(t *testing.T, svc Service, path string) http.Handler {
t.Helper()