Files
Bookra/apps/backend/internal/admin/service_test.go
T
Tomas DvorakandDevin f2f7439050 refactor(db): replace sqlc with handwritten pgx repos
- Finish Paddle-to-Stripe billing migration; drop paddle/stripe libs
- Renumber migrations; add 00012 memberships+manage_token,
  00013 location_type_capacity
- Add tests: admin, auth, bookings customer service
- Remove graphify-out artifacts and stale env files

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 20:21:34 +02:00

118 lines
3.5 KiB
Go

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, "[email protected]", "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, "[email protected]", "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": "[email protected]",
"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, "[email protected]", "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": "[email protected]",
"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, "[email protected]", "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(), "[email protected]", "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)
}
}