This commit is contained in:
@@ -28,6 +28,15 @@
|
|||||||
- Исправлена загрузка данных из Redis в `load_views_from_redis`
|
- Исправлена загрузка данных из Redis в `load_views_from_redis`
|
||||||
- Добавлен fallback механизм с созданием тестовых данных о просмотрах
|
- Добавлен fallback механизм с созданием тестовых данных о просмотрах
|
||||||
- Исправлена проблема когда всегда возвращался 0 для счетчика просмотров
|
- Исправлена проблема когда всегда возвращался 0 для счетчика просмотров
|
||||||
|
- **Исправлена проблема с логином пользователей**: Устранена ошибка RBAC при аутентификации
|
||||||
|
- Добавлена обработка ошибок RBAC в `services/auth.py` при проверке ролей пользователя
|
||||||
|
- Исправлена логика входа для системных администраторов из `ADMIN_EMAILS`
|
||||||
|
- Добавлен fallback механизм входа для админов при недоступности системы ролей
|
||||||
|
- Использован современный синтаксис `list | tuple` вместо устаревшего `(list, tuple)` в `isinstance()`
|
||||||
|
- **Улучшено логирование авторизации**: Убраны избыточные трейсбеки для обычных случаев
|
||||||
|
- Заменены `logger.error` на `logger.warning` для стандартных проверок авторизации
|
||||||
|
- Убраны трейсбеки из логов при обычных ошибках входа и обновления токенов
|
||||||
|
- Исправлены дублирующие slug в тестовых фикстурах, вызывавшие UNIQUE constraint ошибки
|
||||||
|
|
||||||
|
|
||||||
### 🔧 Техническое
|
### 🔧 Техническое
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async def validate_graphql_context(info: GraphQLResolveInfo) -> None:
|
|||||||
|
|
||||||
# Проверка базовой структуры контекста
|
# Проверка базовой структуры контекста
|
||||||
if info is None or not hasattr(info, "context"):
|
if info is None or not hasattr(info, "context"):
|
||||||
logger.error("[validate_graphql_context] Missing GraphQL context information")
|
logger.warning("[validate_graphql_context] Missing GraphQL context information")
|
||||||
msg = "Internal server error: missing context"
|
msg = "Internal server error: missing context"
|
||||||
raise GraphQLError(msg)
|
raise GraphQLError(msg)
|
||||||
|
|
||||||
@@ -127,11 +127,11 @@ async def validate_graphql_context(info: GraphQLResolveInfo) -> None:
|
|||||||
f"[validate_graphql_context] Токен успешно проверен и установлен для пользователя {auth_state.author_id}"
|
f"[validate_graphql_context] Токен успешно проверен и установлен для пользователя {auth_state.author_id}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.error("[validate_graphql_context] Не удалось установить auth: отсутствует request.scope")
|
logger.warning("[validate_graphql_context] Не удалось установить auth: отсутствует request.scope")
|
||||||
msg = "Internal server error: unable to set authentication context"
|
msg = "Internal server error: unable to set authentication context"
|
||||||
raise GraphQLError(msg)
|
raise GraphQLError(msg)
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
logger.error(f"[validate_graphql_context] Пользователь с ID {auth_state.author_id} не найден в базе данных")
|
logger.warning(f"[validate_graphql_context] Пользователь с ID {auth_state.author_id} не найден в базе данных")
|
||||||
msg = "UnauthorizedError - user not found"
|
msg = "UnauthorizedError - user not found"
|
||||||
raise GraphQLError(msg) from None
|
raise GraphQLError(msg) from None
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ def admin_auth_required(resolver: Callable) -> Callable:
|
|||||||
|
|
||||||
# Проверяем авторизацию пользователя
|
# Проверяем авторизацию пользователя
|
||||||
if info is None:
|
if info is None:
|
||||||
logger.error("[admin_auth_required] GraphQL info is None")
|
logger.warning("[admin_auth_required] GraphQL info is None")
|
||||||
msg = "Invalid GraphQL context"
|
msg = "Invalid GraphQL context"
|
||||||
raise GraphQLError(msg)
|
raise GraphQLError(msg)
|
||||||
|
|
||||||
@@ -199,10 +199,10 @@ def admin_auth_required(resolver: Callable) -> Callable:
|
|||||||
auth = info.context["request"].auth
|
auth = info.context["request"].auth
|
||||||
logger.debug(f"[admin_auth_required] Auth из request: {auth.author_id if auth else None}")
|
logger.debug(f"[admin_auth_required] Auth из request: {auth.author_id if auth else None}")
|
||||||
else:
|
else:
|
||||||
logger.error("[admin_auth_required] Auth не найден ни в scope, ни в request")
|
logger.warning("[admin_auth_required] Auth не найден ни в scope, ни в request")
|
||||||
|
|
||||||
if not auth or not getattr(auth, "logged_in", False):
|
if not auth or not getattr(auth, "logged_in", False):
|
||||||
logger.error("[admin_auth_required] Пользователь не авторизован после validate_graphql_context")
|
logger.warning("[admin_auth_required] Пользователь не авторизован после validate_graphql_context")
|
||||||
msg = "UnauthorizedError - please login"
|
msg = "UnauthorizedError - please login"
|
||||||
raise GraphQLError(msg)
|
raise GraphQLError(msg)
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ def admin_auth_required(resolver: Callable) -> Callable:
|
|||||||
# Преобразуем author_id в int для совместимости с базой данных
|
# Преобразуем author_id в int для совместимости с базой данных
|
||||||
author_id = int(auth.author_id) if auth and auth.author_id else None
|
author_id = int(auth.author_id) if auth and auth.author_id else None
|
||||||
if not author_id:
|
if not author_id:
|
||||||
logger.error(f"[admin_auth_required] ID автора не определен: {auth}")
|
logger.warning(f"[admin_auth_required] ID автора не определен: {auth}")
|
||||||
msg = "UnauthorizedError - invalid user ID"
|
msg = "UnauthorizedError - invalid user ID"
|
||||||
raise GraphQLError(msg)
|
raise GraphQLError(msg)
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ def admin_auth_required(resolver: Callable) -> Callable:
|
|||||||
raise GraphQLError(msg)
|
raise GraphQLError(msg)
|
||||||
|
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
logger.error(f"[admin_auth_required] Пользователь с ID {auth.author_id} не найден в базе данных")
|
logger.warning(f"[admin_auth_required] Пользователь с ID {auth.author_id} не найден в базе данных")
|
||||||
msg = "UnauthorizedError - user not found"
|
msg = "UnauthorizedError - user not found"
|
||||||
raise GraphQLError(msg) from None
|
raise GraphQLError(msg) from None
|
||||||
except GraphQLError:
|
except GraphQLError:
|
||||||
@@ -317,7 +317,7 @@ def permission_required(resource: str, operation: str, func: Callable) -> Callab
|
|||||||
)
|
)
|
||||||
return await func(parent, info, *args, **kwargs)
|
return await func(parent, info, *args, **kwargs)
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
logger.error(f"[permission_required] Пользователь с ID {auth.author_id} не найден в базе данных")
|
logger.warning(f"[permission_required] Пользователь с ID {auth.author_id} не найден в базе данных")
|
||||||
msg = "User not found"
|
msg = "User not found"
|
||||||
raise OperationNotAllowedError(msg) from None
|
raise OperationNotAllowedError(msg) from None
|
||||||
|
|
||||||
|
|||||||
@@ -174,12 +174,12 @@ class AuthMiddleware:
|
|||||||
token=None,
|
token=None,
|
||||||
), UnauthenticatedUser()
|
), UnauthenticatedUser()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[auth.authenticate] Ошибка при работе с базой данных: {e}")
|
logger.warning(f"[auth.authenticate] Ошибка при работе с базой данных: {e}")
|
||||||
return AuthCredentials(
|
return AuthCredentials(
|
||||||
author_id=None, scopes={}, logged_in=False, error_message=str(e), email=None, token=None
|
author_id=None, scopes={}, logged_in=False, error_message=str(e), email=None, token=None
|
||||||
), UnauthenticatedUser()
|
), UnauthenticatedUser()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[auth.authenticate] Ошибка при проверке сессии: {e}")
|
logger.warning(f"[auth.authenticate] Ошибка при проверке сессии: {e}")
|
||||||
return AuthCredentials(
|
return AuthCredentials(
|
||||||
author_id=None, scopes={}, logged_in=False, error_message=str(e), email=None, token=None
|
author_id=None, scopes={}, logged_in=False, error_message=str(e), email=None, token=None
|
||||||
), UnauthenticatedUser()
|
), UnauthenticatedUser()
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ async def login(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, A
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка входа: {e}")
|
logger.warning(f"Ошибка входа: {e}")
|
||||||
return {"success": False, "token": None, "author": None, "error": str(e)}
|
return {"success": False, "token": None, "author": None, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ async def logout(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str,
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка выхода: {e}")
|
logger.warning(f"Ошибка выхода: {e}")
|
||||||
return {"success": False}
|
return {"success": False}
|
||||||
|
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ async def refresh_token(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dic
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка обновления токена: {e}")
|
logger.warning(f"Ошибка обновления токена: {e}")
|
||||||
return {"success": False, "token": None, "author": None, "error": str(e)}
|
return {"success": False, "token": None, "author": None, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ async def get_session(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[
|
|||||||
return {"success": False, "token": None, "author": None, "error": error_message}
|
return {"success": False, "token": None, "author": None, "error": error_message}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка получения сессии: {e}")
|
logger.warning(f"Ошибка получения сессии: {e}")
|
||||||
return {"success": False, "token": None, "author": None, "error": str(e)}
|
return {"success": False, "token": None, "author": None, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -362,13 +362,31 @@ class AuthService:
|
|||||||
if not author:
|
if not author:
|
||||||
logger.warning(f"Пользователь {email} не найден")
|
logger.warning(f"Пользователь {email} не найден")
|
||||||
return {"success": False, "token": None, "author": None, "error": "Пользователь не найден"}
|
return {"success": False, "token": None, "author": None, "error": "Пользователь не найден"}
|
||||||
user_roles = get_user_roles_in_community(int(author.id), community_id=1)
|
|
||||||
has_reader_role = "reader" in user_roles
|
|
||||||
|
|
||||||
logger.debug(f"Роли пользователя {email}: {user_roles}")
|
# 🩵 Проверяем права с обработкой ошибок RBAC
|
||||||
|
is_admin_email = author.email in ADMIN_EMAILS.split(",")
|
||||||
|
has_reader_role = False
|
||||||
|
|
||||||
if not has_reader_role and author.email not in ADMIN_EMAILS.split(","):
|
try:
|
||||||
logger.warning(f"У пользователя {email} нет роли 'reader'. Текущие роли: {user_roles}")
|
user_roles = get_user_roles_in_community(int(author.id), community_id=1)
|
||||||
|
has_reader_role = "reader" in user_roles
|
||||||
|
logger.debug(f"Роли пользователя {email}: {user_roles}")
|
||||||
|
except Exception as rbac_error:
|
||||||
|
logger.warning(f"🧿 RBAC ошибка для {email}: {rbac_error}")
|
||||||
|
# Если RBAC не работает, разрешаем вход только админам
|
||||||
|
if not is_admin_email:
|
||||||
|
logger.warning(f"RBAC недоступен и {email} не админ - запрещаем вход")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"token": None,
|
||||||
|
"author": None,
|
||||||
|
"error": "Система ролей временно недоступна. Попробуйте позже.",
|
||||||
|
}
|
||||||
|
logger.info(f"🔒 RBAC недоступен, но {email} - админ, разрешаем вход")
|
||||||
|
|
||||||
|
# Проверяем права: админы или пользователи с ролью reader
|
||||||
|
if not has_reader_role and not is_admin_email:
|
||||||
|
logger.warning(f"У пользователя {email} нет роли 'reader' и он не админ")
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"token": None,
|
"token": None,
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ async def test_ensure_user_has_reader_role(db_session):
|
|||||||
if not community:
|
if not community:
|
||||||
community = Community(
|
community = Community(
|
||||||
id=1,
|
id=1,
|
||||||
name="Test Community",
|
name="Auth Service Test Community",
|
||||||
slug="test-community",
|
slug="auth-service-test-community",
|
||||||
desc="Test community for auth tests",
|
desc="Test community for auth tests",
|
||||||
created_at=int(asyncio.get_event_loop().time())
|
created_at=int(asyncio.get_event_loop().time())
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -832,6 +832,89 @@ def backend_server():
|
|||||||
backend_process.wait()
|
backend_process.wait()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def frontend_server():
|
||||||
|
"""
|
||||||
|
🚀 Фикстура для автоматического запуска/остановки фронтенд сервера.
|
||||||
|
Запускает фронтенд только если он не запущен.
|
||||||
|
"""
|
||||||
|
frontend_process: Optional[subprocess.Popen] = None
|
||||||
|
frontend_running = False
|
||||||
|
|
||||||
|
# Проверяем, не запущен ли уже фронтенд
|
||||||
|
try:
|
||||||
|
response = requests.get("http://localhost:3000/", timeout=2)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✅ Фронтенд сервер уже запущен")
|
||||||
|
frontend_running = True
|
||||||
|
else:
|
||||||
|
frontend_running = False
|
||||||
|
except:
|
||||||
|
frontend_running = False
|
||||||
|
|
||||||
|
if not frontend_running:
|
||||||
|
print("🔄 Запускаем фронтенд сервер для тестов...")
|
||||||
|
try:
|
||||||
|
# Проверяем наличие node_modules
|
||||||
|
node_modules_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "node_modules")
|
||||||
|
if not os.path.exists(node_modules_path):
|
||||||
|
print("📦 Устанавливаем зависимости фронтенда...")
|
||||||
|
subprocess.run(["npm", "install"], check=True,
|
||||||
|
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
# Запускаем фронтенд сервер
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["NODE_ENV"] = "development"
|
||||||
|
|
||||||
|
frontend_process = subprocess.Popen(
|
||||||
|
["npm", "run", "dev"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
env=env,
|
||||||
|
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ждем запуска фронтенда
|
||||||
|
print("⏳ Ждем запуска фронтенда...")
|
||||||
|
for i in range(60): # Ждем максимум 60 секунд
|
||||||
|
try:
|
||||||
|
response = requests.get("http://localhost:3000/", timeout=2)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✅ Фронтенд сервер запущен")
|
||||||
|
frontend_running = True
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
print("❌ Фронтенд сервер не запустился за 60 секунд")
|
||||||
|
if frontend_process:
|
||||||
|
frontend_process.terminate()
|
||||||
|
frontend_process.wait()
|
||||||
|
# Не падаем жестко, а возвращаем False
|
||||||
|
frontend_running = False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Ошибка запуска фронтенда: {e}")
|
||||||
|
if frontend_process:
|
||||||
|
frontend_process.terminate()
|
||||||
|
frontend_process.wait()
|
||||||
|
# Не падаем жестко, а возвращаем False
|
||||||
|
frontend_running = False
|
||||||
|
|
||||||
|
yield frontend_running
|
||||||
|
|
||||||
|
# Cleanup: останавливаем фронтенд только если мы его запускали
|
||||||
|
if frontend_process:
|
||||||
|
print("🛑 Останавливаем фронтенд сервер...")
|
||||||
|
try:
|
||||||
|
frontend_process.terminate()
|
||||||
|
frontend_process.wait(timeout=10)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
frontend_process.kill()
|
||||||
|
frontend_process.wait()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_client(backend_server):
|
def test_client(backend_server):
|
||||||
"""
|
"""
|
||||||
@@ -1124,8 +1207,8 @@ def test_community(db_session, test_users):
|
|||||||
# Создаем сообщество с ID 2, так как ID 1 уже занят основным сообществом
|
# Создаем сообщество с ID 2, так как ID 1 уже занят основным сообществом
|
||||||
community = Community(
|
community = Community(
|
||||||
id=2, # Используем ID 2, чтобы не конфликтовать с основным сообществом
|
id=2, # Используем ID 2, чтобы не конфликтовать с основным сообществом
|
||||||
name="Test Community",
|
name="Test Community Fixture",
|
||||||
slug="test-community",
|
slug="test-community-fixture", # Уникальный slug для этой фикстуры
|
||||||
desc="A test community for testing purposes",
|
desc="A test community for testing purposes",
|
||||||
created_by=test_users[0].id, # Администратор создает сообщество
|
created_by=test_users[0].id, # Администратор создает сообщество
|
||||||
settings={
|
settings={
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ def test_community(db_session, test_users):
|
|||||||
"""Создает тестовое сообщество"""
|
"""Создает тестовое сообщество"""
|
||||||
community = Community(
|
community = Community(
|
||||||
id=100,
|
id=100,
|
||||||
name="Test Community",
|
name="Auth Test Community",
|
||||||
slug="test-community",
|
slug="auth-test-community", # Уникальный slug для auth тестов
|
||||||
desc="Test community for auth tests",
|
desc="Test community for auth tests",
|
||||||
created_by=test_users[0].id,
|
created_by=test_users[0].id,
|
||||||
created_at=int(time.time())
|
created_at=int(time.time())
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ def test_community(db_session, test_users):
|
|||||||
if not community:
|
if not community:
|
||||||
community = Community(
|
community = Community(
|
||||||
id=1,
|
id=1,
|
||||||
name="Test Community",
|
name="RBAC Test Community",
|
||||||
slug="test-community",
|
slug="rbac-test-community",
|
||||||
desc="Test community for RBAC tests",
|
desc="Test community for RBAC tests",
|
||||||
created_by=test_users[0].id,
|
created_by=test_users[0].id,
|
||||||
created_at=int(time.time())
|
created_at=int(time.time())
|
||||||
|
|||||||
@@ -8,75 +8,47 @@ import requests
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_ci
|
def test_backend_health(backend_server):
|
||||||
def test_backend_health():
|
|
||||||
"""Проверяем здоровье бэкенда"""
|
"""Проверяем здоровье бэкенда"""
|
||||||
max_retries = 10
|
assert backend_server, "Бэкенд сервер должен быть запущен"
|
||||||
for attempt in range(1, max_retries + 1):
|
|
||||||
try:
|
response = requests.get("http://localhost:8000/", timeout=10)
|
||||||
response = requests.get("http://localhost:8000/", timeout=10)
|
assert response.status_code == 200, f"Бэкенд вернул статус {response.status_code}"
|
||||||
if response.status_code == 200:
|
print("✅ Бэкенд здоров")
|
||||||
print(f"✅ Бэкенд готов (попытка {attempt})")
|
|
||||||
return
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"⚠️ Попытка {attempt}/{max_retries}: Бэкенд не готов - {e}")
|
|
||||||
if attempt < max_retries:
|
|
||||||
time.sleep(3)
|
|
||||||
else:
|
|
||||||
pytest.fail(f"Бэкенд не готов после {max_retries} попыток")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_ci
|
def test_frontend_health(frontend_server):
|
||||||
def test_frontend_health():
|
|
||||||
"""Проверяем здоровье фронтенда"""
|
"""Проверяем здоровье фронтенда"""
|
||||||
max_retries = 10
|
if not frontend_server:
|
||||||
for attempt in range(1, max_retries + 1):
|
pytest.skip("Фронтенд сервер не удалось запустить (возможно отсутствует npm или зависимости)")
|
||||||
try:
|
|
||||||
response = requests.get("http://localhost:3000/", timeout=10)
|
response = requests.get("http://localhost:3000/", timeout=10)
|
||||||
if response.status_code == 200:
|
assert response.status_code == 200, f"Фронтенд вернул статус {response.status_code}"
|
||||||
print(f"✅ Фронтенд готов (попытка {attempt})")
|
print("✅ Фронтенд здоров")
|
||||||
return
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"⚠️ Попытка {attempt}/{max_retries}: Фронтенд не готов - {e}")
|
|
||||||
if attempt < max_retries:
|
|
||||||
time.sleep(3)
|
|
||||||
else:
|
|
||||||
# В CI фронтенд может быть не запущен, поэтому не падаем
|
|
||||||
pytest.skip("Фронтенд не запущен (ожидаемо в некоторых CI средах)")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_ci
|
def test_graphql_endpoint(backend_server):
|
||||||
def test_graphql_endpoint():
|
|
||||||
"""Проверяем доступность GraphQL endpoint"""
|
"""Проверяем доступность GraphQL endpoint"""
|
||||||
try:
|
assert backend_server, "Бэкенд сервер должен быть запущен"
|
||||||
response = requests.post(
|
|
||||||
"http://localhost:8000/graphql",
|
response = requests.post(
|
||||||
headers={"Content-Type": "application/json"},
|
"http://localhost:8000/graphql",
|
||||||
json={"query": "{ __schema { types { name } } }"},
|
headers={"Content-Type": "application/json"},
|
||||||
timeout=15
|
json={"query": "{ __schema { types { name } } }"},
|
||||||
)
|
timeout=15
|
||||||
if response.status_code == 200:
|
)
|
||||||
print("✅ GraphQL endpoint доступен")
|
assert response.status_code == 200, f"GraphQL endpoint вернул статус {response.status_code}"
|
||||||
return
|
print("✅ GraphQL endpoint доступен")
|
||||||
else:
|
|
||||||
pytest.fail(f"GraphQL endpoint вернул статус {response.status_code}")
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
pytest.fail(f"GraphQL endpoint недоступен: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_ci
|
def test_admin_panel_access(frontend_server):
|
||||||
def test_admin_panel_access():
|
|
||||||
"""Проверяем доступность админ-панели"""
|
"""Проверяем доступность админ-панели"""
|
||||||
try:
|
if not frontend_server:
|
||||||
response = requests.get("http://localhost:3000/admin", timeout=15)
|
pytest.skip("Фронтенд сервер не запущен - админ-панель недоступна")
|
||||||
if response.status_code == 200:
|
|
||||||
print("✅ Админ-панель доступна")
|
response = requests.get("http://localhost:3000/admin", timeout=15)
|
||||||
return
|
assert response.status_code == 200, f"Админ-панель вернула статус {response.status_code}"
|
||||||
else:
|
print("✅ Админ-панель доступна")
|
||||||
pytest.fail(f"Админ-панель вернула статус {response.status_code}")
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
# В CI фронтенд может быть не запущен, поэтому не падаем
|
|
||||||
pytest.skip("Админ-панель недоступна (фронтенд не запущен)")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user