package database_test import ( "context" "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" ) // readTotals reads a webhook database's row of running totals, // asserting that it has exactly one. func readTotals(t *testing.T, db *gorm.DB) database.Totals { t.Helper() var rows []database.Totals require.NoError(t, db.Find(&rows).Error) require.Len(t, rows, 1) return rows[0] } // TestWebhookDBManager_TotalsRowSurvivesReopen verifies that a new // event database starts with one row of zero totals, and that opening // it again keeps that row and what was added to it. func TestWebhookDBManager_TotalsRowSurvivesReopen(t *testing.T) { t.Parallel() mgr, lc := setupTestWebhookDBManager(t) ctx := context.Background() require.NoError(t, lc.Start(ctx)) defer func() { require.NoError(t, lc.Stop(ctx)) }() webhookID := uuid.New().String() db, err := mgr.GetDB(webhookID) require.NoError(t, err) fresh := readTotals(t, db) assert.Equal(t, database.Totals{ID: fresh.ID}, fresh) require.NoError(t, database.AddTotals(db, database.Totals{ Events: 2, Deliveries: 3, Failures: 1, })) // Drop the cached connection so the next open reopens the file, // as a restart would. require.NoError(t, mgr.CloseAll()) db, err = mgr.GetDB(webhookID) require.NoError(t, err) assert.Equal(t, database.Totals{ ID: fresh.ID, Events: 2, Deliveries: 3, Failures: 1, }, readTotals(t, db)) } // TestRetentionReaper_AddsWhatItRemovesToTotals verifies that a sweep // leaves the lifetime totals alone and adds the events, deliveries and // failed deliveries it deletes to the removed totals, so the totals // within retention match the rows still stored. func TestRetentionReaper_AddsWhatItRemovesToTotals(t *testing.T) { t.Parallel() env := setupRetentionTest(t) webhookID := createWebhook(t, env.mainDB.DB(), 30) db, err := env.mgr.GetDB(webhookID) require.NoError(t, err) now := time.Now() expired := now.Add(-40 * 24 * time.Hour) seedEventChain(t, db, webhookID, expired) expiredFailure := seedEventChain(t, db, webhookID, expired) recentFailure := seedEventChain( t, db, webhookID, now.Add(-24*time.Hour), ) for _, id := range []string{ expiredFailure.deliveryID, recentFailure.deliveryID, } { require.NoError(t, db.Model(&database.Delivery{}). Where("id = ?", id). Update("status", database.DeliveryStatusFailed).Error) } // The totals storing those rows would have left. require.NoError(t, database.AddTotals(db, database.Totals{ Events: 3, Deliveries: 3, Failures: 2, })) env.reaper.ExportSweep(context.Background()) totals := readTotals(t, db) assert.Equal(t, database.Totals{ ID: totals.ID, Events: 3, Deliveries: 3, Failures: 2, EventsRemoved: 2, DeliveriesRemoved: 2, FailuresRemoved: 1, }, totals) var events, deliveries, failures int64 require.NoError(t, db.Model(&database.Event{}).Count(&events).Error) require.NoError(t, db.Model(&database.Delivery{}). Count(&deliveries).Error) require.NoError(t, db.Model(&database.Delivery{}). Where("status = ?", database.DeliveryStatusFailed). Count(&failures).Error) assert.Equal(t, events, totals.EventsWithinRetention()) assert.Equal(t, deliveries, totals.DeliveriesWithinRetention()) assert.Equal(t, failures, totals.FailuresWithinRetention()) // A sweep with nothing left to remove changes nothing. env.reaper.ExportSweep(context.Background()) assert.Equal(t, totals, readTotals(t, db)) }