Replaces #559, which conflicted. Model: opus-5-5
This commit is contained in:
+104
-18
@@ -1,12 +1,18 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/fx/fxtest"
|
||||
"sneak.berlin/go/webhooker/internal/config"
|
||||
"sneak.berlin/go/webhooker/internal/handlers"
|
||||
"sneak.berlin/go/webhooker/internal/logger"
|
||||
"sneak.berlin/go/webhooker/internal/middleware"
|
||||
)
|
||||
|
||||
@@ -33,23 +39,71 @@ func SentryClientOptionsForTest(
|
||||
return sentryClientOptions(dsn, release)
|
||||
}
|
||||
|
||||
// CleanShutdownForTest runs the server's stop hook, cleanShutdown,
|
||||
// against hs: a server the test started itself, so it can hold a
|
||||
// request open across the drain. Sentry is off.
|
||||
func CleanShutdownForTest(ctx context.Context, hs *http.Server) {
|
||||
s := &Server{log: slog.New(slog.DiscardHandler), httpServer: hs}
|
||||
s.cleanShutdown(ctx)
|
||||
}
|
||||
|
||||
// newServerForTest builds a Server through New, as the application
|
||||
// does, on a lifecycle that is never started: the hooks New adds to
|
||||
// it never run, so nothing listens.
|
||||
func newServerForTest(
|
||||
t *testing.T,
|
||||
log *logger.Logger,
|
||||
cfg *config.Config,
|
||||
mw *middleware.Middleware,
|
||||
h *handlers.Handlers,
|
||||
) *Server {
|
||||
t.Helper()
|
||||
|
||||
s, err := New(fxtest.NewLifecycle(t), ServerParams{
|
||||
Logger: log,
|
||||
Config: cfg,
|
||||
Middleware: mw,
|
||||
Handlers: h,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// NewRouterForTest builds the real route tree via SetupRoutes with
|
||||
// the supplied middleware and handlers, bypassing the fx lifecycle
|
||||
// and the HTTP listener. Tests use it so that route-group middleware
|
||||
// registration order is exercised exactly as it ships, rather than
|
||||
// against a hand-rebuilt chain that could drift from routes.go.
|
||||
// the supplied middleware and handlers, on a Server from New whose
|
||||
// lifecycle is never started, so no HTTP listener runs. Tests use it
|
||||
// so that route-group middleware registration order is exercised
|
||||
// exactly as it ships, rather than against a hand-rebuilt chain that
|
||||
// could drift from routes.go.
|
||||
func NewRouterForTest(
|
||||
log *slog.Logger,
|
||||
t *testing.T,
|
||||
log *logger.Logger,
|
||||
cfg *config.Config,
|
||||
mw *middleware.Middleware,
|
||||
h *handlers.Handlers,
|
||||
) http.Handler {
|
||||
s := &Server{
|
||||
log: log,
|
||||
mw: mw,
|
||||
h: h,
|
||||
params: ServerParams{Config: cfg},
|
||||
}
|
||||
t.Helper()
|
||||
|
||||
s := newServerForTest(t, log, cfg, mw, h)
|
||||
s.SetupRoutes()
|
||||
|
||||
return s.router
|
||||
}
|
||||
|
||||
// NewRouterWithSentryForTest is NewRouterForTest with the sentryhttp
|
||||
// handler registered, as a set SENTRY_DSN does in production.
|
||||
func NewRouterWithSentryForTest(
|
||||
t *testing.T,
|
||||
log *logger.Logger,
|
||||
cfg *config.Config,
|
||||
mw *middleware.Middleware,
|
||||
h *handlers.Handlers,
|
||||
) http.Handler {
|
||||
t.Helper()
|
||||
|
||||
s := newServerForTest(t, log, cfg, mw, h)
|
||||
s.sentryEnabled.Store(true)
|
||||
s.SetupRoutes()
|
||||
|
||||
return s.router
|
||||
@@ -82,22 +136,54 @@ const ProbePattern = "/probe"
|
||||
// option and the recoverer registered outside it is the thing a test
|
||||
// has to be able to pin.
|
||||
func NewRouterWithProbeForTest(
|
||||
log *slog.Logger,
|
||||
t *testing.T,
|
||||
log *logger.Logger,
|
||||
cfg *config.Config,
|
||||
mw *middleware.Middleware,
|
||||
h *handlers.Handlers,
|
||||
sentryEnabled bool,
|
||||
probe http.HandlerFunc,
|
||||
) http.Handler {
|
||||
s := &Server{
|
||||
log: log,
|
||||
mw: mw,
|
||||
h: h,
|
||||
params: ServerParams{Config: cfg},
|
||||
}
|
||||
t.Helper()
|
||||
|
||||
s := newServerForTest(t, log, cfg, mw, h)
|
||||
s.sentryEnabled.Store(sentryEnabled)
|
||||
s.SetupRoutes()
|
||||
s.router.Handle(ProbePattern, probe)
|
||||
|
||||
return s.router
|
||||
}
|
||||
|
||||
// PageProbePattern is where NewRouterWithPageProbeForTest serves its
|
||||
// probe: inside the /pages route group, the admin page group a
|
||||
// request reaches without signing in.
|
||||
const PageProbePattern = "/pages/probe"
|
||||
|
||||
// NewRouterWithPageProbeForTest is NewRouterWithProbeForTest with the
|
||||
// probe added to the /pages route group once SetupRoutes has built
|
||||
// it, so the probe runs behind that group's own middleware exactly as
|
||||
// the group's real routes do.
|
||||
func NewRouterWithPageProbeForTest(
|
||||
t *testing.T,
|
||||
log *logger.Logger,
|
||||
cfg *config.Config,
|
||||
mw *middleware.Middleware,
|
||||
h *handlers.Handlers,
|
||||
sentryEnabled bool,
|
||||
probe http.HandlerFunc,
|
||||
) http.Handler {
|
||||
t.Helper()
|
||||
|
||||
s := newServerForTest(t, log, cfg, mw, h)
|
||||
s.sentryEnabled.Store(sentryEnabled)
|
||||
s.SetupRoutes()
|
||||
|
||||
for _, route := range s.router.Routes() {
|
||||
pages, ok := route.SubRoutes.(chi.Router)
|
||||
if ok && route.Pattern == "/pages/*" {
|
||||
pages.Get("/probe", probe)
|
||||
}
|
||||
}
|
||||
|
||||
return s.router
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user