lightmux-contrib/recoverer/recoverer_test.go
juancwu b26ef7439e add realip, requestlog, recoverer middlewares
Initial implementation of lightmux-contrib, a sibling module to
lightmux that hosts opinionated middlewares with one sub-package per
middleware:

- realip: resolves the originating client IP from CF-Connecting-IP,
  True-Client-IP, X-Real-IP, or X-Forwarded-For. Optional peer-CIDR
  allowlist via netip.Prefix.
- requestlog: emits a structured http.request record (method, path,
  status, duration, client) per request via splinter.
- recoverer: catches panics, wraps with errx under op "recoverer",
  logs with stack, and writes a 500 response.

Each package exposes a single New(...) constructor returning
func(http.Handler) http.Handler. The contrib module intentionally
does not import lightmux — middlewares interoperate via the standard
stdlib middleware shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 14:03:04 +00:00

70 lines
1.6 KiB
Go

package recoverer
import (
"bytes"
"errors"
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func captureLog(t *testing.T) *bytes.Buffer {
t.Helper()
var buf bytes.Buffer
orig := log.Default().Writer()
log.Default().SetOutput(&buf)
t.Cleanup(func() { log.Default().SetOutput(orig) })
return &buf
}
func TestNewCatchesStringPanic(t *testing.T) {
buf := captureLog(t)
h := New()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic("boom")
}))
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", nil))
if rr.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want 500", rr.Code)
}
out := buf.String()
for _, want := range []string{"recoverer", "panic: boom"} {
if !strings.Contains(out, want) {
t.Errorf("log missing %q\nfull: %s", want, out)
}
}
}
func TestNewWrapsErrorPanic(t *testing.T) {
buf := captureLog(t)
cause := errors.New("db down")
h := New()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic(cause)
}))
h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
out := buf.String()
if !strings.Contains(out, "recoverer: db down") {
t.Errorf("expected errx-wrapped breadcrumb, got: %s", out)
}
}
func TestNewPassesThrough(t *testing.T) {
called := false
h := New()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true
w.WriteHeader(http.StatusOK)
}))
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", nil))
if !called || rr.Code != http.StatusOK {
t.Errorf("non-panic path broken: called=%v code=%d", called, rr.Code)
}
}