Add comprehensive test coverage for server/ package
All checks were successful
CI / test (push) Successful in 30s
All checks were successful
CI / test (push) Successful in 30s
Cover edge cases: statusWriter multi-call/default/unwrap, UUID v4 format and uniqueness, non-string panics, recovery body and log attributes, 4xx log level, default status in logging, request ID propagation, server defaults/options/listen-error/multiple-hooks/logger, router groups with empty prefix/inherited middleware/ordering/path params/ isolation, mount trailing slash, health content-type and POST rejection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -140,4 +140,197 @@ func TestRouterMount(t *testing.T) {
|
||||
t.Fatalf("got body %q, want %q", body, "info")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("mount with trailing slash", func(t *testing.T) {
|
||||
sub := http.NewServeMux()
|
||||
sub.HandleFunc("GET /data", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("data"))
|
||||
})
|
||||
|
||||
r := server.NewRouter()
|
||||
r.Mount("/sub/", sub)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/sub/data", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
body, _ := io.ReadAll(w.Body)
|
||||
if string(body) != "data" {
|
||||
t.Fatalf("got body %q, want %q", body, "data")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouter_PatternWithoutMethod(t *testing.T) {
|
||||
r := server.NewRouter()
|
||||
r.HandleFunc("/static/", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("static"))
|
||||
})
|
||||
|
||||
for _, method := range []string{http.MethodGet, http.MethodPost} {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(method, "/static/file.css", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("%s /static/file.css: got status %d, want %d", method, w.Code, http.StatusOK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_GroupEmptyPrefix(t *testing.T) {
|
||||
r := server.NewRouter()
|
||||
g := r.Group("")
|
||||
g.HandleFunc("GET /hello", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/hello", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("got status %d, want %d", w.Code, http.StatusOK)
|
||||
}
|
||||
if body := w.Body.String(); body != "hello" {
|
||||
t.Fatalf("got body %q, want %q", body, "hello")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_GroupInheritsMiddleware(t *testing.T) {
|
||||
var order []string
|
||||
|
||||
parentMW := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
order = append(order, "parent")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
childMW := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
order = append(order, "child")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
r := server.NewRouter()
|
||||
parent := r.Group("/api", parentMW)
|
||||
child := parent.Group("/v1", childMW)
|
||||
child.HandleFunc("GET /items", func(w http.ResponseWriter, _ *http.Request) {
|
||||
order = append(order, "handler")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/items", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("got status %d, want %d", w.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
expected := []string{"parent", "child", "handler"}
|
||||
if len(order) != len(expected) {
|
||||
t.Fatalf("got %v, want %v", order, expected)
|
||||
}
|
||||
for i, v := range expected {
|
||||
if order[i] != v {
|
||||
t.Fatalf("order[%d] = %q, want %q", i, order[i], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_GroupMiddlewareOrder(t *testing.T) {
|
||||
var order []string
|
||||
|
||||
mwA := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
order = append(order, "A")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
mwB := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
order = append(order, "B")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
r := server.NewRouter()
|
||||
g := r.Group("/api", mwA)
|
||||
sub := g.Group("/v1", mwB)
|
||||
sub.HandleFunc("GET /test", func(w http.ResponseWriter, _ *http.Request) {
|
||||
order = append(order, "handler")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// Parent MW (A) should run before child MW (B), then handler.
|
||||
expected := []string{"A", "B", "handler"}
|
||||
if len(order) != len(expected) {
|
||||
t.Fatalf("got %v, want %v", order, expected)
|
||||
}
|
||||
for i, v := range expected {
|
||||
if order[i] != v {
|
||||
t.Fatalf("order[%d] = %q, want %q", i, order[i], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_PathParamWithGroup(t *testing.T) {
|
||||
r := server.NewRouter()
|
||||
api := r.Group("/api")
|
||||
api.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = w.Write([]byte("id=" + req.PathValue("id")))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/users/42", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("got status %d, want %d", w.Code, http.StatusOK)
|
||||
}
|
||||
if body := w.Body.String(); body != "id=42" {
|
||||
t.Fatalf("got body %q, want %q", body, "id=42")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_MiddlewareNotAppliedToOtherRoutes(t *testing.T) {
|
||||
var mwCalled bool
|
||||
mw := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
mwCalled = true
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
r := server.NewRouter()
|
||||
|
||||
// Add middleware only to /admin group.
|
||||
admin := r.Group("/admin", mw)
|
||||
admin.HandleFunc("GET /dashboard", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("admin"))
|
||||
})
|
||||
|
||||
// Route outside the group.
|
||||
r.HandleFunc("GET /public", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("public"))
|
||||
})
|
||||
|
||||
// Request to /public should NOT trigger group middleware.
|
||||
mwCalled = false
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/public", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if mwCalled {
|
||||
t.Fatal("group middleware should not be called for routes outside the group")
|
||||
}
|
||||
if w.Body.String() != "public" {
|
||||
t.Fatalf("got body %q, want %q", w.Body.String(), "public")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user