check / check (push) Successful in 3m19s
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
162 lines
4.6 KiB
Go
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: <b>hi</b></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")
|
|
}
|