check / check (push) Successful in 3m8s
Each entrypoint in the webhook page's entrypoint list shows when the last event arrived on its URL (relative, with the full UTC time on hover), or "never" if none ever has, and how many events arrived on it within the webhook's retention period. The last event is kept per entrypoint in a new EntrypointTotals row in the event database, written in the transaction that stores the event, so retention leaves it in place. The count is one query per page, grouped by entrypoint, over a new events index on entrypoint_id, deleted_at, resubmitted_from_id and created_at. Resubmitted copies did not arrive on the URL and count in neither. Pre-1.0: the table and index go into the schema in place. Model: opus-5-5
198 lines
5.8 KiB
Go
198 lines
5.8 KiB
Go
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:</span>\s*<span title="` +
|
|
at.UTC().Format(time.DateTime) + ` UTC">[^<]+</span>`
|
|
}
|
|
|
|
// eventsShown matches an entrypoint row's count of n events.
|
|
func eventsShown(n int) string {
|
|
return `Events Within Retention:</span>\s*<span>` +
|
|
strconv.Itoa(n) + `</span>`
|
|
}
|
|
|
|
// 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:</span>\s*<span>never</span>`,
|
|
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)
|
|
}
|