Files
webhooker/internal/database/database_test.go
clawbot 855b9de866
All checks were successful
check / check (push) Successful in 3m4s
Allow retention_days of 0 to mean retain forever (closes #79)
RetentionDays carried gorm:"default:30", so GORM substituted 30 for a
zero value while building the insert. A webhook could therefore never
be configured to keep its events indefinitely: the reaper's
retain-forever branch existed but was unreachable from the normal
create and edit flows.

Introduce database.RetentionForeverDays = 365 * 1000 as the sentinel
for "retain forever" and a Webhook.BeforeSave hook that rewrites any
non-positive RetentionDays to it. The rewrite has to live in the hook
rather than at the call sites: GORM applies the column default while
converting the model to insert values, which happens after BeforeSave,
so anything later loses that race. Putting it on the model also means
a future call site, such as the planned REST API, cannot bypass it.

The reaper now skips a webhook when Webhook.RetainsForever reports
true, which recognises the sentinel and keeps honouring the old <= 0
values for rows written before it existed. Without this the sentinel,
being positive, would have produced a cutoff a thousand years in the
past and a DELETE matching nothing on every sweep.

Form handling is shared by create and edit through parseRetentionDays
so the two cannot drift: an empty field keeps the previous behaviour
(default on create, unchanged on edit), 0 is honoured, and an
unparseable or negative value is a 400 that re-renders the form rather
than a silently substituted default.

The retention inputs drop max="365". That cap was not cosmetic: the
edit form pre-fills the stored value, so a retain-forever webhook
rendered 365000 into an input capped at 365 and browser validation
would have blocked saving any edit to it. min becomes 0 with a hint
explaining what 0 does, and the list and detail views render a
RetentionLabel of "forever" instead of a raw day count.

The 30-day default is consolidated into database.DefaultRetentionDays,
referenced from the handler and from the create form's pre-filled
value, with a test asserting it agrees with the struct tag that cannot
reference it.
2026-08-09 02:32:34 +00:00

103 lines
1.9 KiB
Go

package database_test
import (
"context"
"testing"
"go.uber.org/fx/fxtest"
"sneak.berlin/go/webhooker/internal/config"
"sneak.berlin/go/webhooker/internal/database"
"sneak.berlin/go/webhooker/internal/globals"
"sneak.berlin/go/webhooker/internal/logger"
)
const (
// testAppname is the Globals.Appname used in tests.
testAppname = "webhooker-test"
// testVersion is the Globals.Version used in tests.
testVersion = "test"
// testContentType is the event content type used in tests.
testContentType = "application/json"
// testWebhookName is the Webhook.Name used in tests.
testWebhookName = "test-webhook"
// testForeverLabel is Webhook.RetentionLabel for a retain-forever
// webhook.
testForeverLabel = "forever"
)
func setupTestDB(
t *testing.T,
) (*database.Database, *fxtest.Lifecycle) {
t.Helper()
lc := fxtest.NewLifecycle(t)
g := &globals.Globals{
Appname: testAppname,
Version: testVersion,
}
l, err := logger.New(
lc,
logger.LoggerParams{Globals: g},
)
if err != nil {
t.Fatalf("Failed to create logger: %v", err)
}
c := &config.Config{
DataDir: t.TempDir(),
Environment: "dev",
}
db, err := database.New(lc, database.DatabaseParams{
Config: c,
Logger: l,
})
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
return db, lc
}
func TestDatabaseConnection(t *testing.T) {
t.Parallel()
db, lc := setupTestDB(t)
ctx := context.Background()
err := lc.Start(ctx)
if err != nil {
t.Fatalf("Failed to connect to database: %v", err)
}
defer func() {
stopErr := lc.Stop(ctx)
if stopErr != nil {
t.Errorf(
"Failed to stop lifecycle: %v",
stopErr,
)
}
}()
if db.DB() == nil {
t.Error("Expected non-nil database connection")
}
var result int
err = db.DB().Raw("SELECT 1").Scan(&result).Error
if err != nil {
t.Fatalf("Failed to execute test query: %v", err)
}
if result != 1 {
t.Errorf(
"Expected query result to be 1, got %d",
result,
)
}
}