feat(03-01): add ServeMux groups, JWT verifier, and serve command
Named middleware resolves at boot, HS256 tokens are pinned with required exp/sub, and both binaries expose a signal-aware serve command. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
143
surf/router_test.go
Normal file
143
surf/router_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
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 TestIntParamMalformedIsFalse(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/items/abc", nil)
|
||||
req.SetPathValue("id", "abc")
|
||||
if _, ok := IntParam(req, "id"); ok {
|
||||
t.Fatal("malformed id must be false")
|
||||
}
|
||||
req.SetPathValue("id", "12")
|
||||
n, ok := IntParam(req, "id")
|
||||
if !ok || n != 12 {
|
||||
t.Fatalf("got %d %t", n, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypedIDRouteReturns404(t *testing.T) {
|
||||
r := New(nil)
|
||||
r.Get("/items/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||
if _, ok := IntParam(req, "id"); !ok {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
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("status = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user