package middleware import ( "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/treytartt/honeydue-api/internal/config" "github.com/treytartt/honeydue-api/internal/models" ) func TestAdminAuth_NoHeader_Returns401(t *testing.T) { cfg := &config.Config{ Security: config.SecurityConfig{SecretKey: "test-secret"}, } mw := AdminAuthMiddleware(cfg, nil) handler := mw(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/admin/test", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, rec.Code) assert.Contains(t, rec.Body.String(), "Authorization required") } func TestAdminAuth_InvalidToken_Returns401(t *testing.T) { cfg := &config.Config{ Security: config.SecurityConfig{SecretKey: "test-secret"}, } mw := AdminAuthMiddleware(cfg, nil) handler := mw(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/admin/test", nil) req.Header.Set("Authorization", "Bearer invalid-jwt-token") rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, rec.Code) assert.Contains(t, rec.Body.String(), "Invalid token") } func TestAdminAuth_TokenSchemeOnly_Returns401(t *testing.T) { cfg := &config.Config{ Security: config.SecurityConfig{SecretKey: "test-secret"}, } mw := AdminAuthMiddleware(cfg, nil) handler := mw(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/admin/test", nil) // "Token" scheme is not supported for admin auth, only "Bearer" req.Header.Set("Authorization", "Token some-token") rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, rec.Code) } func TestRequireSuperAdmin_NoAdmin_Returns401(t *testing.T) { mw := RequireSuperAdmin() handler := mw(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/admin/test", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // No admin in context err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, rec.Code) } func TestRequireSuperAdmin_WrongType_Returns401(t *testing.T) { mw := RequireSuperAdmin() handler := mw(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/admin/test", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // Wrong type in context c.Set(AdminUserKey, "not-an-admin") err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, rec.Code) } func TestRequireSuperAdmin_NonSuperAdmin_Returns403(t *testing.T) { mw := RequireSuperAdmin() handler := mw(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/admin/test", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // Regular admin (not super admin) admin := &models.AdminUser{ Email: "admin@test.com", IsActive: true, Role: models.AdminRoleAdmin, } c.Set(AdminUserKey, admin) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusForbidden, rec.Code) assert.Contains(t, rec.Body.String(), "Super admin privileges required") } func TestRequireSuperAdmin_SuperAdmin_Passes(t *testing.T) { mw := RequireSuperAdmin() handler := mw(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/admin/test", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) admin := &models.AdminUser{ Email: "superadmin@test.com", IsActive: true, Role: models.AdminRoleSuperAdmin, } c.Set(AdminUserKey, admin) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) }