check / check (push) Successful in 6m32s
The receiver keeps the query string of the request it received on the event, in a new `raw_query` column of the per-webhook `events` table; a resubmitted copy carries its original's. The event log and the event's page show it with the request, the event log leaving out one over 32 KiB as it does request headers. The archive and log targets carry it. An HTTP target gets a `forwardQuery` setting, off by default, on both target forms and in the target list: on, each delivery, replays and resubmits included, appends the query string to the target URL, joined with `&` to one it already has. Model: opus-5-5
173 lines
4.3 KiB
Go
173 lines
4.3 KiB
Go
package delivery_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
"sneak.berlin/go/webhooker/internal/delivery"
|
|
)
|
|
|
|
// eventQuery is the query string the events in these tests arrived
|
|
// with.
|
|
const eventQuery = "a=1&b=2"
|
|
|
|
// httpTargetConfig is the stored configuration of an HTTP target at
|
|
// targetURL.
|
|
func httpTargetConfig(
|
|
t *testing.T, targetURL string, forwardQuery bool,
|
|
) string {
|
|
t.Helper()
|
|
|
|
cfg, err := json.Marshal(delivery.HTTPTargetConfig{
|
|
URL: targetURL, ForwardQuery: forwardQuery,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return string(cfg)
|
|
}
|
|
|
|
// deliverWithQuery sends one event that arrived with eventQuery to an
|
|
// HTTP target configured with cfg, through the path a received event's
|
|
// delivery takes, and returns the attempt it recorded.
|
|
func deliverWithQuery(t *testing.T, cfg string) database.DeliveryResult {
|
|
t.Helper()
|
|
|
|
s := newISetup(t)
|
|
|
|
event := iSeedEvent(t, s.WebhookDB, s.WebhookID, "{}")
|
|
d := iSeedDelivery(
|
|
t, s.WebhookDB, event.ID, uuid.NewString(),
|
|
database.DeliveryStatusPending,
|
|
)
|
|
task := iTask(
|
|
d, event, s.WebhookID, d.TargetID, "query", cfg, 0, 1, &event.Body,
|
|
)
|
|
task.RawQuery = eventQuery
|
|
|
|
s.Engine.ExportProcessNewTask(context.TODO(), &task)
|
|
|
|
var result database.DeliveryResult
|
|
|
|
require.NoError(t, s.WebhookDB.Where(
|
|
"delivery_id = ?", d.ID,
|
|
).First(&result).Error)
|
|
|
|
return result
|
|
}
|
|
|
|
// TestDeliverHTTP_ForwardQuery proves the URL a delivery is sent to:
|
|
// with the target's setting off, the target URL exactly as configured;
|
|
// with it on, the event's query string appended, joined with "&" to a
|
|
// query string the target URL already has.
|
|
func TestDeliverHTTP_ForwardQuery(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// The target URL's path, without and with a query string of its
|
|
// own.
|
|
const (
|
|
plain = "/in"
|
|
withQuery = "/in?key=k"
|
|
)
|
|
|
|
tests := map[string]struct {
|
|
path string
|
|
forwardQuery bool
|
|
want string
|
|
}{
|
|
"off": {
|
|
path: plain, want: plain,
|
|
},
|
|
"off, the target URL has a query string": {
|
|
path: withQuery, want: withQuery,
|
|
},
|
|
"on": {
|
|
path: plain, forwardQuery: true, want: plain + "?" + eventQuery,
|
|
},
|
|
"on, the target URL has a query string": {
|
|
path: withQuery, forwardQuery: true,
|
|
want: withQuery + "&" + eventQuery,
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
received := make(chan string, 1)
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
received <- r.RequestURI
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
},
|
|
))
|
|
t.Cleanup(ts.Close)
|
|
|
|
result := deliverWithQuery(t, httpTargetConfig(
|
|
t, ts.URL+tc.path, tc.forwardQuery,
|
|
))
|
|
|
|
assert.True(t, result.Success)
|
|
require.Len(t, received, 1)
|
|
assert.Equal(t, tc.want, <-received)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDeliverHTTP_ForwardedQueryKeepsTheTargetURLMasked proves the
|
|
// credential in a target URL's own query string stays masked once the
|
|
// event's query string is appended to it: in a response or error that
|
|
// echoes the URL the target was sent, as the event log's Redactor shows
|
|
// it, and in the error a failed connection stores.
|
|
func TestDeliverHTTP_ForwardedQueryKeepsTheTargetURLMasked(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const secret = "s3cr3t"
|
|
|
|
received := make(chan string, 1)
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
received <- r.RequestURI
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
},
|
|
))
|
|
t.Cleanup(ts.Close)
|
|
|
|
target := &database.Target{
|
|
Type: database.TargetTypeHTTP,
|
|
Config: httpTargetConfig(t, ts.URL+"/in?token="+secret, true),
|
|
}
|
|
|
|
deliverWithQuery(t, target.Config)
|
|
require.Len(t, received, 1)
|
|
|
|
sent := <-received
|
|
require.Equal(t, "/in?token="+secret+"&"+eventQuery, sent)
|
|
|
|
redactor := delivery.NewRedactor(target)
|
|
|
|
for _, echoed := range []string{sent, ts.URL + sent} {
|
|
shown := redactor.Redact("rejected " + echoed)
|
|
assert.NotContains(t, shown, secret, echoed)
|
|
assert.Contains(t, shown, delivery.RedactionMarker, echoed)
|
|
}
|
|
|
|
// Nothing listens on port 1.
|
|
failed := deliverWithQuery(t, httpTargetConfig(
|
|
t, "http://127.0.0.1:1/in?token="+secret, true,
|
|
))
|
|
require.NotEmpty(t, failed.Error)
|
|
assert.NotContains(t, failed.Error, secret)
|
|
assert.NotContains(t, failed.Error, eventQuery)
|
|
}
|