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 ` + name + `` } // 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": {"hi"}, }, time.Now()) olderShows := func(t *testing.T, page string) { t.Helper() assert.Contains(t, page, arrivedAt("Billing sender")) assert.Contains(t, page, "
User-Agent: shop/1
") assert.Contains(t, page, "
X-Shop-Event: order.created
") 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, "
X-Shop-Event: order.paid
") assert.Contains(t, page, "
X-Note: <b>hi</b>
") assert.NotContains(t, page, "hi") 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") }