check / check (push) Successful in 4m23s
The webhook page and everything under it move from /source/ID to /hook/ID, the list and new-webhook form to /hooks and /hooks/new, and the event log from .../logs to /hook/ID/events, body download included. Entrypoint URLs move from /webhook/UUID to /h/UUID, and the webhook page shows only that form. The old paths are gone. Links, redirects, form actions, tests, comments and the README follow. Go identifiers and template file names are unchanged. A new route test posts to the entrypoint URL the webhook page shows and checks that the receiver rate limit applies to it. Model: opus-5-5
145 lines
3.9 KiB
Go
145 lines
3.9 KiB
Go
package handlers_test
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
"sneak.berlin/go/webhooker/internal/handlers"
|
|
"sneak.berlin/go/webhooker/internal/session"
|
|
)
|
|
|
|
// deletedMarker is the suffix the event log appends to the name
|
|
// of a target that no longer exists.
|
|
const deletedMarker = " (deleted)"
|
|
|
|
// deleteTargetThroughHandler removes a target through the real
|
|
// deletion handler, so the test soft-deletes exactly the way the
|
|
// UI does rather than by writing the timestamp itself.
|
|
func deleteTargetThroughHandler(
|
|
t *testing.T,
|
|
h *handlers.Handlers,
|
|
sess *session.Session,
|
|
webhookID, targetID string,
|
|
) {
|
|
t.Helper()
|
|
|
|
req := postRequest(
|
|
"/hook/"+webhookID+"/targets/"+targetID+"/delete",
|
|
authenticatedCookies(
|
|
t, sess, deleteTestUserID, deleteTestUsername,
|
|
),
|
|
map[string]string{
|
|
paramSourceID: webhookID,
|
|
paramTargetID: targetID,
|
|
},
|
|
)
|
|
w := httptest.NewRecorder()
|
|
|
|
h.HandleTargetDelete().ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusSeeOther, w.Code)
|
|
}
|
|
|
|
// TestHandleSourceLogs_NamesDeletedTarget proves a delivery
|
|
// produced by a since-deleted target still names it on the event
|
|
// log, marked as deleted.
|
|
//
|
|
// Deletes are soft and deliveries carry no foreign key to the
|
|
// target row, so the history outlives the target. Against a
|
|
// scoped lookup the delivery resolves to a zero view and the page
|
|
// renders ": delivered" with nothing saying what it was delivered
|
|
// to.
|
|
func TestHandleSourceLogs_NamesDeletedTarget(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 := seedTarget(t, db, wh.ID, database.TargetTypeLog)
|
|
|
|
seedDeliveredEvent(t, dbMgr, wh.ID, tgt.ID)
|
|
|
|
// The control: the name is on the page while the target
|
|
// lives, and is not yet marked as deleted.
|
|
before := renderSourceLogsPage(t, h, sess, wh.ID)
|
|
assert.Contains(t, before, tgt.Name)
|
|
assert.NotContains(t, before, tgt.Name+deletedMarker)
|
|
|
|
deleteTargetThroughHandler(t, h, sess, wh.ID, tgt.ID)
|
|
|
|
after := renderSourceLogsPage(t, h, sess, wh.ID)
|
|
|
|
assert.Contains(
|
|
t, after, tgt.Name+deletedMarker,
|
|
"a delivery from a deleted target must keep its name, "+
|
|
"marked as no longer existing",
|
|
)
|
|
assert.Contains(
|
|
t, after, "delivered",
|
|
"the delivery history itself must survive the delete",
|
|
)
|
|
}
|
|
|
|
// TestHandleSourceLogs_MasksDeletedTargetConfig proves that
|
|
// naming a deleted target does not widen what the page shows of
|
|
// it: its stored configuration stays masked by exactly the rules
|
|
// a live target's is.
|
|
//
|
|
// The lookup behind the name reads soft-deleted rows, so it
|
|
// carries a full target row — credential blob included — into the
|
|
// place a zero value used to sit. The projection to TargetView is
|
|
// what keeps that blob away from the template, and it must hold
|
|
// for a deleted row too.
|
|
func TestHandleSourceLogs_MasksDeletedTargetConfig(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)
|
|
deleteTargetThroughHandler(t, h, sess, 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 name is there; only the credential is not.
|
|
assert.Contains(t, body, tgt.Name+deletedMarker)
|
|
}
|