Files
mcr/internal/server/admin_test.go
Kyle Isom d5580f01f2 Migrate module path from kyle/ to mc/ org
All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:05:59 -07:00

149 lines
4.0 KiB
Go

package server
import (
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"github.com/go-chi/chi/v5"
"git.wntrmute.dev/mc/mcr/internal/auth"
"git.wntrmute.dev/mc/mcr/internal/db"
"git.wntrmute.dev/mc/mcr/internal/policy"
)
func openAdminTestDB(t *testing.T) *db.DB {
t.Helper()
path := filepath.Join(t.TempDir(), "test.db")
d, err := db.Open(path)
if err != nil {
t.Fatalf("Open: %v", err)
}
t.Cleanup(func() { _ = d.Close() })
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
return d
}
type fakePolicyReloader struct {
reloadCount int
}
func (f *fakePolicyReloader) Reload(_ policy.RuleStore) error {
f.reloadCount++
return nil
}
// buildAdminRouter creates a chi router with admin routes wired up,
// using a fakeValidator that returns admin claims for any bearer token.
func buildAdminRouter(t *testing.T, database *db.DB) (chi.Router, *fakePolicyReloader) {
t.Helper()
validator := &fakeValidator{
claims: &auth.Claims{Subject: "admin-uuid", AccountType: "human", Roles: []string{"admin"}},
}
login := &fakeLoginClient{token: "test-token", expiresIn: 3600}
reloader := &fakePolicyReloader{}
gcState := &GCState{}
r := chi.NewRouter()
MountAdminRoutes(r, validator, "mcr-test", AdminDeps{
DB: database,
Login: login,
Engine: reloader,
AuditFn: nil,
GCState: gcState,
})
return r, reloader
}
// buildNonAdminRouter creates a chi router that returns non-admin claims.
func buildNonAdminRouter(t *testing.T, database *db.DB) chi.Router {
t.Helper()
validator := &fakeValidator{
claims: &auth.Claims{Subject: "user-uuid", AccountType: "human", Roles: []string{"user"}},
}
login := &fakeLoginClient{token: "test-token", expiresIn: 3600}
reloader := &fakePolicyReloader{}
gcState := &GCState{}
r := chi.NewRouter()
MountAdminRoutes(r, validator, "mcr-test", AdminDeps{
DB: database,
Login: login,
Engine: reloader,
AuditFn: nil,
GCState: gcState,
})
return r
}
// adminReq is a convenience helper for making HTTP requests against the admin
// router, automatically including the Authorization header.
func adminReq(t *testing.T, router http.Handler, method, path string, body string) *httptest.ResponseRecorder {
t.Helper()
var bodyReader io.Reader
if body != "" {
bodyReader = strings.NewReader(body)
}
req := httptest.NewRequest(method, path, bodyReader)
req.Header.Set("Authorization", "Bearer valid-token")
if body != "" {
req.Header.Set("Content-Type", "application/json")
}
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
return rr
}
func TestRequireAdminAllowed(t *testing.T) {
claims := &auth.Claims{Subject: "admin-uuid", AccountType: "human", Roles: []string{"admin"}}
handler := RequireAdmin()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest("GET", "/test", nil)
req = req.WithContext(auth.ContextWithClaims(req.Context(), claims))
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("admin allowed: got %d, want 200", rr.Code)
}
}
func TestRequireAdminDenied(t *testing.T) {
claims := &auth.Claims{Subject: "user-uuid", AccountType: "human", Roles: []string{"user"}}
handler := RequireAdmin()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
t.Fatal("inner handler should not be called")
}))
req := httptest.NewRequest("GET", "/test", nil)
req = req.WithContext(auth.ContextWithClaims(req.Context(), claims))
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusForbidden {
t.Fatalf("non-admin denied: got %d, want 403", rr.Code)
}
}
func TestRequireAdminNoClaims(t *testing.T) {
handler := RequireAdmin()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
t.Fatal("inner handler should not be called")
}))
req := httptest.NewRequest("GET", "/test", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Fatalf("no claims: got %d, want 401", rr.Code)
}
}