package delivery_test import ( "context" "database/sql" "fmt" "net/http" "net/http/httptest" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" _ "modernc.org/sqlite" "sneak.berlin/go/webhooker/internal/database" ) // assertNoTargetRows opens the per-webhook database file directly, // outside GORM, and fails if its targets table holds any rows. // Target config is the credential for slack and http targets, and // event databases are the files that get backed up and handed // around. func assertNoTargetRows(t *testing.T, dbPath string) { t.Helper() sqlDB, err := sql.Open( "sqlite", fmt.Sprintf("file:%s?mode=ro", dbPath), ) require.NoError(t, err) defer func() { _ = sqlDB.Close() }() var tables int require.NoError(t, sqlDB.QueryRowContext( t.Context(), "SELECT count(*) FROM sqlite_master "+ "WHERE type = 'table' AND name = 'targets'", ).Scan(&tables)) if tables == 0 { return } var rows int require.NoError(t, sqlDB.QueryRowContext( t.Context(), "SELECT count(*) FROM targets", ).Scan(&rows)) assert.Zero( t, rows, "per-webhook event database must hold no target rows", ) } // TestEventDBHoldsNoTargetRows drives a delivery and then a retry // through the real engine write paths and asserts neither leaves a // target row behind in events-*.db. func TestEventDBHoldsNoTargetRows(t *testing.T) { t.Parallel() s := newISetup(t) ts := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }, )) defer ts.Close() cfg := iHTTPConfig(ts.URL) targetID := uuid.New().String() dbPath := s.DBMgr.DBPath(s.WebhookID) event := iSeedEvent( t, s.WebhookDB, s.WebhookID, `{"leak":"none"}`, ) body := event.Body // A new delivery. d := iSeedDelivery( t, s.WebhookDB, event.ID, targetID, database.DeliveryStatusPending, ) task := iTask( d, event, s.WebhookID, targetID, "leaky-target", cfg, 5, 1, &body, ) s.Engine.ExportProcessNewTask(context.TODO(), &task) iAssertStatus( t, s.WebhookDB, d.ID, database.DeliveryStatusDelivered, ) assertNoTargetRows(t, dbPath) // A retry. rd := iSeedDelivery( t, s.WebhookDB, event.ID, targetID, database.DeliveryStatusRetrying, ) rTask := iTask( rd, event, s.WebhookID, targetID, "leaky-target", cfg, 5, 2, &body, ) s.Engine.ExportProcessRetryTask(context.TODO(), &rTask) iAssertStatus( t, s.WebhookDB, rd.ID, database.DeliveryStatusDelivered, ) assertNoTargetRows(t, dbPath) } // TestEventDBHoldsNoTargetRowsOnFailedDelivery covers the failure // write path, which updates the delivery to failed and records a // result, rather than the success path above. func TestEventDBHoldsNoTargetRowsOnFailedDelivery(t *testing.T) { t.Parallel() s := newISetup(t) ts := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) }, )) defer ts.Close() cfg := iHTTPConfig(ts.URL) targetID := uuid.New().String() event := iSeedEvent( t, s.WebhookDB, s.WebhookID, `{"leak":"none"}`, ) body := event.Body d := iSeedDelivery( t, s.WebhookDB, event.ID, targetID, database.DeliveryStatusPending, ) task := iTask( d, event, s.WebhookID, targetID, "leaky-target", cfg, 0, 1, &body, ) s.Engine.ExportProcessNewTask(context.TODO(), &task) iAssertStatus( t, s.WebhookDB, d.ID, database.DeliveryStatusFailed, ) assertNoTargetRows(t, s.DBMgr.DBPath(s.WebhookID)) }