Files
summercms/modules/cabana/dispatch_smoke_test.go
Jakub Zych 1bd91a4522 feat(19.7-01): in-process dispatch marker and compiled handler lookup
- bouncer.Dispatch, WithDispatch, Dispatched: context-only pre-resolved
  principal marker honoured by Registry.Middleware for listed guard names
- cabana.ResolveBackend honours a backend-kind marker; additive
  ResolveBackendToken, ScopeLimitFor and BackendPrincipal resolvers
- surf.Router.Handler and compileHandlers: one compiled chain per route,
  shared by BuildRouter validation and compile
- smoke tests TestDispatchSmoke, TestDispatchedSmoke, TestDispatchMarkerSmoke
- READMEs and docs/services/{authentication,routing}.md updated
2026-10-22 16:00:19 +02:00

73 lines
3.3 KiB
Go

package cabana
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/bouncer"
)
// TestDispatchMarkerSmoke covers ResolveBackend with the in-process dispatch
// marker (phase 19.7 D-17): a backend marker is honoured without a bearer or
// a published resolver, a frontend marker is refused, and a scoped marker is
// still checked against the cover codes.
func TestDispatchMarkerSmoke(t *testing.T) {
app := backpack.New(nil)
withMarker := func(p *bouncer.Principal) *http.Request {
r := httptest.NewRequest(http.MethodGet, "/acme/posts", nil)
marker := &bouncer.Dispatch{Guards: []string{"backend"}, Principal: p, Credential: "cred"}
return r.WithContext(bouncer.WithDispatch(r.Context(), marker))
}
// A backend marker: the principal is returned and stored on the context.
r, principal, err := ResolveBackend(app, withMarker(&bouncer.Principal{ID: 42, Backend: true}), "acme.*")
if err != nil || principal == nil || principal.ID != 42 {
t.Fatalf("backend marker: %+v, %v", principal, err)
}
if got, ok := bouncer.User(r.Context()); !ok || got.ID != 42 {
t.Fatalf("principal not stored on the returned request: %+v, %v", got, ok)
}
if cred, ok := bouncer.Credential(r.Context()); !ok || cred != "cred" {
t.Fatalf("credential not stored on the returned request: %v, %v", cred, ok)
}
// A frontend marker (Backend false) is never a backend principal.
if _, _, err := ResolveBackend(app, withMarker(&bouncer.Principal{ID: 7}), "acme.*"); !errors.Is(err, ErrUnauthenticated) {
t.Fatalf("frontend marker: err = %v, want ErrUnauthenticated", err)
}
// A scoped marker whose scope does not cover acme.* is refused as a
// scoped token would be; one that covers it passes.
scoped := &bouncer.Principal{ID: 42, Backend: true, Scope: &bouncer.ScopeLimit{Scopes: [][]string{{"other.*"}}}}
if _, _, err := ResolveBackend(app, withMarker(scoped), "acme.*"); !errors.Is(err, ErrTokenScope) {
t.Fatalf("scoped marker: err = %v, want ErrTokenScope", err)
}
covering := &bouncer.Principal{ID: 42, Backend: true, Scope: &bouncer.ScopeLimit{Scopes: [][]string{{"acme.*"}}}}
if _, _, err := ResolveBackend(app, withMarker(covering), "acme.*"); err != nil {
t.Fatalf("covering scoped marker: %v", err)
}
// Without a marker the request still needs a bearer; a forged header is
// not a marker.
plain := httptest.NewRequest(http.MethodGet, "/acme/posts", nil)
plain.Header.Set("X-Summer-Dispatch", "42")
if _, _, err := ResolveBackend(app, plain, "acme.*"); !errors.Is(err, ErrUnauthenticated) {
t.Fatalf("no marker: err = %v, want ErrUnauthenticated", err)
}
// The additive resolvers answer unauthenticated on an app without cabana.
if _, _, _, err := ResolveBackendToken(context.Background(), app, "g15_0000000000000000000000000000000000000000"); !errors.Is(err, ErrUnauthenticated) {
t.Fatalf("ResolveBackendToken without cabana: %v", err)
}
if limit, known, methodOK := ScopeLimitFor(app, []string{"*"}, http.MethodGet); limit != nil || known != 0 || methodOK != 0 {
t.Fatalf("ScopeLimitFor without cabana = %v, %d, %d", limit, known, methodOK)
}
if _, err := BackendPrincipal(context.Background(), app, 1); !errors.Is(err, ErrUnauthenticated) {
t.Fatalf("BackendPrincipal without cabana: %v", err)
}
}