check / check (push) Waiting to run
Each row of the recent events on the webhook page links to the
event's own page, /hook/{id}/events/{eventID}, and expands to show its
body; only the newest starts expanded. The event's page shows its
details, its whole body and every delivery with its attempts.
One renderer, newBodyView with templates/event_body.html, shows a body
in all three places: whole up to 32 KiB, cut there in the lists with a
link to the event's page, JSON pretty-printed, a body of more than 200
lines or 32 KiB in a scrolling box, and a body that is not text left
out beside its download link. A resubmitted copy links to its
original's page.
Model: opus-5-5
153 lines
4.2 KiB
Go
153 lines
4.2 KiB
Go
package handlers_test
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi"
|
|
"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/session"
|
|
)
|
|
|
|
// serveEventPage runs the real event page handler as the test user
|
|
// for the given webhook and event ids.
|
|
func serveEventPage(
|
|
t *testing.T,
|
|
h *handlers.Handlers,
|
|
sess *session.Session,
|
|
webhookID, eventID string,
|
|
) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
|
|
req := httptest.NewRequestWithContext(
|
|
context.Background(),
|
|
http.MethodGet,
|
|
"/hook/"+webhookID+"/events/"+eventID,
|
|
nil,
|
|
)
|
|
|
|
for _, c := range authenticatedCookies(
|
|
t, sess, deleteTestUserID, deleteTestUsername,
|
|
) {
|
|
req.AddCookie(c)
|
|
}
|
|
|
|
rctx := chi.NewRouteContext()
|
|
rctx.URLParams.Add(paramSourceID, webhookID)
|
|
rctx.URLParams.Add(paramEventID, eventID)
|
|
|
|
req = req.WithContext(
|
|
context.WithValue(req.Context(), chi.RouteCtxKey, rctx),
|
|
)
|
|
|
|
w := httptest.NewRecorder()
|
|
h.HandleEventDetail().ServeHTTP(w, req)
|
|
|
|
return w
|
|
}
|
|
|
|
// TestHandleEventDetail_ShowsEventWholeWithDeliveries proves the
|
|
// event's page shows its details, its whole body even past the cap
|
|
// of the lists of events, pretty-printed and in the scrolling box,
|
|
// and each delivery with its status and attempts.
|
|
func TestHandleEventDetail_ShowsEventWholeWithDeliveries(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
f := newRecentEventsFixture(t)
|
|
target := seedTarget(t, f.db, f.webhook.ID, database.TargetTypeHTTP)
|
|
|
|
const sentinel = "TAIL-SENTINEL-5b2e"
|
|
|
|
body := `{"pad":"` + strings.Repeat("x", 2*bodyCap) +
|
|
`","tail":"` + sentinel + `"}`
|
|
event := f.event(t, contentTypeJSON, body, time.Now())
|
|
f.attempt(t, f.delivery(
|
|
t, event, target.ID, database.DeliveryStatusFailed,
|
|
), http.StatusBadGateway, time.Second)
|
|
|
|
w := serveEventPage(t, f.h, f.sess, f.webhook.ID, event.ID)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
page := w.Body.String()
|
|
|
|
assert.Contains(t, page, event.ID)
|
|
assert.Contains(t, page, contentTypeJSON)
|
|
assert.Contains(t, page, strconv.Itoa(len(body))+" bytes")
|
|
assert.Contains(t, page, "{\n "pad": "xxx")
|
|
assert.Contains(t, page, ""tail": ""+sentinel+""\n}")
|
|
assert.Contains(t, page, `style="max-height: 32rem; overflow-y: auto"`)
|
|
assert.NotContains(t, page, "Showing the first")
|
|
assert.Contains(t, page, target.Name)
|
|
assert.Contains(t, page, ">failed</span>")
|
|
assert.Contains(t, page, "Status: 502")
|
|
}
|
|
|
|
// TestHandleEventDetail_ResubmitLinks proves a resubmitted copy's
|
|
// page links to its original's page, and the original's page says
|
|
// it was resubmitted.
|
|
func TestHandleEventDetail_ResubmitLinks(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
f := newRecentEventsFixture(t)
|
|
original := f.event(t, contentTypeJSON, "{}", time.Now())
|
|
|
|
cp := &database.Event{
|
|
WebhookID: f.webhook.ID,
|
|
Method: http.MethodPost,
|
|
Body: "{}",
|
|
ContentType: contentTypeJSON,
|
|
ResubmittedFromID: &original.ID,
|
|
}
|
|
require.NoError(t, f.webhookDB.Omit(clause.Associations).Create(cp).Error)
|
|
|
|
w := serveEventPage(t, f.h, f.sess, f.webhook.ID, cp.ID)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(
|
|
t, w.Body.String(),
|
|
`href="/hook/`+f.webhook.ID+`/events/`+original.ID+`"`,
|
|
)
|
|
|
|
w = serveEventPage(t, f.h, f.sess, f.webhook.ID, original.ID)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), "as 1 new event<")
|
|
}
|
|
|
|
// TestHandleEventDetail_UnknownEventNotFound proves the page is a
|
|
// 404 for an event that does not exist and for one that belongs to
|
|
// another webhook.
|
|
func TestHandleEventDetail_UnknownEventNotFound(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)
|
|
|
|
mine := seedWebhook(t, db)
|
|
theirs := seedWebhook(t, db)
|
|
|
|
seedEventWithBody(t, dbMgr, mine.ID, "{}")
|
|
elsewhere := seedEventWithBody(t, dbMgr, theirs.ID, "{}")
|
|
|
|
for _, id := range []string{"no-such-event", elsewhere.ID} {
|
|
w := serveEventPage(t, h, sess, mine.ID, id)
|
|
assert.Equal(t, http.StatusNotFound, w.Code, id)
|
|
}
|
|
}
|