package surf import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "git.golem15.com/golem15/summercms/pact" ) type routePlugin struct { id string mw map[string]pact.Middleware path string use []string } func (p routePlugin) ID() string { return p.id } func (p routePlugin) Requires() []string { return nil } func (p routePlugin) Register(any) error { return nil } func (p routePlugin) Boot(any) error { return nil } func (p routePlugin) Middlewares() map[string]pact.Middleware { return p.mw } func (p routePlugin) Routes(r pact.Router) error { r.Group("/api", Use(p.use...), func(g pact.Router) { g.Get(p.path, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Hit", "1") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"ok":true}`)) }) }) return nil } func TestMissingMiddlewareNamesPluginAndName(t *testing.T) { r := New(nil) p := routePlugin{id: "golem15.demo", path: "/items", use: []string{"jwt.auth"}} r.BindPlugin(p.ID()) if err := p.Routes(r); err != nil { t.Fatal(err) } _, err := r.compile() if err == nil || !strings.Contains(err.Error(), "golem15.demo") || !strings.Contains(err.Error(), "jwt.auth") { t.Fatalf("want plugin and middleware in error, got %v", err) } } func TestRecoverReturnsOpaqueJSON500(t *testing.T) { r := New(nil) r.Get("/panic", func(http.ResponseWriter, *http.Request) { panic("secret internals") }) h, err := r.compile() if err != nil { t.Fatal(err) } rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/panic", nil)) if rec.Code != http.StatusInternalServerError { t.Fatalf("status = %d", rec.Code) } body := rec.Body.String() if strings.Contains(body, "secret") { t.Fatalf("leaked panic: %s", body) } var payload map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { t.Fatal(err) } if payload["error"] != true || payload["message"] != "Internal server error" { t.Fatalf("payload = %v", payload) } } func TestCORSPreflightBypassesNamedAuth(t *testing.T) { called := false r := New([]string{"http://localhost:3000"}) if err := r.RegisterMiddleware("golem15.user", "jwt.auth", func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { called = true next.ServeHTTP(w, req) }) }); err != nil { t.Fatal(err) } r.BindPlugin("golem15.demo") r.Group("/api", Use("jwt.auth"), func(g pact.Router) { g.Get("/items", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) }) h, err := r.compile() if err != nil { t.Fatal(err) } req := httptest.NewRequest(http.MethodOptions, "/api/items", nil) req.Header.Set("Origin", "http://localhost:3000") rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusNoContent { t.Fatalf("status = %d", rec.Code) } if called { t.Fatal("jwt.auth ran on preflight") } if rec.Header().Get("Access-Control-Allow-Origin") != "http://localhost:3000" { t.Fatalf("ACA origin = %q", rec.Header().Get("Access-Control-Allow-Origin")) } } func TestTypedIDRouteReturns404(t *testing.T) { r := New(nil) r.Get("/items/{id}", func(w http.ResponseWriter, req *http.Request) { id, ok := IntParam(req, "id") if !ok || id != 1 { http.NotFound(w, req) return } w.WriteHeader(http.StatusOK) }) r.Where("id", `[0-9]+`) h, err := r.compile() if err != nil { t.Fatal(err) } rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/items/nope", nil)) if rec.Code != http.StatusNotFound { t.Fatalf("malformed status = %d", rec.Code) } rec = httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/items/99", nil)) if rec.Code != http.StatusNotFound { t.Fatalf("unknown status = %d", rec.Code) } rec = httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/items/1", nil)) if rec.Code != http.StatusOK { t.Fatalf("known status = %d", rec.Code) } } func TestWhereInRejectsOutsideEnum(t *testing.T) { r := New(nil) r.Get("/kinds/{kind}", func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) }) r.WhereIn("kind", "widget", "gadget") h, err := r.compile() if err != nil { t.Fatal(err) } rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/kinds/other", nil)) if rec.Code != http.StatusNotFound { t.Fatalf("status = %d", rec.Code) } rec = httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/kinds/widget", nil)) if rec.Code != http.StatusOK { t.Fatalf("status = %d", rec.Code) } } func TestWhereInvalidRegexFailsCompile(t *testing.T) { r := New(nil) r.Get("/items/{id}", func(http.ResponseWriter, *http.Request) {}) r.Where("id", "[") if _, err := r.compile(); err == nil { t.Fatal("want compile error for invalid regex") } } func TestWhereUnknownParamFailsCompile(t *testing.T) { r := New(nil) r.Get("/items/{id}", func(http.ResponseWriter, *http.Request) {}) r.Where("slug", `[a-z]+`) if _, err := r.compile(); err == nil || !strings.Contains(err.Error(), "slug") { t.Fatal("want unknown param error") } }