Store a webhook's retention as a plain day count, 0 meaning forever (closes #539)
check / check (push) Waiting to run
check / check (push) Waiting to run
retention_days loses its column default of 30, and Webhook.RetentionDays its default tag, so a 0 is stored as 0. The 365000-day stand-in RetentionForeverDays and the BeforeSave hook that wrote it are gone. The reaper and the "forever" label read 0 as forever, and the forms accept 0 or 1 to MaxFiniteRetentionDays; a blank field still gives 30 on the new webhook form and keeps the stored value on the edit form. A negative value, which the forms refuse, is still read as forever so it cannot put the reaper's cutoff in the future. Model: opus-5-5
This commit is contained in:
@@ -2,8 +2,6 @@ package database_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -47,8 +45,7 @@ func storedRetention(t *testing.T, db *gorm.DB, id string) int {
|
||||
}
|
||||
|
||||
// newWebhookWithRetention creates a webhook through the ordinary Create
|
||||
// path, so the BeforeSave hook and the GORM column default both apply
|
||||
// exactly as they do in production.
|
||||
// path, as the new webhook form does.
|
||||
func newWebhookWithRetention(
|
||||
t *testing.T,
|
||||
db *gorm.DB,
|
||||
@@ -67,7 +64,7 @@ func newWebhookWithRetention(
|
||||
return wh.ID
|
||||
}
|
||||
|
||||
func TestWebhookBeforeSave_ZeroBecomesForeverSentinel(t *testing.T) {
|
||||
func TestWebhookRetentionDays_ZeroIsStoredAsZero(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := startedTestDB(t)
|
||||
@@ -75,31 +72,10 @@ func TestWebhookBeforeSave_ZeroBecomesForeverSentinel(t *testing.T) {
|
||||
wh := &database.Webhook{RetentionDays: 0}
|
||||
id := newWebhookWithRetention(t, db, wh)
|
||||
|
||||
assert.Equal(
|
||||
t,
|
||||
database.RetentionForeverDays,
|
||||
storedRetention(t, db, id),
|
||||
"a zero retention must be stored as the sentinel, "+
|
||||
"not replaced by the column default",
|
||||
)
|
||||
assert.Equal(t, 0, storedRetention(t, db, id))
|
||||
}
|
||||
|
||||
func TestWebhookBeforeSave_NegativeBecomesForeverSentinel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := startedTestDB(t)
|
||||
|
||||
wh := &database.Webhook{RetentionDays: -5}
|
||||
id := newWebhookWithRetention(t, db, wh)
|
||||
|
||||
assert.Equal(
|
||||
t,
|
||||
database.RetentionForeverDays,
|
||||
storedRetention(t, db, id),
|
||||
)
|
||||
}
|
||||
|
||||
func TestWebhookBeforeSave_PositiveIsPreserved(t *testing.T) {
|
||||
func TestWebhookRetentionDays_DayCountIsStored(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := startedTestDB(t)
|
||||
@@ -110,10 +86,9 @@ func TestWebhookBeforeSave_PositiveIsPreserved(t *testing.T) {
|
||||
assert.Equal(t, 7, storedRetention(t, db, id))
|
||||
}
|
||||
|
||||
// TestWebhookBeforeSave_UpdateToZeroBecomesSentinel proves the hook
|
||||
// fires on update as well as insert, via the same Save call the edit
|
||||
// handler makes.
|
||||
func TestWebhookBeforeSave_UpdateToZeroBecomesSentinel(t *testing.T) {
|
||||
// TestWebhookRetentionDays_UpdateToZeroIsStoredAsZero changes a day
|
||||
// count to 0 through the same Save call the edit handler makes.
|
||||
func TestWebhookRetentionDays_UpdateToZeroIsStoredAsZero(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := startedTestDB(t)
|
||||
@@ -125,28 +100,7 @@ func TestWebhookBeforeSave_UpdateToZeroBecomesSentinel(t *testing.T) {
|
||||
wh.RetentionDays = 0
|
||||
require.NoError(t, db.Omit(clause.Associations).Save(wh).Error)
|
||||
|
||||
assert.Equal(
|
||||
t,
|
||||
database.RetentionForeverDays,
|
||||
storedRetention(t, db, id),
|
||||
)
|
||||
}
|
||||
|
||||
// TestWebhookRetentionColumnDefaultMatchesConstant guards the one place
|
||||
// the default lives twice: a struct tag cannot reference a constant, so
|
||||
// this asserts the tag and DefaultRetentionDays agree.
|
||||
func TestWebhookRetentionColumnDefaultMatchesConstant(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
field, ok := reflect.TypeFor[database.Webhook]().
|
||||
FieldByName("RetentionDays")
|
||||
require.True(t, ok, "Webhook.RetentionDays must exist")
|
||||
|
||||
assert.Equal(
|
||||
t,
|
||||
"default:"+strconv.Itoa(database.DefaultRetentionDays),
|
||||
field.Tag.Get("gorm"),
|
||||
)
|
||||
assert.Equal(t, 0, storedRetention(t, db, id))
|
||||
}
|
||||
|
||||
// TestMaxFiniteRetentionDaysIsTheOverflowCeiling asserts that the
|
||||
@@ -176,14 +130,6 @@ func TestMaxFiniteRetentionDaysIsTheOverflowCeiling(t *testing.T) {
|
||||
time.Duration(overCeiling*hoursPerDay)*time.Hour,
|
||||
"one day past the ceiling must overflow",
|
||||
)
|
||||
|
||||
assert.Less(
|
||||
t,
|
||||
database.MaxFiniteRetentionDays,
|
||||
database.RetentionForeverDays,
|
||||
"the sentinel sits above the ceiling and is only safe "+
|
||||
"because retain-forever webhooks skip the arithmetic",
|
||||
)
|
||||
}
|
||||
|
||||
func TestWebhookRetainsForeverAndLabel(t *testing.T) {
|
||||
@@ -195,16 +141,8 @@ func TestWebhookRetainsForeverAndLabel(t *testing.T) {
|
||||
forever bool
|
||||
label string
|
||||
}{
|
||||
{
|
||||
"sentinel",
|
||||
database.RetentionForeverDays, true, testForeverLabel,
|
||||
},
|
||||
{
|
||||
"above sentinel",
|
||||
database.RetentionForeverDays + 1, true, testForeverLabel,
|
||||
},
|
||||
{"legacy zero", 0, true, testForeverLabel},
|
||||
{"legacy negative", -1, true, testForeverLabel},
|
||||
{"zero", 0, true, testForeverLabel},
|
||||
{"negative", -1, true, testForeverLabel},
|
||||
{"default", database.DefaultRetentionDays, false, "30 days"},
|
||||
{"one day", 1, false, "1 day"},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user