Files
remnawave-bedolaga-telegram…/tests/services/test_payment_service_yookassa.py
T
2025-10-12 10:13:32 +03:00

246 lines
7.7 KiB
Python

"""Тесты для YooKassa-сценариев PaymentService."""
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional
import pytest
ROOT_DIR = Path(__file__).resolve().parents[2]
if str(ROOT_DIR) not in sys.path:
sys.path.insert(0, str(ROOT_DIR))
import app.services.payment_service as payment_service_module # noqa: E402
from app.config import settings # noqa: E402
from app.services.payment_service import PaymentService # noqa: E402
@pytest.fixture
def anyio_backend() -> str:
"""Запускаем async-тесты на asyncio, чтобы избежать зависимостей trio."""
return "asyncio"
class DummySession:
"""Простейшая заглушка AsyncSession."""
def __init__(self) -> None:
self.committed = False
async def commit(self) -> None:
self.committed = True
async def rollback(self) -> None:
self.rolled_back = True # type: ignore[attr-defined]
class DummyLocalPayment:
"""Объект, имитирующий локальную запись платежа."""
def __init__(self, payment_id: int = 101) -> None:
self.id = payment_id
self.created_at = datetime(2024, 1, 1, 12, 0, 0)
class StubYooKassaService:
"""Заглушка для SDK, сохраняющая вызовы."""
def __init__(self, response: Dict[str, Any]) -> None:
self.response = response
self.calls: list[Dict[str, Any]] = []
async def create_payment(self, **kwargs: Any) -> Dict[str, Any]:
self.calls.append(kwargs)
return self.response
async def create_sbp_payment(self, **kwargs: Any) -> Dict[str, Any]:
self.calls.append(kwargs)
return self.response
def _make_service(yookassa_service: Optional[StubYooKassaService]) -> PaymentService:
service = PaymentService.__new__(PaymentService) # type: ignore[call-arg]
service.bot = None
service.yookassa_service = yookassa_service
service.stars_service = None
service.mulenpay_service = None
service.pal24_service = None
service.mulenpay_service = None
service.cryptobot_service = None
return service
@pytest.mark.anyio("asyncio")
async def test_create_yookassa_payment_success(monkeypatch: pytest.MonkeyPatch) -> None:
"""Успешное создание платежа формирует корректные метаданные и локальную запись."""
response = {
"id": "yk_123",
"status": "pending",
"confirmation_url": "https://yookassa.ru/confirm",
"amount": {"value": "140.00", "currency": "RUB"},
"metadata": {"existing": "value"},
"created_at": "2024-01-01T12:00:00Z",
"test_mode": False,
}
service = _make_service(StubYooKassaService(response))
db = DummySession()
captured_args: Dict[str, Any] = {}
async def fake_create_yookassa_payment(**kwargs: Any) -> DummyLocalPayment:
captured_args.update(kwargs)
return DummyLocalPayment(payment_id=555)
monkeypatch.setattr(
payment_service_module,
"create_yookassa_payment",
fake_create_yookassa_payment,
raising=False,
)
monkeypatch.setattr(
type(settings),
"format_price",
lambda self, amount: f"{amount / 100:.0f}",
raising=False,
)
result = await service.create_yookassa_payment(
db=db,
user_id=42,
amount_kopeks=14000,
description="Пополнение",
receipt_email="user@example.com",
metadata={"custom": "data"},
)
assert result is not None
assert result["local_payment_id"] == 555
assert result["yookassa_payment_id"] == "yk_123"
assert result["amount_kopeks"] == 14000
assert result["amount_rubles"] == 140
assert result["status"] == "pending"
assert captured_args["user_id"] == 42
assert captured_args["metadata_json"]["custom"] == "data"
assert captured_args["metadata_json"]["user_id"] == "42"
assert captured_args["metadata_json"]["amount_kopeks"] == "14000"
assert isinstance(captured_args["yookassa_created_at"], datetime)
@pytest.mark.anyio("asyncio")
async def test_create_yookassa_payment_returns_none_when_service_missing() -> None:
"""Если сервис не настроен, метод должен вернуть None."""
service = _make_service(None)
db = DummySession()
result = await service.create_yookassa_payment(
db=db,
user_id=1,
amount_kopeks=1000,
description="Пополнение",
)
assert result is None
@pytest.mark.anyio("asyncio")
async def test_create_yookassa_payment_handles_error_response(monkeypatch: pytest.MonkeyPatch) -> None:
"""Ответ с ключом error должен приводить к None без записи в БД."""
response = {"error": True}
service = _make_service(StubYooKassaService(response))
db = DummySession()
called = False
async def fake_create_yookassa_payment(**kwargs: Any) -> DummyLocalPayment:
nonlocal called
called = True
return DummyLocalPayment()
monkeypatch.setattr(
payment_service_module,
"create_yookassa_payment",
fake_create_yookassa_payment,
raising=False,
)
result = await service.create_yookassa_payment(
db=db,
user_id=1,
amount_kopeks=5000,
description="Пополнение",
)
assert result is None
assert called is False
@pytest.mark.anyio("asyncio")
async def test_create_yookassa_sbp_payment_success(monkeypatch: pytest.MonkeyPatch) -> None:
"""Проверяем SBP-сценарий, включая передачу confirmation_token."""
response = {
"id": "yk_sbp_001",
"status": "pending",
"confirmation_url": "https://yookassa.ru/confirm",
"confirmation": {"confirmation_token": "token123"},
"created_at": "2024-02-01T10:00:00Z",
}
service = _make_service(StubYooKassaService(response))
db = DummySession()
captured_args: Dict[str, Any] = {}
async def fake_create_yookassa_payment(**kwargs: Any) -> DummyLocalPayment:
captured_args.update(kwargs)
return DummyLocalPayment(payment_id=777)
monkeypatch.setattr(
payment_service_module,
"create_yookassa_payment",
fake_create_yookassa_payment,
raising=False,
)
result = await service.create_yookassa_sbp_payment(
db=db,
user_id=7,
amount_kopeks=25000,
description="СБП пополнение",
)
assert result is not None
assert result["confirmation_token"] == "token123"
assert captured_args["payment_method_type"] == "bank_card"
assert captured_args["metadata_json"]["type"] == "balance_topup_sbp"
@pytest.mark.anyio("asyncio")
async def test_create_yookassa_sbp_payment_returns_none_on_error(monkeypatch: pytest.MonkeyPatch) -> None:
"""Ошибочный ответ СБП не должен создавать запись."""
response = {"error": "invalid"}
service = _make_service(StubYooKassaService(response))
db = DummySession()
called = False
async def fake_create_yookassa_payment(**kwargs: Any) -> DummyLocalPayment:
nonlocal called
called = True
return DummyLocalPayment()
monkeypatch.setattr(
payment_service_module,
"create_yookassa_payment",
fake_create_yookassa_payment,
raising=False,
)
result = await service.create_yookassa_sbp_payment(
db=db,
user_id=1,
amount_kopeks=1000,
description="СБП пополнение",
)
assert result is None
assert called is False