check / check (push) Successful in 4m45s
The receiver dropped the query string of every request it received, so a sender's URL parameters were silently lost. Each event now keeps it, as sent, 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 in the shared request block, the event log leaving out one over 32 KiB with a link, as for headers. The archive and log targets carry it. HTTP targets gain "Pass the query string on to this target", off by default: on, deliveries, replays and resubmits append it to the target URL, joined with `&` to one already there. The access log still hides it. 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)
|
|
}
|