Files
member-console/internal/auth/auth_test.go
T
cgalo5758 8514df5b50 Remediate operator UX audit findings
Fix HTMX expired-session handling, CSP-blocked form behaviors, reorder
recovery, billing currency display, plan/checkout guards, FedWiki quota
edge cases, and operator/member empty/error states.

Add entitlement uniqueness migrations, canonical migration source
wiring,
and regression coverage for the remediated flows. Update status docs
with
the audit triage and model inventory.
2026-07-03 11:58:03 -05:00

103 lines
3.3 KiB
Go

package auth
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/alexedwards/scs/v2"
)
// newTestConfig builds a minimal Config whose Middleware only touches
// SessionManager, so no Redis/OIDC wiring is needed. scs.New() defaults to
// an in-memory store when Store is left nil.
func newTestConfig() *Config {
sm := scs.New()
return &Config{SessionManager: sm}
}
// TestMiddleware_ExpiredSession_HTMXRequest_RespondsWithHXRedirect covers
// finding #15: an HTMX request against an expired/unauthenticated session
// used to get a 302 to the (cross-origin) Keycloak authorize URL, which the
// XHR can't follow — error-handler.js showed a lying "Network error" toast.
// The middleware must instead respond 401 with HX-Redirect: /login so htmx
// performs a same-origin client-side redirect.
func TestMiddleware_ExpiredSession_HTMXRequest_RespondsWithHXRedirect(t *testing.T) {
cfg := newTestConfig()
mw := cfg.Middleware()
nextCalled := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
})
req := httptest.NewRequest(http.MethodGet, "/partials/operator/organizations/abc/enrollment", nil)
req.Header.Set("HX-Request", "true")
rec := httptest.NewRecorder()
cfg.SessionManager.LoadAndSave(mw(next)).ServeHTTP(rec, req)
if nextCalled {
t.Fatal("expected middleware to short-circuit an unauthenticated HTMX request, but next handler ran")
}
if rec.Code != http.StatusUnauthorized {
t.Fatalf("expected 401, got %d", rec.Code)
}
if got := rec.Header().Get("HX-Redirect"); got != "/login" {
t.Fatalf("expected HX-Redirect header %q, got %q", "/login", got)
}
}
// TestMiddleware_ExpiredSession_NonHTMXRequest_RedirectsToLogin verifies a
// plain browser navigation still gets the original 302-to-/login behavior
// (unchanged by the finding #15 fix).
func TestMiddleware_ExpiredSession_NonHTMXRequest_RedirectsToLogin(t *testing.T) {
cfg := newTestConfig()
mw := cfg.Middleware()
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("expected middleware to short-circuit an unauthenticated request, but next handler ran")
})
req := httptest.NewRequest(http.MethodGet, "/dashboard", nil)
rec := httptest.NewRecorder()
cfg.SessionManager.LoadAndSave(mw(next)).ServeHTTP(rec, req)
if rec.Code != http.StatusFound {
t.Fatalf("expected 302, got %d", rec.Code)
}
if got := rec.Header().Get("Location"); got != "/login" {
t.Fatalf("expected Location header %q, got %q", "/login", got)
}
if got := rec.Header().Get("HX-Redirect"); got != "" {
t.Fatalf("did not expect HX-Redirect on a non-HTMX request, got %q", got)
}
}
// TestMiddleware_PublicPath_SkipsAuth guards against a regression where the
// HX-Request branch swallows the public-path bypass.
func TestMiddleware_PublicPath_SkipsAuth(t *testing.T) {
cfg := newTestConfig()
mw := cfg.Middleware()
nextCalled := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
w.WriteHeader(http.StatusOK)
})
req := httptest.NewRequest(http.MethodGet, "/login", nil)
req.Header.Set("HX-Request", "true")
rec := httptest.NewRecorder()
cfg.SessionManager.LoadAndSave(mw(next)).ServeHTTP(rec, req)
if !nextCalled {
t.Fatal("expected /login to bypass auth and reach the next handler")
}
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", rec.Code)
}
}