package handlers_test import ( "net/http" "strconv" "strings" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm/clause" "sneak.berlin/go/webhooker/internal/database" "sneak.berlin/go/webhooker/internal/handlers" "sneak.berlin/go/webhooker/internal/logger" "sneak.berlin/go/webhooker/internal/session" ) // entrypointRow returns the part of a rendered webhook page from an // entrypoint's URL to the next entrypoint's, which holds its figures. func entrypointRow(t *testing.T, page, entrypointID string) string { t.Helper() _, row, found := strings.Cut(page, `id="entrypoint-url-`+entrypointID+`"`) require.True(t, found) row, _, _ = strings.Cut(row, `id="entrypoint-url-`) return row } // lastEventShown matches an entrypoint row's last event arriving at at. func lastEventShown(at time.Time) string { return `Last Event:\s*[^<]+` } // eventsShown matches an entrypoint row's count of n events. func eventsShown(n int) string { return `Events Within Retention:\s*` + strconv.Itoa(n) + `` } // TestHandleSourceDetail_ShowsEntrypointEvents proves each entrypoint // on the webhook page shows its own figures: how many events arrived // through it within the webhook's retention period, leaving out one // older than that, and when the newest arrived, or "never" for an // entrypoint with none. func TestHandleSourceDetail_ShowsEntrypointEvents(t *testing.T) { t.Parallel() var ( h *handlers.Handlers sess *session.Session db *database.Database dbMgr *database.WebhookDBManager ) app := newTestApp(t, &h, &sess, &db, &dbMgr) app.RequireStart() t.Cleanup(app.RequireStop) wh := &database.Webhook{ UserID: deleteTestUserID, Name: "figures", RetentionDays: 7, } require.NoError(t, db.DB().Omit(clause.Associations).Create(wh).Error) webhookDB, err := dbMgr.GetDB(wh.ID) require.NoError(t, err) entrypoint := func() *database.Entrypoint { ep := &database.Entrypoint{ WebhookID: wh.ID, Path: uuid.New().String(), Active: true, } require.NoError(t, db.DB().Omit(clause.Associations).Create(ep).Error) return ep } // event stores an event that arrived on ep's URL age ago and // records it as ep's last event, as the receiver does. event := func(ep *database.Entrypoint, age time.Duration) time.Time { e := &database.Event{ WebhookID: wh.ID, EntrypointID: ep.ID, Method: http.MethodPost, } e.CreatedAt = time.Now().Add(-age) require.NoError(t, webhookDB.Omit(clause.Associations).Create(e).Error) require.NoError(t, database.AddEntrypointTotals(webhookDB, database.EntrypointTotals{ EntrypointID: ep.ID, LastEventAt: e.CreatedAt, })) return e.CreatedAt } busy, quiet, unused := entrypoint(), entrypoint(), entrypoint() event(busy, 8*24*time.Hour) // older than the 7 days kept event(busy, 3*time.Hour) busyLast := event(busy, time.Hour) quietLast := event(quiet, 2*24*time.Hour) body := renderSourceDetailPage(t, h, sess, wh.ID) assert.Regexp(t, lastEventShown(busyLast), entrypointRow(t, body, busy.ID)) assert.Regexp(t, eventsShown(2), entrypointRow(t, body, busy.ID)) assert.Regexp(t, lastEventShown(quietLast), entrypointRow(t, body, quiet.ID)) assert.Regexp(t, eventsShown(1), entrypointRow(t, body, quiet.ID)) assert.Regexp(t, `Last Event:\s*never`, entrypointRow(t, body, unused.ID)) assert.Regexp(t, eventsShown(0), entrypointRow(t, body, unused.ID)) } // TestHandleSourceDetail_EntrypointLastEventSurvivesRetention checks // that once retention has removed every event that arrived on an // entrypoint's URL, the entrypoint still shows when the last one // arrived rather than "never". func TestHandleSourceDetail_EntrypointLastEventSurvivesRetention( t *testing.T, ) { t.Parallel() var ( h *handlers.Handlers sess *session.Session db *database.Database dbMgr *database.WebhookDBManager log *logger.Logger ) app := newTestApp(t, &h, &sess, &db, &dbMgr, &log) app.RequireStart() t.Cleanup(app.RequireStop) wh := &database.Webhook{ UserID: deleteTestUserID, Name: "swept", RetentionDays: 1, } require.NoError(t, db.DB().Omit(clause.Associations).Create(wh).Error) ep := seedEntrypoint(t, db, wh.ID) webhookDB, events := receiveEvents(t, h, dbMgr, wh.ID, ep.Path, 1) arrived := events[0].CreatedAt statsAge(t, webhookDB, events[0].ID, time.Now().Add(-50*time.Hour)) statsPrune(t, db, dbMgr, log, webhookDB) require.Empty(t, listEvents(t, webhookDB)) row := entrypointRow(t, renderSourceDetailPage(t, h, sess, wh.ID), ep.ID) assert.Regexp(t, lastEventShown(arrived), row) assert.Regexp(t, eventsShown(0), row) } // TestHandleSourceDetail_ResubmitLeavesEntrypointFigures checks that a // resubmitted copy, which did not arrive on the entrypoint's URL, // changes neither the entrypoint's last event nor its count. func TestHandleSourceDetail_ResubmitLeavesEntrypointFigures( t *testing.T, ) { t.Parallel() var ( h *handlers.Handlers sess *session.Session db *database.Database dbMgr *database.WebhookDBManager ) app := newTestApp(t, &h, &sess, &db, &dbMgr) app.RequireStart() t.Cleanup(app.RequireStop) wh := seedWebhook(t, db) ep := seedEntrypoint(t, db, wh.ID) webhookDB, events := receiveEvents(t, h, dbMgr, wh.ID, ep.Path, 1) arrived := events[0].CreatedAt require.Equal(t, http.StatusSeeOther, postResubmit(t, h, sess, wh.ID, events[0].ID).Code) require.Len(t, listEvents(t, webhookDB), 2) var totals database.EntrypointTotals require.NoError(t, webhookDB.Take(&totals).Error) assert.True(t, arrived.Equal(totals.LastEventAt)) row := entrypointRow(t, renderSourceDetailPage(t, h, sess, wh.ID), ep.ID) assert.Regexp(t, lastEventShown(arrived), row) assert.Regexp(t, eventsShown(1), row) }