package server import ( "log/slog" "net/http" "net/http/httptest" "os" "strings" "testing" ) func TestMain(m *testing.M) { slog.SetDefault(slog.New(slog.DiscardHandler)) 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) } }) } }