check / check (push) Waiting to run
A route test now posts an oversized body with no session or CSRF token to each page route group, /settings included, and requires 413 with no CSRF cookie. Before, only the login form pinned the cap ahead of CSRF; reordering the /settings, /hooks or /hook groups failed nothing. The MaxBodySize doc comment says other methods pass uncapped on purpose, and the middleware test comment names the helper it describes. The three router helpers in the server tests build the Server through New, on a lifecycle that is never started, instead of setting its fields by hand. The README already described the cap's position correctly. Model: opus-5-5
110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
package server_test
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/webhooker/internal/server"
|
|
)
|
|
|
|
// TestResponseControllerThroughProductionRouter sets a write deadline
|
|
// and flushes through http.ResponseController, behind the shipped
|
|
// router and over a real connection, and checks that both reach
|
|
// net/http's own writer.
|
|
//
|
|
// Every middleware that wraps the writer has to let them through with
|
|
// an Unwrap method. One that does not makes the call return
|
|
// http.ErrNotSupported, or, if it has a Flush of its own that cannot
|
|
// reach further in, makes the flush silently do nothing; either way
|
|
// the handler that trips over it is far from the cause.
|
|
//
|
|
// It runs once with the defaults and once with metrics and Sentry on,
|
|
// because those two add middleware to the chain, and through both the
|
|
// global middleware and an admin page route group, which adds its own.
|
|
func TestResponseControllerThroughProductionRouter(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Without /metrics credentials, metricsConfig is the default
|
|
// Config.
|
|
cases := []struct {
|
|
name string
|
|
username, password string
|
|
sentryEnabled bool
|
|
}{
|
|
{name: "defaults"},
|
|
{
|
|
name: "metrics and Sentry on",
|
|
username: metricsUser, password: metricsAuthValue,
|
|
sentryEnabled: true,
|
|
},
|
|
}
|
|
|
|
// The probe answers with what each call returned.
|
|
probe := func(w http.ResponseWriter, _ *http.Request) {
|
|
rc := http.NewResponseController(w)
|
|
|
|
deadlineErr := rc.SetWriteDeadline(time.Now().Add(time.Minute))
|
|
flushErr := rc.Flush()
|
|
|
|
_, _ = fmt.Fprintf(
|
|
w, "deadline: %v, flush: %v", deadlineErr, flushErr,
|
|
)
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
env := newTestEnvWithConfig(
|
|
t, metricsConfig(t, tc.username, tc.password),
|
|
)
|
|
|
|
routers := map[string]http.Handler{
|
|
server.ProbePattern: server.NewRouterWithProbeForTest(
|
|
t, env.log, env.cfg, env.mw, env.hnd,
|
|
tc.sentryEnabled, probe,
|
|
),
|
|
server.PageProbePattern: server.NewRouterWithPageProbeForTest(
|
|
t, env.log, env.cfg, env.mw, env.hnd,
|
|
tc.sentryEnabled, probe,
|
|
),
|
|
}
|
|
|
|
for path, router := range routers {
|
|
srv := httptest.NewServer(router)
|
|
t.Cleanup(srv.Close)
|
|
|
|
req, err := http.NewRequestWithContext(
|
|
t.Context(), http.MethodGet, srv.URL+path, nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
resp, err := srv.Client().Do(req)
|
|
require.NoError(t, err)
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
require.NoError(t, resp.Body.Close())
|
|
|
|
assert.Equal(
|
|
t, "deadline: <nil>, flush: <nil>", string(body), path,
|
|
)
|
|
|
|
// A response the server holds until the handler returns
|
|
// goes out with a Content-Length; one flushed while the
|
|
// handler is still running goes out in chunks.
|
|
assert.Equal(
|
|
t, []string{"chunked"}, resp.TransferEncoding,
|
|
"%s: the flush must reach the client", path,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|