package admin import ( "net/http" "net/http/httptest" "testing" "bookra/apps/backend/internal/auth" "bookra/apps/backend/internal/db" "github.com/gin-gonic/gin" ) func newTestRouter(t *testing.T, adminSvc *Service, verifier *auth.Verifier, repo db.Repository, demoMode bool) *gin.Engine { t.Helper() gin.SetMode(gin.TestMode) r := gin.New() r.Use(RequireAdmin(adminSvc, verifier, repo, demoMode)) r.GET("/v1/admin/stats", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"actor": ActorFromContext(c)}) }) return r } func request(t *testing.T, r *gin.Engine, headers map[string]string) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(http.MethodGet, "/v1/admin/stats", nil) for k, v := range headers { req.Header.Set(k, v) } w := httptest.NewRecorder() r.ServeHTTP(w, req) return w } func TestRequireAdminRejectsUnauthenticated(t *testing.T) { repo := db.NewMemoryRepository() svc := NewService(repo, "admin@example.com", "secret-key") verifier, _ := auth.NewVerifier("", "jwt-secret") defer verifier.Close() r := newTestRouter(t, svc, verifier, repo, false) if w := request(t, r, nil); w.Code != http.StatusForbidden { t.Fatalf("expected 403, got %d", w.Code) } } func TestRequireAdminRejectsInvalidHeaderCreds(t *testing.T) { repo := db.NewMemoryRepository() svc := NewService(repo, "admin@example.com", "secret-key") verifier, _ := auth.NewVerifier("", "jwt-secret") defer verifier.Close() r := newTestRouter(t, svc, verifier, repo, false) w := request(t, r, map[string]string{ "X-Admin-Email": "admin@example.com", "X-Admin-Key": "wrong-key", }) if w.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", w.Code) } } func TestRequireAdminAcceptsHeaderCreds(t *testing.T) { repo := db.NewMemoryRepository() svc := NewService(repo, "admin@example.com", "secret-key") verifier, _ := auth.NewVerifier("", "jwt-secret") defer verifier.Close() r := newTestRouter(t, svc, verifier, repo, false) w := request(t, r, map[string]string{ "X-Admin-Email": "admin@example.com", "X-Admin-Key": "secret-key", }) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d", w.Code) } } func TestRequireAdminVerifiesBearerTokenAndRole(t *testing.T) { repo := db.NewMemoryRepository() secret := "jwt-secret" svc := NewService(repo, "admin@example.com", "secret-key") verifier, _ := auth.NewVerifier("", secret) defer verifier.Close() r := newTestRouter(t, svc, verifier, repo, false) // A locally issued token for a non-admin subject must be rejected even though // the token itself is valid. authSvc := auth.NewService(repo, secret) _, tokens, err := authSvc.RegisterWithPassword(t.Context(), "user@example.com", "password123", "Regular User") if err != nil { t.Fatalf("register: %v", err) } w := request(t, r, map[string]string{"Authorization": "Bearer " + tokens.AccessToken}) if w.Code != http.StatusForbidden { t.Fatalf("expected 403 for non-admin subject, got %d", w.Code) } // A forged bearer string must be rejected outright. w = request(t, r, map[string]string{"Authorization": "Bearer not-a-token"}) if w.Code != http.StatusUnauthorized { t.Fatalf("expected 401 for invalid token, got %d", w.Code) } } func TestRequireAdminDemoMode(t *testing.T) { repo := db.NewMemoryRepository() svc := NewService(repo, "", "") verifier, _ := auth.NewVerifier("", "jwt-secret") defer verifier.Close() r := newTestRouter(t, svc, verifier, repo, true) if w := request(t, r, nil); w.Code != http.StatusOK { t.Fatalf("expected 200 in demo mode, got %d", w.Code) } }