Files
webhooker/internal/handlers/event_request_test.go
T
sneak 5bfe5e74bd
check / check (push) Successful in 3m19s
Show an event's entrypoint and request headers (closes #389)
An expanded event in the event log and the event's own page now show
the entrypoint the event arrived at (its description, "Entrypoint"
when it has none, or "deleted entrypoint"; never its URL) and the
full request headers, one per line, sorted by name. Both pages draw
them through one shared template, event_request.html. The event
columns now load entrypoint_id and headers, and the webhook's
entrypoints are read once per page.

Model: opus-5-5
2026-10-03 01:19:09 +00:00

162 lines
4.6 KiB
Go

package handlers_test
import (
"encoding/json"
"net/http"
"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"
)
// arrivedAt is how a page names the entrypoint an event arrived at.
func arrivedAt(name string) string {
return `Arrived at <span class="text-gray-900">` + name + `</span>`
}
// entrypoint records one of the fixture webhook's entrypoints.
func (f *recentEventsFixture) entrypoint(
t *testing.T, description string,
) *database.Entrypoint {
t.Helper()
ep := &database.Entrypoint{
WebhookID: f.webhook.ID,
Path: uuid.NewString(),
Description: description,
Active: true,
}
require.NoError(t, f.db.DB().Omit(clause.Associations).Create(ep).Error)
return ep
}
// eventAt records an event that arrived at the entrypoint with the
// given request headers, stored as the receiver stores them.
func (f *recentEventsFixture) eventAt(
t *testing.T,
ep *database.Entrypoint,
headers http.Header,
receivedAt time.Time,
) *database.Event {
t.Helper()
headersJSON, err := json.Marshal(headers)
require.NoError(t, err)
event := &database.Event{
WebhookID: f.webhook.ID,
EntrypointID: ep.ID,
Method: http.MethodPost,
Headers: string(headersJSON),
Body: "{}",
BodyBytes: 2,
ContentType: contentTypeJSON,
}
event.CreatedAt = receivedAt
require.NoError(t, f.webhookDB.Omit(
clause.Associations,
).Create(event).Error)
return event
}
// TestEventRequest_EachEventShowsItsOwnEntrypointAndHeaders proves two
// events that arrived at two entrypoints each show their own
// entrypoint and request headers, in the event log and on their own
// pages, with the headers sorted by name and escaped, and never the
// entrypoint's URL.
func TestEventRequest_EachEventShowsItsOwnEntrypointAndHeaders(
t *testing.T,
) {
t.Parallel()
f := newRecentEventsFixture(t)
billing := f.entrypoint(t, "Billing sender")
unnamed := f.entrypoint(t, "")
older := f.eventAt(t, billing, http.Header{
"X-Shop-Event": {"order.created"},
"User-Agent": {"shop/1"},
}, time.Now().Add(-time.Minute))
newer := f.eventAt(t, unnamed, http.Header{
"X-Shop-Event": {"order.paid"},
"X-Note": {"<b>hi</b>"},
}, time.Now())
olderShows := func(t *testing.T, page string) {
t.Helper()
assert.Contains(t, page, arrivedAt("Billing sender"))
assert.Contains(t, page, "<div>User-Agent: shop/1</div>")
assert.Contains(t, page, "<div>X-Shop-Event: order.created</div>")
assert.Less(t,
strings.Index(page, "User-Agent: shop/1"),
strings.Index(page, "X-Shop-Event: order.created"),
"headers are sorted by name",
)
assert.NotContains(t, page, "order.paid")
assert.NotContains(t, page, billing.Path)
}
newerShows := func(t *testing.T, page string) {
t.Helper()
assert.Contains(t, page, arrivedAt("Entrypoint"))
assert.Contains(t, page, "<div>X-Shop-Event: order.paid</div>")
assert.Contains(t, page, "<div>X-Note: &lt;b&gt;hi&lt;/b&gt;</div>")
assert.NotContains(t, page, "<b>hi</b>")
assert.NotContains(t, page, "order.created")
assert.NotContains(t, page, unnamed.Path)
}
// The log lists the newer event first, so everything between
// the two events' first mentions belongs to the newer one.
_, rest, found := strings.Cut(renderSourceLogsPage(
t, f.h, f.sess, f.webhook.ID,
), newer.ID)
require.True(t, found)
newerPart, olderPart, found := strings.Cut(rest, older.ID)
require.True(t, found)
newerShows(t, newerPart)
olderShows(t, olderPart)
w := serveEventPage(t, f.h, f.sess, f.webhook.ID, newer.ID)
require.Equal(t, http.StatusOK, w.Code)
newerShows(t, w.Body.String())
w = serveEventPage(t, f.h, f.sess, f.webhook.ID, older.ID)
require.Equal(t, http.StatusOK, w.Code)
olderShows(t, w.Body.String())
}
// TestEventRequest_DeletedEntrypoint proves an event whose entrypoint
// has since been deleted says so in the event log and on its own page.
func TestEventRequest_DeletedEntrypoint(t *testing.T) {
t.Parallel()
f := newRecentEventsFixture(t)
ep := f.entrypoint(t, "Retired sender")
event := f.eventAt(t, ep, nil, time.Now())
require.NoError(t, f.db.DB().Delete(ep).Error)
page := renderSourceLogsPage(t, f.h, f.sess, f.webhook.ID)
assert.Contains(t, page, arrivedAt("deleted entrypoint"))
assert.NotContains(t, page, "Retired sender")
w := serveEventPage(t, f.h, f.sess, f.webhook.ID, event.ID)
require.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), arrivedAt("deleted entrypoint"))
assert.NotContains(t, w.Body.String(), "Retired sender")
}