Add convective outlook presentation

This commit is contained in:
2026-06-11 15:41:38 +00:00
parent d6734d5ffc
commit e897ae52df
4 changed files with 224 additions and 3 deletions

View File

@@ -430,6 +430,110 @@ func TestForecastPayloadLatitudeLongitudeNotRounded(t *testing.T) {
assertApprox(t, us.Longitude, -90.199456, 0.000001)
}
func TestOutlookRunPayloadNilInputReturnsNil(t *testing.T) {
if OutlookRunPayload(nil, UnitsUS, time.UTC) != nil {
t.Fatalf("expected nil outlook run input to return nil payload")
}
}
func TestOutlookRunPayloadTimezoneConversionAndCopySemantics(t *testing.T) {
loc := time.FixedZone("UTC-05:00", -5*60*60)
asOf := time.Date(2026, 6, 11, 18, 0, 0, 0, time.UTC)
issuedAt := asOf.Add(-1 * time.Hour)
severityRank := 5
latitude := 38.627123
longitude := -90.199456
geometry := json.RawMessage(`{"type":"Point","coordinates":[-90.2,38.6]}`)
run := &model.WeatherOutlookRun{
LocationID: "stl",
LocationName: "St. Louis",
Latitude: &latitude,
Longitude: &longitude,
AsOf: asOf,
IssuedAt: &issuedAt,
Outlooks: []model.WeatherOutlook{{
ID: "cat-1",
Provider: "spc",
Product: "convective",
Day: 1,
OutlookType: "categorical",
Label: "SLGT",
LabelText: "Slight Risk",
SeverityRank: &severityRank,
ValidFrom: asOf,
ValidTo: asOf.Add(6 * time.Hour),
IssuedAt: issuedAt,
ExpiresAt: asOf.Add(6 * time.Hour),
Forecaster: "DIAL",
Headline: "Severe storms possible",
Summary: "Scattered severe storms are possible.",
Discussion: "Discussion text.",
SourceURL: "https://example.test/source",
ImageURL: "https://example.test/image.png",
ContainsLocation: true,
Geometry: geometry,
}},
}
payload := OutlookRunPayload(run, UnitsUS, loc)
out, ok := payload.(*model.WeatherOutlookRun)
if !ok {
t.Fatalf("expected *model.WeatherOutlookRun payload, got %T", payload)
}
if out == run {
t.Fatalf("expected outlook run payload to be copied")
}
if out.Latitude == run.Latitude || out.Longitude == run.Longitude || out.IssuedAt == run.IssuedAt {
t.Fatalf("expected run pointers to be copied")
}
if len(out.Outlooks) != 1 {
t.Fatalf("expected one outlook, got %d", len(out.Outlooks))
}
if out.Outlooks[0].SeverityRank == run.Outlooks[0].SeverityRank {
t.Fatalf("expected severity rank pointer to be copied")
}
if &out.Outlooks[0].Geometry[0] == &run.Outlooks[0].Geometry[0] {
t.Fatalf("expected geometry bytes to be copied")
}
assertOffsetSeconds(t, out.AsOf, -5*60*60)
assertOffsetSeconds(t, *out.IssuedAt, -5*60*60)
assertOffsetSeconds(t, out.Outlooks[0].ValidFrom, -5*60*60)
assertOffsetSeconds(t, out.Outlooks[0].ValidTo, -5*60*60)
assertOffsetSeconds(t, out.Outlooks[0].IssuedAt, -5*60*60)
assertOffsetSeconds(t, out.Outlooks[0].ExpiresAt, -5*60*60)
if !out.AsOf.UTC().Equal(asOf) || !out.Outlooks[0].ValidFrom.UTC().Equal(asOf) {
t.Fatalf("expected timezone conversion to preserve instants")
}
if *out.Latitude != latitude || *out.Longitude != longitude {
t.Fatalf("expected latitude/longitude preserved, got %v %v", out.Latitude, out.Longitude)
}
if out.Outlooks[0].SeverityRank == nil || *out.Outlooks[0].SeverityRank != severityRank {
t.Fatalf("expected severity rank preserved, got %v", out.Outlooks[0].SeverityRank)
}
if string(out.Outlooks[0].Geometry) != string(geometry) {
t.Fatalf("expected geometry bytes preserved, got %s", out.Outlooks[0].Geometry)
}
*out.Latitude = 99
*out.Longitude = -99
*out.IssuedAt = time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
*out.Outlooks[0].SeverityRank = 99
out.Outlooks[0].Geometry[0] = '['
if *run.Latitude != latitude || *run.Longitude != longitude || !run.IssuedAt.Equal(issuedAt) {
t.Fatalf("expected source run pointers not to mutate")
}
if *run.Outlooks[0].SeverityRank != severityRank {
t.Fatalf("expected source severity rank not to mutate")
}
if string(run.Outlooks[0].Geometry) != string(geometry) {
t.Fatalf("expected source geometry not to mutate, got %s", run.Outlooks[0].Geometry)
}
assertOffsetSeconds(t, run.AsOf, 0)
assertOffsetSeconds(t, run.Outlooks[0].ValidFrom, 0)
}
func float64Ptr(v float64) *float64 {
return &v
}