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( env.log.Get(), env.cfg, env.mw, env.hnd, tc.sentryEnabled, probe, ), server.PageProbePattern: server.NewRouterWithPageProbeForTest( env.log.Get(), 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: , flush: ", 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, ) } }) } }