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