summaryrefslogtreecommitdiff
path: root/internal/server/server_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/server/server_test.go')
-rw-r--r--internal/server/server_test.go122
1 files changed, 122 insertions, 0 deletions
diff --git a/internal/server/server_test.go b/internal/server/server_test.go
new file mode 100644
index 0000000..af86868
--- /dev/null
+++ b/internal/server/server_test.go
@@ -0,0 +1,122 @@
+package server
+
+import (
+ "io"
+ "log/slog"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "strings"
+ "testing"
+)
+
+// TestMain silences slog so the request logs from logRequests don't
+// clutter test output.
+func TestMain(m *testing.M) {
+ slog.SetDefault(slog.New(slog.NewTextHandler(io.Discard, nil)))
+ os.Exit(m.Run())
+}
+
+func newTestServer(t *testing.T) *Server {
+ t.Helper()
+ return New(&Config{Name: "test"})
+}
+
+func TestRoutes(t *testing.T) {
+ s := newTestServer(t)
+
+ tests := []struct {
+ name string
+ method string
+ path string
+ want int
+ }{
+ {"index", http.MethodGet, "/", http.StatusOK},
+ {"index style", http.MethodGet, "/css/style.css", http.StatusOK},
+ {"unknown path", http.MethodGet, "/nonsense", http.StatusNotFound},
+ {"wrong method", http.MethodPost, "/", http.StatusMethodNotAllowed},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rec := httptest.NewRecorder()
+ s.mux.ServeHTTP(rec, httptest.NewRequest(tt.method, tt.path, nil))
+
+ if rec.Code != tt.want {
+ t.Errorf("%s %s: got status %d, want %d", tt.method, tt.path, rec.Code, tt.want)
+ }
+ })
+ }
+}
+
+func TestIndexRendersTemplate(t *testing.T) {
+ s := New(&Config{Name: "testname123"})
+
+ rec := httptest.NewRecorder()
+ s.mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
+
+ body := rec.Body.String()
+ if !strings.Contains(body, "testname123") {
+ t.Errorf("index body doesn't contain server name, got:\n%s", body)
+ }
+
+ if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/html") {
+ t.Errorf("got Content-Type %q, want text/html", ct)
+ }
+}
+
+func TestStatusRecorder(t *testing.T) {
+ t.Run("records and forwards WriteHeader", func(t *testing.T) {
+ rec := httptest.NewRecorder()
+ sr := &statusRecorder{ResponseWriter: rec, status: http.StatusOK}
+
+ sr.WriteHeader(http.StatusTeapot)
+
+ if sr.status != http.StatusTeapot {
+ t.Errorf("recorder kept status %d, want %d", sr.status, http.StatusTeapot)
+ }
+ if rec.Code != http.StatusTeapot {
+ t.Errorf("underlying writer got status %d, want %d", rec.Code, http.StatusTeapot)
+ }
+ })
+
+ t.Run("defaults to 200 when handler never calls WriteHeader", func(t *testing.T) {
+ rec := httptest.NewRecorder()
+ sr := &statusRecorder{ResponseWriter: rec, status: http.StatusOK}
+
+ if _, err := sr.Write([]byte("hello")); err != nil {
+ t.Fatalf("Write: %v", err)
+ }
+
+ if sr.status != http.StatusOK {
+ t.Errorf("recorder kept status %d, want %d", sr.status, http.StatusOK)
+ }
+ })
+}
+
+// TestLogRequests checks the middleware passes requests through untouched,
+// on both the Write path (200) and the WriteHeader path (404).
+func TestLogRequests(t *testing.T) {
+ s := newTestServer(t)
+ handler := logRequests(s.mux)
+
+ tests := []struct {
+ name string
+ path string
+ want int
+ }{
+ {"success passes through", "/", http.StatusOK},
+ {"not found passes through", "/nonsense", http.StatusNotFound},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, tt.path, nil))
+
+ if rec.Code != tt.want {
+ t.Errorf("got status %d, want %d", rec.Code, tt.want)
+ }
+ })
+ }
+}