Files
remnawave-bedolaga-telegram…/tests/conftest.py
PEDZEO 6b69ec750e feat: add cabinet (personal account) backend API
- Add JWT authentication for cabinet users
- Add Telegram WebApp authentication
- Add subscription management endpoints
- Add balance and transactions endpoints
- Add referral system endpoints
- Add tickets support for cabinet
- Add webhooks and websocket for real-time updates
- Add email verification service

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 23:20:20 +03:00

220 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Глобальные фикстуры и настройки окружения для тестов."""
import asyncio
import inspect
import os
import sys
import types
from datetime import datetime, timezone
from pathlib import Path
import pytest
# Add project root to Python path for imports
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
# Подменяем параметры подключения к БД, чтобы SQLAlchemy не требовал aiosqlite.
os.environ.setdefault("DATABASE_MODE", "postgresql")
os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://user:pass@localhost/test_db")
os.environ.setdefault("BOT_TOKEN", "test-token")
# Создаём заглушки для драйверов, которых может не быть в окружении тестов.
sys.modules.setdefault("asyncpg", types.ModuleType("asyncpg"))
sys.modules.setdefault("aiosqlite", types.ModuleType("aiosqlite"))
# Эмуляция redis.asyncio, чтобы модуль кеша мог импортироваться.
if "redis.asyncio" not in sys.modules:
redis_module = types.ModuleType("redis")
redis_async_module = types.ModuleType("redis.asyncio")
class _FakeRedisClient:
async def ping(self):
"""Имитируем успешный ответ ping."""
return True
async def close(self):
"""Закрытие соединения ничего не делает."""
async def get(self, key): # noqa: ANN001
return None
async def set(self, key, value, ex=None): # noqa: ANN001
return True
async def delete(self, *keys): # noqa: ANN001
return 0
async def keys(self, pattern="*"): # noqa: ANN001
return []
async def exists(self, key): # noqa: ANN001
return False
async def expire(self, key, seconds): # noqa: ANN001
return True
async def incr(self, key): # noqa: ANN001
return 1
def _from_url(url): # noqa: ANN001
return _FakeRedisClient()
redis_async_module.from_url = _from_url
redis_async_module.Redis = _FakeRedisClient
sys.modules["redis"] = redis_module
sys.modules["redis.asyncio"] = redis_async_module
# Минимальная реализация SDK YooKassa, чтобы импорт сервисов не падал.
if "yookassa" not in sys.modules:
fake_yookassa = types.ModuleType("yookassa")
class _FakeConfiguration:
@staticmethod
def configure(*args, **kwargs):
"""Конфигурация заглушки ничего не делает."""
class _FakePayment:
@staticmethod
def create(*args, **kwargs):
"""Возвращает объект с минимально необходимыми атрибутами."""
class _Response:
id = "yk_fake"
status = "pending"
paid = False
refundable = False
metadata = {}
amount = types.SimpleNamespace(value="0.00", currency="RUB")
confirmation = types.SimpleNamespace(confirmation_url="https://example.com")
created_at = datetime.utcnow()
description = ""
test = False
return _Response()
fake_yookassa.Configuration = _FakeConfiguration
fake_yookassa.Payment = _FakePayment
sys.modules["yookassa"] = fake_yookassa
# Подготавливаем вложенные пакеты, используемые сервисом.
domain_module = types.ModuleType("yookassa.domain")
request_module = types.ModuleType("yookassa.domain.request")
payment_builder_module = types.ModuleType("yookassa.domain.request.payment_request_builder")
common_module = types.ModuleType("yookassa.domain.common")
confirmation_module = types.ModuleType("yookassa.domain.common.confirmation_type")
class _FakePaymentRequestBuilder:
def __init__(self):
self.data: dict = {}
def set_amount(self, value): # noqa: ANN001 - упрощённая заглушка
self.data["amount"] = value
return self
def set_capture(self, value): # noqa: ANN001
self.data["capture"] = value
return self
def set_confirmation(self, value): # noqa: ANN001
self.data["confirmation"] = value
return self
def set_description(self, value): # noqa: ANN001
self.data["description"] = value
return self
def set_metadata(self, value): # noqa: ANN001
self.data["metadata"] = value
return self
def set_receipt(self, value): # noqa: ANN001
self.data["receipt"] = value
return self
def set_payment_method_data(self, value): # noqa: ANN001
self.data["payment_method_data"] = value
return self
def build(self):
return self.data
class _FakeConfirmationType:
REDIRECT = "redirect"
payment_builder_module.PaymentRequestBuilder = _FakePaymentRequestBuilder
confirmation_module.ConfirmationType = _FakeConfirmationType
sys.modules["yookassa.domain"] = domain_module
sys.modules["yookassa.domain.request"] = request_module
sys.modules["yookassa.domain.request.payment_request_builder"] = payment_builder_module
sys.modules["yookassa.domain.common"] = common_module
sys.modules["yookassa.domain.common.confirmation_type"] = confirmation_module
@pytest.fixture
def fixed_datetime() -> datetime:
"""Возвращает фиксированную отметку времени для воспроизводимых проверок."""
return datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
def pytest_configure(config: pytest.Config) -> None:
"""Регистрируем маркеры для асинхронных тестов."""
config.addinivalue_line(
"markers",
"asyncio: запуск асинхронного теста через встроенный цикл событий",
)
config.addinivalue_line(
"markers",
"anyio: запуск асинхронного теста через встроенный цикл событий",
)
def _unwrap_test(obj): # noqa: ANN001 - вспомогательная функция для определения coroutine
"""Возвращает исходную функцию, снимая обёртки pytest и декораторов."""
unwrapped = obj
while hasattr(unwrapped, "__wrapped__"):
unwrapped = unwrapped.__wrapped__
return unwrapped
@pytest.hookimpl(tryfirst=True)
def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> bool | None:
"""Позволяет запускать async def тесты без дополнительных плагинов."""
# Пропускаем если pytest-asyncio уже обработал этот тест
if hasattr(pyfuncitem, "_request") and hasattr(pyfuncitem._request, "_pyfuncitem"):
markers = list(pyfuncitem.iter_markers())
for marker in markers:
if marker.name in ("asyncio", "anyio"):
# pytest-asyncio обработает этот тест
return None
test_func = _unwrap_test(pyfuncitem.obj)
if not inspect.iscoroutinefunction(test_func):
return None
# Проверяем, не обработан ли уже тест плагином pytest-asyncio
# Если pyfuncitem.obj не возвращает корутину - пропускаем
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
signature = inspect.signature(test_func)
call_kwargs = {
name: value
for name, value in pyfuncitem.funcargs.items()
if name in signature.parameters
}
coro = pyfuncitem.obj(**call_kwargs)
if coro is None:
# Уже обработано другим плагином
return None
loop.run_until_complete(coro)
finally:
asyncio.set_event_loop(None)
loop.close()
return True