package database_test import ( "log/slog" "net/http" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "sneak.berlin/go/webhooker/internal/database" "sneak.berlin/go/webhooker/internal/database/databasetest" ) // withFixedNow returns db with GORM's clock stopped at a time in UTC, // so that the created and updated times it writes come back from // SQLite equal to the row written, which holds them in UTC. func withFixedNow(db *gorm.DB) (*gorm.DB, time.Time) { at := time.Date(2026, 10, 13, 12, 30, 45, 123456789, time.UTC) return db.Session(&gorm.Session{ NowFunc: func() time.Time { return at }, }), at } // newBaseModel returns a BaseModel with a new ID. func newBaseModel() database.BaseModel { return database.BaseModel{ID: uuid.New().String()} } // assertRoundTrip writes row, which must be the only row of its // table, and reads back the same row. func assertRoundTrip[T any](t *testing.T, db *gorm.DB, row *T) { t.Helper() require.NoError(t, db.Create(row).Error) var got T require.NoError(t, db.First(&got).Error) assert.Equal(t, *row, got) } // TestMainSchemaHoldsEveryModel writes a row of each main database // model, every field set, into a database made from its SQL files. func TestMainSchemaHoldsEveryModel(t *testing.T) { t.Parallel() mainDB, err := database.Open( t.TempDir(), slog.New(slog.DiscardHandler), ) require.NoError(t, err) t.Cleanup(func() { _ = mainDB.Close() }) db, at := withFixedNow(mainDB.DB()) assertRoundTrip(t, db, &database.Setting{ Key: "session_key", Value: "value", }) assertRoundTrip(t, db, &database.User{ BaseModel: newBaseModel(), Username: "user", Password: "password hash", }) assertRoundTrip(t, db, &database.APIKey{ BaseModel: newBaseModel(), UserID: uuid.New().String(), Key: "key", Description: "API key description", LastUsedAt: &at, }) assertRoundTrip(t, db, &database.Webhook{ BaseModel: newBaseModel(), UserID: uuid.New().String(), Name: "webhook", Description: "webhook description", RetentionDays: 7, }) assertRoundTrip(t, db, &database.Entrypoint{ BaseModel: newBaseModel(), WebhookID: uuid.New().String(), Path: uuid.New().String(), Description: "entrypoint description", Active: true, }) assertRoundTrip(t, db, &database.Target{ BaseModel: newBaseModel(), WebhookID: uuid.New().String(), Name: "target", Type: database.TargetTypeHTTP, Active: true, Config: `{"url":"https://example.com/"}`, MaxRetries: 3, }) } // TestEventSchemaHoldsEveryModel does the same for a webhook's event // database, which the manager makes from its SQL files. func TestEventSchemaHoldsEveryModel(t *testing.T) { t.Parallel() mgr := databasetest.NewWebhookDBManager(t, t.TempDir()) t.Cleanup(func() { _ = mgr.CloseAll() }) events, err := mgr.GetDB(uuid.New().String()) require.NoError(t, err) db, at := withFixedNow(events) source := uuid.New().String() assertRoundTrip(t, db, &database.Event{ BaseModel: newBaseModel(), WebhookID: uuid.New().String(), EntrypointID: uuid.New().String(), Method: http.MethodPost, RawQuery: "a=1", Headers: `{"X":"Y"}`, Body: "body", ContentType: "text/plain", BodyBytes: 4, ResubmittedFromID: &source, }) assertRoundTrip(t, db, &database.Delivery{ BaseModel: newBaseModel(), EventID: uuid.New().String(), TargetID: uuid.New().String(), Status: database.DeliveryStatusDelivered, FinishedAt: &at, Replay: true, }) assertRoundTrip(t, db, &database.DeliveryResult{ BaseModel: newBaseModel(), DeliveryID: uuid.New().String(), AttemptNum: 2, Success: true, StatusCode: 200, ResponseBody: "ok", Error: "error", Duration: 15, }) assertRoundTrip(t, db, &database.TargetTotals{ TargetID: uuid.New().String(), Deliveries: 5, Delivered: 3, Failed: 1, DeliveriesRemoved: 2, FailedRemoved: 1, }) assertRoundTrip(t, db, &database.EntrypointTotals{ EntrypointID: uuid.New().String(), LastEventAt: at, }) // A new event database has its one row of event totals already. require.NoError(t, database.AddEventTotals(db, database.EventTotals{ Events: 3, EventsRemoved: 1, LastEventAt: &at, })) var totals []database.EventTotals require.NoError(t, db.Find(&totals).Error) assert.Equal(t, []database.EventTotals{{ ID: 1, Events: 3, EventsRemoved: 1, LastEventAt: &at, }}, totals) }