Update to support upstream weatherfeeder v0.12.1 and add ends field to the alerts schema
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful

This commit is contained in:
2026-06-16 20:08:03 -05:00
parent f4dd701204
commit 2a33fe01cf
15 changed files with 117 additions and 36 deletions

View File

@@ -147,6 +147,7 @@ func cloneAlert(alert model.WeatherAlert) model.WeatherAlert {
out.Sent = copyTime(alert.Sent)
out.Effective = copyTime(alert.Effective)
out.Onset = copyTime(alert.Onset)
out.Ends = copyTime(alert.Ends)
out.Expires = copyTime(alert.Expires)
if alert.References != nil {
out.References = make([]model.AlertReference, len(alert.References))
@@ -170,7 +171,11 @@ func isActiveAlert(alert model.WeatherAlert, activeAt time.Time) bool {
if alert.Effective != nil && activeAt.Before(*alert.Effective) {
return false
}
if alert.Expires != nil && !activeAt.Before(*alert.Expires) {
endBoundary := alert.Ends
if endBoundary == nil {
endBoundary = alert.Expires
}
if endBoundary != nil && !activeAt.Before(*endBoundary) {
return false
}
return true

View File

@@ -132,7 +132,7 @@ func TestServiceLatestActiveAlertRunDelegatesAndFilters(t *testing.T) {
if repo.alertRunCalls != 1 {
t.Fatalf("expected one repository call, got %d", repo.alertRunCalls)
}
assertAlertIDs(t, run, []string{"current", "effective-at-boundary", "missing-effective", "missing-expires", "later-onset"})
assertAlertIDs(t, run, []string{"current", "effective-at-boundary", "missing-effective", "missing-expires", "later-onset", "ends-preferred"})
}
func TestServiceLatestActiveAlertRunNoData(t *testing.T) {
@@ -165,9 +165,9 @@ func TestServiceLatestActiveAlertRunPropagatesErrors(t *testing.T) {
func TestServiceLatestActiveAlertRunKeepsMetadataWithEmptyAlerts(t *testing.T) {
activeAt := testTime(12)
repo := &fakeRepository{alerts: testAlertRunWithAlerts([]model.WeatherAlert{
testAlert("expired", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(12)),
testAlert("cancel", "Cancel", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(13)),
testAlert("future", "Alert", testTimePtr(9), testTimePtr(13), testTimePtr(13), testTimePtr(14)),
testAlert("expired", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(12), testTimePtr(13)),
testAlert("cancel", "Cancel", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(13), testTimePtr(13)),
testAlert("future", "Alert", testTimePtr(9), testTimePtr(13), testTimePtr(13), testTimePtr(14), testTimePtr(14)),
})}
svc := NewService(repo)
@@ -213,7 +213,8 @@ func TestServiceLatestActiveAlertRunDoesNotMutateRepositoryRun(t *testing.T) {
*run.Alerts[0].Sent = testTime(1)
*run.Alerts[0].Effective = testTime(2)
*run.Alerts[0].Onset = testTime(3)
*run.Alerts[0].Expires = testTime(4)
*run.Alerts[0].Ends = testTime(4)
*run.Alerts[0].Expires = testTime(5)
*run.Alerts[0].References[0].Sent = testTime(5)
run.Alerts[0].ID = "changed"
run.Alerts[0].References[0].ID = "changed"
@@ -237,7 +238,10 @@ func TestServiceLatestActiveAlertRunDoesNotMutateRepositoryRun(t *testing.T) {
if original.Alerts[0].Onset == nil || !original.Alerts[0].Onset.Equal(testTime(11)) {
t.Fatalf("expected original onset unchanged, got %v", original.Alerts[0].Onset)
}
if original.Alerts[0].Expires == nil || !original.Alerts[0].Expires.Equal(testTime(13)) {
if original.Alerts[0].Ends == nil || !original.Alerts[0].Ends.Equal(testTime(13)) {
t.Fatalf("expected original ends unchanged, got %v", original.Alerts[0].Ends)
}
if original.Alerts[0].Expires == nil || !original.Alerts[0].Expires.Equal(testTime(12)) {
t.Fatalf("expected original expires unchanged, got %v", original.Alerts[0].Expires)
}
if original.Alerts[0].References[0].ID != "ref-current" {
@@ -246,11 +250,28 @@ func TestServiceLatestActiveAlertRunDoesNotMutateRepositoryRun(t *testing.T) {
if original.Alerts[0].References[0].Sent == nil || !original.Alerts[0].References[0].Sent.Equal(testTime(8)) {
t.Fatalf("expected original reference sent unchanged, got %v", original.Alerts[0].References[0].Sent)
}
if len(original.Alerts) != 8 {
if len(original.Alerts) != 10 {
t.Fatalf("expected original alert slice unchanged, got %d entries", len(original.Alerts))
}
}
func TestServiceLatestActiveAlertRunUsesEndsBeforeExpires(t *testing.T) {
activeAt := testTime(12)
repo := &fakeRepository{alerts: testAlertRunWithAlerts([]model.WeatherAlert{
testAlert("ends-at-boundary", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(12), testTimePtr(13)),
testAlert("ends-after-active-expires-before", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(13), testTimePtr(11)),
testAlert("expires-fallback", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(11), nil, testTimePtr(13)),
testAlert("expires-fallback-expired", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(11), nil, testTimePtr(12)),
})}
svc := NewService(repo)
run, err := svc.LatestActiveAlertRun(context.Background(), activeAt)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertAlertIDs(t, run, []string{"ends-after-active-expires-before", "expires-fallback"})
}
func TestServiceDelegatesLatestConvectiveOutlookRun(t *testing.T) {
repo := &fakeRepository{outlookRun: testOutlookRun()}
svc := NewService(repo)
@@ -509,14 +530,16 @@ func TestServicePropagatesErrors(t *testing.T) {
func testAlertRun() *model.WeatherAlertRun {
return testAlertRunWithAlerts([]model.WeatherAlert{
testAlert("current", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(13)),
testAlert("expired", "Update", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(12)),
testAlert("future-effective", "Alert", testTimePtr(9), testTimePtr(13), testTimePtr(13), testTimePtr(15)),
testAlert("canceled", " cancel ", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(13)),
testAlert("effective-at-boundary", "Alert", testTimePtr(9), testTimePtr(12), testTimePtr(12), testTimePtr(14)),
testAlert("missing-effective", "Alert", testTimePtr(9), nil, nil, testTimePtr(14)),
testAlert("missing-expires", "Alert", testTimePtr(9), testTimePtr(10), nil, nil),
testAlert("later-onset", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(13), testTimePtr(14)),
testAlert("current", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(13), testTimePtr(12)),
testAlert("expired", "Update", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(12), testTimePtr(13)),
testAlert("future-effective", "Alert", testTimePtr(9), testTimePtr(13), testTimePtr(13), testTimePtr(15), testTimePtr(15)),
testAlert("canceled", " cancel ", testTimePtr(9), testTimePtr(10), testTimePtr(11), testTimePtr(13), testTimePtr(13)),
testAlert("effective-at-boundary", "Alert", testTimePtr(9), testTimePtr(12), testTimePtr(12), testTimePtr(14), testTimePtr(14)),
testAlert("missing-effective", "Alert", testTimePtr(9), nil, nil, testTimePtr(14), testTimePtr(14)),
testAlert("missing-expires", "Alert", testTimePtr(9), testTimePtr(10), nil, nil, nil),
testAlert("later-onset", "Alert", testTimePtr(9), testTimePtr(10), testTimePtr(13), testTimePtr(14), testTimePtr(14)),
testAlert("ends-preferred", "Alert", testTimePtr(9), testTimePtr(10), nil, testTimePtr(14), testTimePtr(11)),
testAlert("ends-at-boundary", "Alert", testTimePtr(9), testTimePtr(10), nil, testTimePtr(12), testTimePtr(14)),
})
}
@@ -533,7 +556,7 @@ func testAlertRunWithAlerts(alerts []model.WeatherAlert) *model.WeatherAlertRun
}
}
func testAlert(id string, messageType string, sent *time.Time, effective *time.Time, onset *time.Time, expires *time.Time) model.WeatherAlert {
func testAlert(id string, messageType string, sent *time.Time, effective *time.Time, onset *time.Time, ends *time.Time, expires *time.Time) model.WeatherAlert {
refSent := testTime(8)
return model.WeatherAlert{
ID: id,
@@ -551,6 +574,7 @@ func testAlert(id string, messageType string, sent *time.Time, effective *time.T
Sent: sent,
Effective: effective,
Onset: onset,
Ends: ends,
Expires: expires,
AreaDescription: "St. Louis City",
SenderName: "NWS St. Louis",