Files
sm-user-plugin/turnstile_routes_test.go
Jakub Zych 1fc0ce7ed0 test: TestTurnstileRoutes pins golem15.user.turnstile per mode
Replaces TestTurnstileRoutesSmoke: empty adds nothing; open, closed and a
space-padded closed add turnstile:{mode} to POST login and register only,
every other route keeps its middleware; invalid values fail naming the key.
2026-10-20 19:19:36 +02:00

159 lines
4.8 KiB
Go

package user
import (
"net/http"
"strings"
"testing"
"git.golem15.com/golem15/summercms/modules/pact"
)
// recordedRoute is one registration seen by recordingRouter.
type recordedRoute struct {
method string
path string
middleware []string
}
// recordingRouter is a pact.Router that records each registration with its
// full path and the group plus route middleware. The plugin requires a
// framework whose surf does not know the turnstile:* names, so the smoke test
// checks the declared middleware instead of assembling a real router.
type recordingRouter struct {
prefix string
middleware []string
routes *[]recordedRoute
}
func newRecordingRouter() *recordingRouter {
return &recordingRouter{routes: &[]recordedRoute{}}
}
func (r *recordingRouter) Group(prefix string, middleware []string, fn func(pact.Router)) {
fn(&recordingRouter{
prefix: r.prefix + prefix,
middleware: append(append([]string{}, r.middleware...), middleware...),
routes: r.routes,
})
}
func (r *recordingRouter) GroupRaw(prefix string, middleware []string, fn func(pact.Router)) {
r.Group(prefix, middleware, fn)
}
func (r *recordingRouter) add(method, path string, middleware []string) {
*r.routes = append(*r.routes, recordedRoute{
method: method,
path: r.prefix + path,
middleware: append(append([]string{}, r.middleware...), middleware...),
})
}
func (r *recordingRouter) Get(path string, _ http.HandlerFunc, mw ...string) {
r.add(http.MethodGet, path, mw)
}
func (r *recordingRouter) Post(path string, _ http.HandlerFunc, mw ...string) {
r.add(http.MethodPost, path, mw)
}
func (r *recordingRouter) Put(path string, _ http.HandlerFunc, mw ...string) {
r.add(http.MethodPut, path, mw)
}
func (r *recordingRouter) Patch(path string, _ http.HandlerFunc, mw ...string) {
r.add(http.MethodPatch, path, mw)
}
func (r *recordingRouter) Delete(path string, _ http.HandlerFunc, mw ...string) {
r.add(http.MethodDelete, path, mw)
}
func (r *recordingRouter) Where(string, string) {}
func (r *recordingRouter) WhereIn(string, ...string) {}
func recordUserRoutes(t *testing.T, mode string) ([]recordedRoute, error) {
t.Helper()
app := bareApp(t)
if err := app.Config.Set("golem15.user.turnstile", mode); err != nil {
t.Fatal(err)
}
rec := newRecordingRouter()
err := (&Plugin{app: app}).Routes(rec)
return *rec.routes, err
}
func turnstileOf(route recordedRoute) []string {
var out []string
for _, name := range route.middleware {
if strings.HasPrefix(name, "turnstile") {
out = append(out, name)
}
}
return out
}
// TestTurnstileRoutes pins golem15.user.turnstile (D-07): empty adds no
// middleware; open and closed (surrounding spaces trimmed) append
// turnstile:{mode} to POST /_user/api/v1/login and /register only, leaving
// every other route's middleware as it is with the key empty; any other
// value fails Routes naming the key.
func TestTurnstileRoutes(t *testing.T) {
off, err := recordUserRoutes(t, "")
if err != nil {
t.Fatalf("empty turnstile: Routes error %v", err)
}
if len(off) == 0 {
t.Fatal("no routes recorded")
}
guarded := map[string]bool{
http.MethodPost + " /_user/api/v1/login": true,
http.MethodPost + " /_user/api/v1/register": true,
}
seen := 0
for _, route := range off {
if got := turnstileOf(route); len(got) > 0 {
t.Errorf("empty turnstile: %s %s carries %v", route.method, route.path, got)
}
if guarded[route.method+" "+route.path] {
seen++
}
}
if seen != len(guarded) {
t.Fatalf("found %d of the guarded routes, want %d", seen, len(guarded))
}
cases := []struct {
name, mode, want string
}{
{name: "open", mode: "open", want: "turnstile:open"},
{name: "closed", mode: "closed", want: "turnstile:closed"},
{name: "closed with spaces", mode: " closed ", want: "turnstile:closed"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got, err := recordUserRoutes(t, c.mode)
if err != nil {
t.Fatalf("Routes error %v", err)
}
if len(got) != len(off) {
t.Fatalf("%d routes, want %d", len(got), len(off))
}
for i, route := range got {
base := off[i]
if route.method != base.method || route.path != base.path {
t.Fatalf("route %d is %s %s, want %s %s", i, route.method, route.path, base.method, base.path)
}
want := base.middleware
if guarded[route.method+" "+route.path] {
want = append(append([]string{}, base.middleware...), c.want)
}
if strings.Join(route.middleware, ",") != strings.Join(want, ",") {
t.Errorf("%s %s middleware %v, want %v", route.method, route.path, route.middleware, want)
}
}
})
}
for _, bad := range []string{"bogus", "Closed", "on", "true"} {
if _, err := recordUserRoutes(t, bad); err == nil || !strings.Contains(err.Error(), "golem15.user.turnstile") {
t.Errorf("turnstile %q: error %v, want one naming golem15.user.turnstile", bad, err)
}
}
}