package handlers_test import ( "context" "fmt" "net/http" "net/http/httptest" "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" ) // contentTypeJSON is the content type the seeded events in this // package carry. Shared across the seed helpers so the literal // appears once. const contentTypeJSON = "application/json" // seedDeliveredEvent records an event and a delivery for it in // the webhook's own database, so the log page has a delivery // to render against the target. func seedDeliveredEvent( t *testing.T, dbMgr *database.WebhookDBManager, webhookID, targetID string, ) { t.Helper() webhookDB, err := dbMgr.GetDB(webhookID) require.NoError(t, err) event := &database.Event{ WebhookID: webhookID, Method: http.MethodPost, Body: `{"test":true}`, ContentType: contentTypeJSON, } require.NoError(t, webhookDB.Omit( clause.Associations, ).Create(event).Error) dlv := &database.Delivery{ EventID: event.ID, TargetID: targetID, Status: database.DeliveryStatusDelivered, } require.NoError(t, webhookDB.Omit( clause.Associations, ).Create(dlv).Error) } // renderSourceLogsPage runs the real event log handler for a // webhook and returns the rendered HTML. func renderSourceLogsPage( t *testing.T, h *handlers.Handlers, sess *session.Session, webhookID string, ) string { t.Helper() return renderSourceLogsPageWithQuery( t, h, sess, webhookID, "", ) } // renderSourceLogsPageWithQuery is renderSourceLogsPage over a // caller-supplied query string, for the page state a redirect back to // the log carries in one. func renderSourceLogsPageWithQuery( t *testing.T, h *handlers.Handlers, sess *session.Session, webhookID, query string, ) string { t.Helper() req := httptest.NewRequestWithContext( context.Background(), http.MethodGet, "/hook/"+webhookID+"/events"+query, nil, ) for _, c := range authenticatedCookies( t, sess, deleteTestUserID, deleteTestUsername, ) { req.AddCookie(c) } rctx := chi.NewRouteContext() rctx.URLParams.Add(paramSourceID, webhookID) req = req.WithContext( context.WithValue( req.Context(), chi.RouteCtxKey, rctx, ), ) w := httptest.NewRecorder() h.HandleSourceLogs().ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) return w.Body.String() } // TestHandleSourceLogs_MasksSlackWebhookURL proves the event // log page is handed a display-safe projection of each target // rather than the stored row, so the credential cannot be // rendered from its template data. func TestHandleSourceLogs_MasksSlackWebhookURL(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) tgt := seedConfiguredTarget( t, db, wh.ID, database.TargetTypeSlack, `{"webhookUrl":"`+slackWebhookURL+`"}`, ) seedDeliveredEvent(t, dbMgr, wh.ID, tgt.ID) body := renderSourceLogsPage(t, h, sess, wh.ID) assert.NotContains(t, body, slackSecretPath) assert.NotContains(t, body, "T00000000") assert.NotContains(t, body, "B00000000") assert.NotContains( t, body, "XXXXXXXXXXXXXXXXXXXXXXXX", ) assert.NotContains(t, body, "webhookUrl") // The page still identifies the delivery's target. assert.Contains(t, body, tgt.Name) assert.Contains(t, body, "delivered") } // TestHandleSourceLogs_ShowsFiftyNewestEvents proves the event log // holds the 50 newest events, newest first, and not one more, and says // how many events there are in all. func TestHandleSourceLogs_ShowsFiftyNewestEvents(t *testing.T) { t.Parallel() f := newRecentEventsFixture(t) base := time.Now().Add(-time.Hour) for i := range 51 { f.event( t, fmt.Sprintf("application/x-log-%02d", i), "{}", base.Add(time.Duration(i)*time.Second), ) } body := renderSourceLogsPage(t, f.h, f.sess, f.webhook.ID) assert.Equal(t, 50, strings.Count(body, `role="button"`)) assert.NotContains(t, body, "application/x-log-00") assert.Contains(t, body, "application/x-log-01") assert.Less( t, strings.Index(body, "application/x-log-50"), strings.Index(body, "application/x-log-49"), ) assert.Contains(t, body, "50 most recent of 51 events") } // TestHandleSourceLogs_OnlyNewestStartsExpanded proves that of the // events in the log only the newest starts expanded. func TestHandleSourceLogs_OnlyNewestStartsExpanded(t *testing.T) { t.Parallel() f := newRecentEventsFixture(t) now := time.Now() f.event(t, "application/x-older", "{}", now.Add(-time.Minute)) f.event(t, "application/x-newer", "{}", now) body := renderSourceLogsPage(t, f.h, f.sess, f.webhook.ID) assert.Equal(t, 1, strings.Count(body, " data-open>")) open := strings.Index(body, " data-open>") newer := strings.Index(body, "application/x-newer") older := strings.Index(body, "application/x-older") assert.Less(t, open, newer, "the newest event is not the open one") assert.Less(t, newer, older) }