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>
144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
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)
|
|
}
|
|
}
|