Add comprehensive test coverage for server/ package
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:
2026-03-21 13:55:22 +03:00
parent cea75d198b
commit 7fae6247d5
4 changed files with 680 additions and 0 deletions

View File

@@ -1,9 +1,12 @@
package server_test
import (
"bytes"
"context"
"io"
"log/slog"
"net/http"
"strings"
"testing"
"time"
@@ -117,6 +120,140 @@ func TestServerWithMiddleware(t *testing.T) {
})
}
func TestServerDefaults(t *testing.T) {
var buf bytes.Buffer
logger := slog.New(slog.NewTextHandler(&buf, nil))
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
srv := server.New(handler, append(server.Defaults(logger), server.WithAddr(":0"))...)
go func() { _ = srv.ListenAndServe() }()
waitForAddr(t, srv)
resp, err := http.Get("http://" + srv.Addr())
if err != nil {
t.Fatalf("GET failed: %v", err)
}
resp.Body.Close()
// Defaults includes RequestID middleware, so response should have X-Request-Id.
if resp.Header.Get("X-Request-Id") == "" {
t.Fatal("expected X-Request-Id header from Defaults middleware")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
}
func TestServerListenError(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
// Use an invalid address to trigger a listen error.
srv := server.New(handler, server.WithAddr(":-1"))
err := srv.ListenAndServe()
if err == nil {
t.Fatal("expected error from invalid address, got nil")
}
}
func TestServerMultipleOnShutdownHooks(t *testing.T) {
var calls []int
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
srv := server.New(handler,
server.WithAddr(":0"),
server.WithOnShutdown(func() { calls = append(calls, 1) }),
server.WithOnShutdown(func() { calls = append(calls, 2) }),
server.WithOnShutdown(func() { calls = append(calls, 3) }),
)
go func() { _ = srv.ListenAndServe() }()
waitForAddr(t, srv)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
t.Fatalf("shutdown failed: %v", err)
}
if len(calls) != 3 {
t.Fatalf("expected 3 hooks called, got %d: %v", len(calls), calls)
}
}
func TestServerShutdownWithLogger(t *testing.T) {
var buf bytes.Buffer
logger := slog.New(slog.NewTextHandler(&buf, nil))
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
srv := server.New(handler,
server.WithAddr(":0"),
server.WithLogger(logger),
)
errCh := make(chan error, 1)
go func() { errCh <- srv.ListenAndServe() }()
waitForAddr(t, srv)
// Send SIGINT to trigger graceful shutdown via ListenAndServe's signal handler.
// Instead, use Shutdown directly and check log from server start.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
// The server logs "server started" on ListenAndServe.
logOutput := buf.String()
if !strings.Contains(logOutput, "server started") {
t.Fatalf("expected 'server started' in log, got %q", logOutput)
}
}
func TestServerOptions(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
// Verify options don't panic and server starts correctly.
srv := server.New(handler,
server.WithAddr(":0"),
server.WithReadTimeout(5*time.Second),
server.WithReadHeaderTimeout(3*time.Second),
server.WithWriteTimeout(10*time.Second),
server.WithIdleTimeout(60*time.Second),
server.WithShutdownTimeout(5*time.Second),
)
go func() { _ = srv.ListenAndServe() }()
waitForAddr(t, srv)
resp, err := http.Get("http://" + srv.Addr())
if err != nil {
t.Fatalf("GET failed: %v", err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("got status %d, want %d", resp.StatusCode, http.StatusOK)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
}
// waitForAddr polls until the server's Addr() is non-empty.
func waitForAddr(t *testing.T, srv *server.Server) {
t.Helper()