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( "/source/"+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) }