feat(06-01): grow router verbs, factories, and guard registry

- Add Post/Put/Patch/Delete on pact.Router and surf Router/Group
- Resolve name:param middleware via RegisterMiddlewareFactory
- Add bouncer.Registry with Guard, CredentialGuard, UnauthorizedWriter
- Re-express jwt as NewJWTGuard without changing Middleware bodies
This commit is contained in:
Jakub Zych
2026-09-19 18:59:12 +02:00
parent ecab09c37a
commit d376b1be2d
9 changed files with 678 additions and 14 deletions

View File

@@ -180,3 +180,115 @@ func TestWhereUnknownParamFailsCompile(t *testing.T) {
t.Fatal("want unknown param error")
}
}
func TestPostAndGetSamePathAreIndependent(t *testing.T) {
r := New(nil)
r.Get("/items", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("get"))
})
r.Post("/items", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte("post"))
})
h, err := r.compile()
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/items", nil))
if rec.Code != http.StatusOK || rec.Body.String() != "get" {
t.Fatalf("GET status=%d body=%q", rec.Code, rec.Body.String())
}
rec = httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/items", nil))
if rec.Code != http.StatusCreated || rec.Body.String() != "post" {
t.Fatalf("POST status=%d body=%q", rec.Code, rec.Body.String())
}
}
func TestVerbsOnRouterAndGroup(t *testing.T) {
r := New(nil)
r.Put("/r", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("put")) })
r.Patch("/r", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("patch")) })
r.Delete("/r", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("delete")) })
r.Group("/g", nil, func(g pact.Router) {
g.Get("/x", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("gget")) })
g.Post("/x", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("gpost")) })
g.Put("/x", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("gput")) })
g.Patch("/x", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("gpatch")) })
g.Delete("/x", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("gdelete")) })
})
h, err := r.compile()
if err != nil {
t.Fatal(err)
}
cases := []struct {
method, path, want string
}{
{http.MethodPut, "/r", "put"},
{http.MethodPatch, "/r", "patch"},
{http.MethodDelete, "/r", "delete"},
{http.MethodGet, "/g/x", "gget"},
{http.MethodPost, "/g/x", "gpost"},
{http.MethodPut, "/g/x", "gput"},
{http.MethodPatch, "/g/x", "gpatch"},
{http.MethodDelete, "/g/x", "gdelete"},
}
for _, tc := range cases {
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(tc.method, tc.path, nil))
if rec.Code != http.StatusOK || rec.Body.String() != tc.want {
t.Fatalf("%s %s status=%d body=%q", tc.method, tc.path, rec.Code, rec.Body.String())
}
}
}
func TestMiddlewareFactoryReceivesParam(t *testing.T) {
r := New(nil)
var gotParam string
ran := false
if err := r.RegisterMiddlewareFactory("golem15.fonoteka", "inv.scope", func(param string) pact.Middleware {
gotParam = param
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ran = true
next.ServeHTTP(w, req)
})
}
}); err != nil {
t.Fatal(err)
}
r.Get("/x", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}, "inv.scope:write")
h, err := r.compile()
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/x", nil))
if rec.Code != http.StatusNoContent {
t.Fatalf("status = %d", rec.Code)
}
if gotParam != "write" {
t.Fatalf("param = %q", gotParam)
}
if !ran {
t.Fatal("factory middleware did not run")
}
}
func TestDuplicateMiddlewareFactoryNamesPluginAndName(t *testing.T) {
r := New(nil)
fn := func(string) pact.Middleware {
return func(next http.Handler) http.Handler { return next }
}
if err := r.RegisterMiddlewareFactory("golem15.fonoteka", "inv.scope", fn); err != nil {
t.Fatal(err)
}
err := r.RegisterMiddlewareFactory("golem15.other", "inv.scope", fn)
if err == nil || !strings.Contains(err.Error(), "golem15.fonoteka") || !strings.Contains(err.Error(), "inv.scope") {
t.Fatalf("want plugin and factory name in error, got %v", err)
}
}