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) } } }