package server_test import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/getsentry/sentry-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/webhooker/internal/server" ) // sentrySecretSegment is the path segment of a Slack incoming-webhook // URL — the part that is the bearer credential. No Sentry event may // carry it. const sentrySecretSegment = "T00000000/B00000000/QQSENTRYSECRETQQ" // sentryEventFor builds the event Sentry would ship for a request // carrying the given raw query, using the SDK's own request // conversion rather than a hand-built Request, so the test tracks // what the SDK actually collects. func sentryEventFor(t *testing.T, rawQuery string) *sentry.Event { t.Helper() req := httptest.NewRequestWithContext( context.Background(), http.MethodPost, "/source/abc/targets?"+rawQuery, nil, ) event := sentry.NewEvent() event.Request = sentry.NewRequest(req) return event } // TestSentryScrub_QueryStringIsCollectedUnscrubbed pins the reason the // hook exists: the SDK copies the raw query into the event on its own, // independently of the access log, which records only the route // pattern or a redacted query for the same request. func TestSentryScrub_QueryStringIsCollectedUnscrubbed(t *testing.T) { t.Parallel() event := sentryEventFor( t, "url=https://hooks.slack.com/services/"+sentrySecretSegment, ) require.Contains( t, event.Request.QueryString, sentrySecretSegment, "the SDK is expected to collect the raw query; "+ "if it no longer does, the scrub hook's premise changed", ) } // TestSentryScrub_RedactsQueryString is the regression test: the hook // installed on both BeforeSend and BeforeSendTransaction must leave no // byte of the query in the event that goes off-host. func TestSentryScrub_RedactsQueryString(t *testing.T) { t.Parallel() event := sentryEventFor( t, "url=https://hooks.slack.com/services/"+sentrySecretSegment, ) scrubbed := server.ScrubSentryRequestForTest(event, nil) require.NotNil(t, scrubbed) encoded, err := json.Marshal(scrubbed) require.NoError(t, err) assert.NotContains(t, string(encoded), sentrySecretSegment) assert.NotContains(t, string(encoded), "hooks.slack.com") } // TestSentryScrub_KeepsTheRoutingContext checks the hook does not cost // the debugging signal: the path still identifies the failing route. func TestSentryScrub_KeepsTheRoutingContext(t *testing.T) { t.Parallel() event := sentryEventFor(t, "page=2") scrubbed := server.ScrubSentryRequestForTest(event, nil) require.NotNil(t, scrubbed) assert.Contains(t, scrubbed.Request.URL, "/source/abc/targets") assert.Equal(t, http.MethodPost, scrubbed.Request.Method) } // TestSentryScrub_ToleratesEventsWithoutARequest covers the events the // hook sees outside an HTTP handler, where no request is attached. func TestSentryScrub_ToleratesEventsWithoutARequest(t *testing.T) { t.Parallel() event := sentry.NewEvent() scrubbed := server.ScrubSentryRequestForTest(event, nil) require.NotNil(t, scrubbed) assert.Nil(t, scrubbed.Request) assert.Nil(t, server.ScrubSentryRequestForTest(nil, nil)) }