diff --git a/internal/server/server.go b/internal/server/server.go index ba2ddbab..efc2f030 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -28,6 +28,15 @@ func ProvideRouter(cfg config.Config, htmlRender render.HTMLRender) *gin.Engine } r := gin.New() r.Use(CORSMiddlewareWithConfig(cfg), RequestContextMiddleware(), audit.ContextMiddleware(), RequestLogger(), gin.Recovery()) + r.NoRoute(func(c *gin.Context) { + if acceptsHTML(c) { + c.HTML(http.StatusNotFound, "not_found.gohtml", gin.H{ + "CurrentPath": c.Request.URL.Path, + }) + return + } + c.JSON(http.StatusNotFound, ErrorResponse{Error: "Not found"}) + }) r.Static("/static", "./static") r.Static("/dist", "./dist") r.GET("/debug/pprof", gin.WrapH(http.DefaultServeMux)) diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 97c8d7ca..a4b08b56 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -6,6 +6,7 @@ import ( "io" "log" "mal/internal/config" + "mal/templates" "net/http" "net/http/httptest" "strings" @@ -182,3 +183,50 @@ func TestRespondErrorIncludesRequestContext(t *testing.T) { t.Fatalf("log line missing request route: %s", logLine) } } + +func TestProvideRouterRendersNotFoundPageForHTMLRequests(t *testing.T) { + gin.SetMode(gin.TestMode) + + renderer, err := templates.ProvideRenderer() + if err != nil { + t.Fatalf("ProvideRenderer: %v", err) + } + + router := ProvideRouter(config.Config{GinMode: gin.TestMode}, renderer) + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/missing", nil) + req.Header.Set("Accept", "text/html") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusNotFound { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusNotFound) + } + for _, want := range []string{"You got a little lost", "This page slipped off the map for a minute", "Head back home"} { + if !strings.Contains(rec.Body.String(), want) { + t.Fatalf("not found page missing %q in body:\n%s", want, rec.Body.String()) + } + } +} + +func TestProvideRouterReturnsJSONForAPINotFound(t *testing.T) { + gin.SetMode(gin.TestMode) + + renderer, err := templates.ProvideRenderer() + if err != nil { + t.Fatalf("ProvideRenderer: %v", err) + } + + router := ProvideRouter(config.Config{GinMode: gin.TestMode}, renderer) + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/api/missing", nil) + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusNotFound { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusNotFound) + } + if got := rec.Body.String(); got != `{"error":"Not found"}` { + t.Fatalf("body = %q, want %q", got, `{"error":"Not found"}`) + } +} diff --git a/templates/not_found.gohtml b/templates/not_found.gohtml index cf80ff3e..2aeddcf1 100644 --- a/templates/not_found.gohtml +++ b/templates/not_found.gohtml @@ -1,7 +1,39 @@ {{define "title"}}Not Found{{end}} {{define "content"}} -
Page not found
-+ This page slipped off the map for a minute. Let's get you back to something worth watching. +
+ +