mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-20 11:50:27 +00:00
1266 lines
43 KiB
Python
1266 lines
43 KiB
Python
"""Интеграционные проверки обработки вебхуков PaymentService."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from types import SimpleNamespace, ModuleType
|
|
from typing import Any, Dict, Optional
|
|
import sys
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock
|
|
|
|
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
|
|
import app.services.payment.cryptobot as cryptobot_module # noqa: E402
|
|
from app.services.payment_service import PaymentService # noqa: E402
|
|
from app.database.models import PaymentMethod # noqa: E402
|
|
from app.config import settings # noqa: E402
|
|
|
|
|
|
class DummyBot:
|
|
def __init__(self) -> None:
|
|
self.sent_messages: list[Dict[str, Any]] = []
|
|
|
|
async def send_message(self, *args: Any, **kwargs: Any) -> None: # pragma: no cover - бизнес-логика тестируется через вызов
|
|
self.sent_messages.append({"args": args, "kwargs": kwargs})
|
|
|
|
|
|
class FakeScalarResult:
|
|
def __init__(self, items: list[Any]) -> None:
|
|
self._items = list(items)
|
|
|
|
def all(self) -> list[Any]: # pragma: no cover - утилитарный метод
|
|
return list(self._items)
|
|
|
|
def first(self) -> Any: # pragma: no cover - утилитарный метод
|
|
return self._items[0] if self._items else None
|
|
|
|
def one(self) -> Any: # pragma: no cover - утилитарный метод
|
|
if len(self._items) != 1:
|
|
raise ValueError("Expected exactly one result")
|
|
return self._items[0]
|
|
|
|
def one_or_none(self) -> Any: # pragma: no cover - утилитарный метод
|
|
if not self._items:
|
|
return None
|
|
if len(self._items) > 1:
|
|
raise ValueError("Expected zero or one result")
|
|
return self._items[0]
|
|
|
|
def __iter__(self): # pragma: no cover - утилитарный метод
|
|
return iter(self._items)
|
|
|
|
|
|
class FakeResult:
|
|
def __init__(self, value: Any = None) -> None:
|
|
self._value = value
|
|
|
|
def _as_iterable(self) -> list[Any]:
|
|
if isinstance(self._value, list):
|
|
return self._value
|
|
if self._value is None:
|
|
return []
|
|
return [self._value]
|
|
|
|
def scalar(self) -> Any:
|
|
items = self._as_iterable()
|
|
return items[0] if items else None
|
|
|
|
def scalar_one_or_none(self) -> Any:
|
|
items = self._as_iterable()
|
|
if not items:
|
|
return None
|
|
if len(items) > 1:
|
|
raise ValueError("Expected zero or one result")
|
|
return items[0]
|
|
|
|
def first(self) -> Any: # pragma: no cover - утилитарный метод
|
|
items = self._as_iterable()
|
|
return items[0] if items else None
|
|
|
|
def all(self) -> list[Any]: # pragma: no cover - утилитарный метод
|
|
return list(self._as_iterable())
|
|
|
|
def one_or_none(self) -> Any: # pragma: no cover - утилитарный метод
|
|
items = self._as_iterable()
|
|
if not items:
|
|
return None
|
|
if len(items) > 1:
|
|
raise ValueError("Expected zero or one result")
|
|
return items[0]
|
|
|
|
def scalars(self) -> FakeScalarResult: # pragma: no cover - утилитарный метод
|
|
return FakeScalarResult(self._as_iterable())
|
|
|
|
|
|
class FakeSession:
|
|
def __init__(self) -> None:
|
|
self.commits = 0
|
|
self.refreshed: list[Any] = []
|
|
self.added: list[Any] = []
|
|
self.execute_statements: list[Any] = []
|
|
self.execute_results: list[Any] = []
|
|
|
|
async def commit(self) -> None:
|
|
self.commits += 1
|
|
|
|
async def rollback(self) -> None: # pragma: no cover
|
|
return None
|
|
|
|
async def refresh(self, obj: Any) -> None:
|
|
self.refreshed.append(obj)
|
|
|
|
def add(self, obj: Any) -> None: # pragma: no cover - используется при создании транзакций
|
|
self.added.append(obj)
|
|
|
|
async def execute(self, statement: Any, *args: Any, **kwargs: Any) -> FakeResult:
|
|
self.execute_statements.append(statement)
|
|
if self.execute_results:
|
|
result = self.execute_results.pop(0)
|
|
if callable(result): # pragma: no cover - гибкость для будущих тестов
|
|
result = result(statement, *args, **kwargs)
|
|
else:
|
|
result = None
|
|
|
|
if isinstance(result, FakeResult):
|
|
return result
|
|
|
|
return FakeResult(result)
|
|
|
|
|
|
def _make_service(bot: DummyBot) -> PaymentService:
|
|
service = PaymentService.__new__(PaymentService) # type: ignore[call-arg]
|
|
service.bot = bot
|
|
service.yookassa_service = None
|
|
service.stars_service = None
|
|
service.mulenpay_service = None
|
|
service.pal24_service = None
|
|
service.cryptobot_service = None
|
|
service.heleket_service = None
|
|
return service
|
|
|
|
|
|
@pytest.fixture
|
|
def anyio_backend() -> str:
|
|
return "asyncio"
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
@pytest.mark.parametrize("status_field", ["payment_status", "status", "paymentStatus"])
|
|
async def test_process_mulenpay_callback_success(
|
|
monkeypatch: pytest.MonkeyPatch, status_field: str
|
|
) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
fake_session = FakeSession()
|
|
payment = SimpleNamespace(
|
|
uuid="mulen_uuid",
|
|
mulen_payment_id=123,
|
|
amount_kopeks=5000,
|
|
user_id=42,
|
|
transaction_id=None,
|
|
is_paid=False,
|
|
)
|
|
|
|
async def fake_get_by_uuid(db, uuid):
|
|
return payment
|
|
|
|
async def fake_get_by_id(db, mid):
|
|
return None
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_mulenpay_payment_by_uuid", fake_get_by_uuid)
|
|
monkeypatch.setattr(payment_service_module, "get_mulenpay_payment_by_mulen_id", fake_get_by_id)
|
|
|
|
transactions: list[Dict[str, Any]] = []
|
|
|
|
async def fake_create_transaction(db, **kwargs):
|
|
transactions.append(kwargs)
|
|
return SimpleNamespace(id=777, **kwargs)
|
|
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
|
|
updated_status: dict[str, Any] = {}
|
|
|
|
async def fake_update_status(db, payment=None, status=None, **kwargs):
|
|
payment.status = status
|
|
payment.is_paid = status == "success"
|
|
updated_status.update({"status": status, "kwargs": kwargs})
|
|
|
|
monkeypatch.setattr(payment_service_module, "update_mulenpay_payment_status", fake_update_status)
|
|
|
|
async def fake_link(db, payment=None, transaction_id=None):
|
|
payment.transaction_id = transaction_id
|
|
|
|
monkeypatch.setattr(payment_service_module, "link_mulenpay_payment_to_transaction", fake_link)
|
|
|
|
user = SimpleNamespace(
|
|
id=42,
|
|
telegram_id=100500,
|
|
balance_kopeks=0,
|
|
has_made_first_topup=False,
|
|
promo_group=None,
|
|
subscription=None,
|
|
referred_by_id=None,
|
|
referrer=None,
|
|
)
|
|
user.get_primary_promo_group = lambda: getattr(user, "promo_group", None)
|
|
|
|
async def fake_get_user(db, user_id):
|
|
return user
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_user_by_id", fake_get_user)
|
|
monkeypatch.setattr(type(settings), "format_price", lambda self, amount: f"{amount / 100:.2f}₽", raising=False)
|
|
|
|
referral_mock = SimpleNamespace(process_referral_topup=AsyncMock())
|
|
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_mock)
|
|
|
|
class DummyAdminService:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
self.calls: list[Any] = []
|
|
|
|
async def send_balance_topup_notification(self, *args, **kwargs):
|
|
self.calls.append((args, kwargs))
|
|
|
|
admin_service = DummyAdminService(bot)
|
|
monkeypatch.setitem(sys.modules, "app.services.admin_notification_service", SimpleNamespace(AdminNotificationService=lambda bot: admin_service))
|
|
|
|
service.build_topup_success_keyboard = AsyncMock(return_value=None)
|
|
|
|
payload = {
|
|
"uuid": "mulen_uuid",
|
|
"id": 123,
|
|
"amount": "50.00",
|
|
}
|
|
payload[status_field] = "success"
|
|
|
|
result = await service.process_mulenpay_callback(fake_session, payload)
|
|
|
|
assert result is True
|
|
assert transactions and transactions[0]["user_id"] == 42
|
|
assert payment.transaction_id == 777
|
|
assert updated_status["status"] == "success"
|
|
assert user.balance_kopeks == 5000
|
|
assert fake_session.commits >= 1
|
|
assert bot.sent_messages # сообщение пользователю отправлено
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_cryptobot_webhook_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
fake_session = FakeSession()
|
|
payment = SimpleNamespace(
|
|
invoice_id="inv_1",
|
|
user_id=7,
|
|
status="pending",
|
|
transaction_id=None,
|
|
amount="12.50",
|
|
asset="USDT",
|
|
amount_float=12.5,
|
|
)
|
|
|
|
async def fake_get_crypto(db, invoice_id):
|
|
return payment
|
|
|
|
async def fake_update_status(db, invoice_id, status, paid_at):
|
|
payment.status = status
|
|
payment.paid_at = paid_at
|
|
return payment
|
|
|
|
async def fake_link(db, invoice_id, transaction_id):
|
|
payment.transaction_id = transaction_id
|
|
|
|
fake_cryptobot_module = ModuleType("app.database.crud.cryptobot")
|
|
fake_cryptobot_module.get_cryptobot_payment_by_invoice_id = fake_get_crypto
|
|
fake_cryptobot_module.update_cryptobot_payment_status = fake_update_status
|
|
fake_cryptobot_module.link_cryptobot_payment_to_transaction = fake_link
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.cryptobot", fake_cryptobot_module)
|
|
|
|
transactions: list[Dict[str, Any]] = []
|
|
created_transaction: SimpleNamespace | None = None
|
|
|
|
async def fake_create_transaction(db, **kwargs):
|
|
nonlocal created_transaction
|
|
transactions.append(kwargs)
|
|
created_transaction = SimpleNamespace(id=888, **kwargs)
|
|
return created_transaction
|
|
|
|
fake_transaction_module = ModuleType("app.database.crud.transaction")
|
|
fake_transaction_module.create_transaction = fake_create_transaction
|
|
|
|
async def fake_get_transaction_by_id(db, transaction_id):
|
|
return created_transaction
|
|
|
|
fake_transaction_module.get_transaction_by_id = fake_get_transaction_by_id
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.transaction", fake_transaction_module)
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
|
|
user = SimpleNamespace(
|
|
id=7,
|
|
telegram_id=700,
|
|
balance_kopeks=0,
|
|
has_made_first_topup=False,
|
|
promo_group=None,
|
|
subscription=None,
|
|
referred_by_id=None,
|
|
referrer=None,
|
|
)
|
|
user.get_primary_promo_group = lambda: getattr(user, "promo_group", None)
|
|
|
|
async def fake_get_user_crypto(db, user_id):
|
|
return user
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_user_by_id", fake_get_user_crypto)
|
|
|
|
fake_user_module = ModuleType("app.database.crud.user")
|
|
fake_user_module.get_user_by_id = fake_get_user_crypto
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.user", fake_user_module)
|
|
|
|
referral_crypto = SimpleNamespace(process_referral_topup=AsyncMock())
|
|
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_crypto)
|
|
|
|
admin_calls: list[Any] = []
|
|
|
|
class DummyAdminService2:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
async def send_balance_topup_notification(self, *args, **kwargs):
|
|
admin_calls.append((args, kwargs))
|
|
|
|
monkeypatch.setitem(sys.modules, "app.services.admin_notification_service", SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminService2(bot)))
|
|
|
|
class DummyAsyncSession:
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc, tb):
|
|
return False
|
|
|
|
async def rollback(self): # pragma: no cover - defensive stub
|
|
return None
|
|
|
|
monkeypatch.setattr(cryptobot_module, "AsyncSessionLocal", lambda: DummyAsyncSession())
|
|
monkeypatch.setattr(payment_service_module.currency_converter, "usd_to_rub", AsyncMock(return_value=140.0))
|
|
monkeypatch.setattr(type(settings), "format_price", lambda self, amount: f"{amount / 100:.2f}₽", raising=False)
|
|
service.build_topup_success_keyboard = AsyncMock(return_value=None)
|
|
|
|
payload = {
|
|
"update_type": "invoice_paid",
|
|
"payload": {
|
|
"invoice_id": "inv_1",
|
|
"paid_at": "2024-01-01T12:00:00Z",
|
|
},
|
|
}
|
|
|
|
result = await service.process_cryptobot_webhook(fake_session, payload)
|
|
|
|
assert result is True
|
|
assert transactions and transactions[0]["amount_kopeks"] == 14000
|
|
assert user.balance_kopeks == 14000
|
|
assert payment.transaction_id == 888
|
|
assert bot.sent_messages
|
|
assert admin_calls
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_heleket_webhook_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
fake_session = FakeSession()
|
|
|
|
payment = SimpleNamespace(
|
|
uuid="heleket-uuid",
|
|
order_id="heleket-order",
|
|
user_id=77,
|
|
amount="150.00",
|
|
amount_float=150.0,
|
|
amount_kopeks=15000,
|
|
status="check",
|
|
payer_amount=None,
|
|
payer_currency=None,
|
|
exchange_rate=None,
|
|
discount_percent=None,
|
|
payment_url=None,
|
|
transaction_id=None,
|
|
)
|
|
|
|
async def fake_get_by_uuid(db, uuid):
|
|
return payment if uuid == payment.uuid else None
|
|
|
|
async def fake_get_by_order(db, order_id):
|
|
return payment if order_id == payment.order_id else None
|
|
|
|
async def fake_update(
|
|
db,
|
|
uuid,
|
|
*,
|
|
status=None,
|
|
payer_amount=None,
|
|
payer_currency=None,
|
|
exchange_rate=None,
|
|
discount_percent=None,
|
|
paid_at=None,
|
|
payment_url=None,
|
|
metadata=None,
|
|
):
|
|
if status is not None:
|
|
payment.status = status
|
|
if payer_amount is not None:
|
|
payment.payer_amount = payer_amount
|
|
if payer_currency is not None:
|
|
payment.payer_currency = payer_currency
|
|
if exchange_rate is not None:
|
|
payment.exchange_rate = exchange_rate
|
|
if discount_percent is not None:
|
|
payment.discount_percent = discount_percent
|
|
if payment_url is not None:
|
|
payment.payment_url = payment_url
|
|
payment.paid_at = paid_at
|
|
if metadata:
|
|
payment.metadata_json = metadata
|
|
return payment
|
|
|
|
async def fake_link(db, uuid, transaction_id):
|
|
payment.transaction_id = transaction_id
|
|
return payment
|
|
|
|
heleket_module = ModuleType("app.database.crud.heleket")
|
|
heleket_module.get_heleket_payment_by_uuid = fake_get_by_uuid
|
|
heleket_module.get_heleket_payment_by_order_id = fake_get_by_order
|
|
heleket_module.update_heleket_payment = fake_update
|
|
heleket_module.link_heleket_payment_to_transaction = fake_link
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.heleket", heleket_module)
|
|
|
|
transactions: list[Dict[str, Any]] = []
|
|
|
|
async def fake_create_transaction(db, **kwargs):
|
|
transactions.append(kwargs)
|
|
return SimpleNamespace(id=321, **kwargs)
|
|
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
|
|
user = SimpleNamespace(
|
|
id=77,
|
|
telegram_id=7700,
|
|
balance_kopeks=0,
|
|
has_made_first_topup=False,
|
|
promo_group=None,
|
|
subscription=None,
|
|
referred_by_id=None,
|
|
referrer=None,
|
|
language="ru",
|
|
)
|
|
user.get_primary_promo_group = lambda: getattr(user, "promo_group", None)
|
|
|
|
async def fake_get_user(db, user_id):
|
|
return user if user_id == user.id else None
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_user_by_id", fake_get_user)
|
|
monkeypatch.setattr("app.services.payment.heleket.format_referrer_info", lambda u: "")
|
|
|
|
monkeypatch.setattr(type(settings), "format_price", lambda self, amount: f"{amount / 100:.2f}₽", raising=False)
|
|
|
|
referral_stub = SimpleNamespace(process_referral_topup=AsyncMock())
|
|
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_stub)
|
|
|
|
admin_calls: list[Any] = []
|
|
|
|
class DummyAdminService:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
async def send_balance_topup_notification(self, *args, **kwargs):
|
|
admin_calls.append((args, kwargs))
|
|
|
|
monkeypatch.setitem(
|
|
sys.modules,
|
|
"app.services.admin_notification_service",
|
|
SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminService(bot)),
|
|
)
|
|
|
|
service.build_topup_success_keyboard = AsyncMock(return_value=None)
|
|
|
|
payload = {
|
|
"uuid": "heleket-uuid",
|
|
"status": "paid",
|
|
"payer_amount": "2.50",
|
|
"payer_currency": "USDT",
|
|
"discount_percent": -5,
|
|
"payer_amount_exchange_rate": "0.0166",
|
|
"paid_at": "2024-01-02T12:00:00Z",
|
|
"url": "https://pay.example",
|
|
}
|
|
|
|
result = await service.process_heleket_webhook(fake_session, payload)
|
|
|
|
assert result is True
|
|
assert transactions and transactions[0]["payment_method"] == PaymentMethod.HELEKET
|
|
assert payment.transaction_id == 321
|
|
assert user.balance_kopeks == 15000
|
|
assert user.has_made_first_topup is True
|
|
assert fake_session.commits >= 1
|
|
assert bot.sent_messages
|
|
assert admin_calls
|
|
referral_stub.process_referral_topup.assert_awaited_once()
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_yookassa_webhook_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
fake_session = FakeSession()
|
|
payment = SimpleNamespace(
|
|
yookassa_payment_id="yk_123",
|
|
user_id=21,
|
|
amount_kopeks=10000,
|
|
transaction_id=None,
|
|
status="pending",
|
|
is_paid=False,
|
|
)
|
|
|
|
async def fake_get_payment(db, payment_id):
|
|
return payment
|
|
|
|
async def fake_update(db, payment_id, status, is_paid, is_captured, captured_at, payment_method_type):
|
|
payment.status = status
|
|
payment.is_paid = is_paid
|
|
payment.captured_at = captured_at
|
|
return payment
|
|
|
|
async def fake_link(db, payment_id, transaction_id):
|
|
payment.transaction_id = transaction_id
|
|
|
|
yk_module = ModuleType("app.database.crud.yookassa")
|
|
yk_module.get_yookassa_payment_by_id = fake_get_payment
|
|
yk_module.update_yookassa_payment_status = fake_update
|
|
yk_module.link_yookassa_payment_to_transaction = fake_link
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.yookassa", yk_module)
|
|
|
|
transactions: list[Dict[str, Any]] = []
|
|
|
|
async def fake_create_transaction(db, **kwargs):
|
|
transactions.append(kwargs)
|
|
return SimpleNamespace(id=999, **kwargs)
|
|
|
|
trx_module = ModuleType("app.database.crud.transaction")
|
|
trx_module.create_transaction = fake_create_transaction
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.transaction", trx_module)
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
|
|
user = SimpleNamespace(
|
|
id=21,
|
|
telegram_id=2100,
|
|
balance_kopeks=0,
|
|
has_made_first_topup=False,
|
|
promo_group=None,
|
|
subscription=None,
|
|
referred_by_id=None,
|
|
referrer=None,
|
|
)
|
|
user.get_primary_promo_group = lambda: getattr(user, "promo_group", None)
|
|
|
|
async def fake_get_user(db, user_id):
|
|
return user
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_user_by_id", fake_get_user)
|
|
monkeypatch.setattr(type(settings), "format_price", lambda self, amount: f"{amount / 100:.2f}₽", raising=False)
|
|
|
|
referral_mock = SimpleNamespace(process_referral_topup=AsyncMock())
|
|
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_mock)
|
|
|
|
admin_calls: list[Any] = []
|
|
|
|
class DummyAdminService:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
async def send_balance_topup_notification(self, *args, **kwargs):
|
|
admin_calls.append((args, kwargs))
|
|
|
|
monkeypatch.setitem(sys.modules, "app.services.admin_notification_service", SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminService(bot)))
|
|
service.build_topup_success_keyboard = AsyncMock(return_value=None)
|
|
|
|
payload = {
|
|
"object": {
|
|
"id": "yk_123",
|
|
"status": "succeeded",
|
|
"paid": True,
|
|
"payment_method": {"type": "bank_card"},
|
|
}
|
|
}
|
|
|
|
result = await service.process_yookassa_webhook(fake_session, payload)
|
|
|
|
assert result is True
|
|
assert transactions and transactions[0]["amount_kopeks"] == 10000
|
|
assert payment.transaction_id == 999
|
|
assert payment.is_paid is True
|
|
assert user.balance_kopeks == 10000
|
|
assert bot.sent_messages
|
|
assert admin_calls
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_yookassa_webhook_uses_remote_status(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
fake_session = FakeSession()
|
|
payment = SimpleNamespace(
|
|
yookassa_payment_id="yk_789",
|
|
user_id=42,
|
|
amount_kopeks=20000,
|
|
transaction_id=None,
|
|
status="pending",
|
|
is_paid=False,
|
|
)
|
|
|
|
async def fake_get_payment(db, payment_id):
|
|
return payment
|
|
|
|
async def fake_update(db, payment_id, status, is_paid, is_captured, captured_at, payment_method_type):
|
|
payment.status = status
|
|
payment.is_paid = is_paid
|
|
payment.captured_at = captured_at
|
|
payment.payment_method_type = payment_method_type
|
|
return payment
|
|
|
|
async def fake_link(db, payment_id, transaction_id):
|
|
payment.transaction_id = transaction_id
|
|
|
|
yk_module = ModuleType("app.database.crud.yookassa")
|
|
yk_module.get_yookassa_payment_by_id = fake_get_payment
|
|
yk_module.update_yookassa_payment_status = fake_update
|
|
yk_module.link_yookassa_payment_to_transaction = fake_link
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.yookassa", yk_module)
|
|
|
|
transactions: list[Dict[str, Any]] = []
|
|
|
|
async def fake_create_transaction(db, **kwargs):
|
|
transactions.append(kwargs)
|
|
return SimpleNamespace(id=555, **kwargs)
|
|
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
|
|
user = SimpleNamespace(
|
|
id=42,
|
|
telegram_id=4200,
|
|
balance_kopeks=0,
|
|
has_made_first_topup=False,
|
|
promo_group=None,
|
|
subscription=None,
|
|
referred_by_id=None,
|
|
referrer=None,
|
|
)
|
|
|
|
async def fake_get_user(db, user_id):
|
|
return user
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_user_by_id", fake_get_user)
|
|
monkeypatch.setattr(type(settings), "format_price", lambda self, amount: f"{amount / 100:.2f}₽", raising=False)
|
|
|
|
referral_mock = SimpleNamespace(process_referral_topup=AsyncMock())
|
|
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_mock)
|
|
|
|
admin_calls: list[Any] = []
|
|
|
|
class DummyAdminService:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
async def send_balance_topup_notification(self, *args, **kwargs):
|
|
admin_calls.append((args, kwargs))
|
|
|
|
monkeypatch.setitem(
|
|
sys.modules,
|
|
"app.services.admin_notification_service",
|
|
SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminService(bot)),
|
|
)
|
|
|
|
service.build_topup_success_keyboard = AsyncMock(return_value=None)
|
|
|
|
remote_payload = {
|
|
"id": "yk_789",
|
|
"status": "succeeded",
|
|
"paid": True,
|
|
"amount_value": 200.0,
|
|
"amount_currency": "rub",
|
|
"payment_method_type": "bank_card",
|
|
"refundable": True,
|
|
}
|
|
|
|
get_info_mock = AsyncMock(return_value=remote_payload)
|
|
service.yookassa_service = SimpleNamespace(get_payment_info=get_info_mock)
|
|
|
|
payload = {
|
|
"object": {
|
|
"id": "yk_789",
|
|
"status": "pending",
|
|
"paid": False,
|
|
}
|
|
}
|
|
|
|
result = await service.process_yookassa_webhook(fake_session, payload)
|
|
|
|
assert result is True
|
|
assert payment.status == "succeeded"
|
|
assert payment.is_paid is True
|
|
assert transactions and transactions[0]["amount_kopeks"] == 20000
|
|
assert payment.transaction_id == 555
|
|
get_info_mock.assert_awaited_once_with("yk_789")
|
|
assert admin_calls
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_yookassa_webhook_handles_cancellation(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
fake_session = FakeSession()
|
|
payment = SimpleNamespace(
|
|
yookassa_payment_id="yk_cancel",
|
|
user_id=77,
|
|
amount_kopeks=5000,
|
|
transaction_id=None,
|
|
status="pending",
|
|
is_paid=False,
|
|
captured_at=None,
|
|
payment_method_type=None,
|
|
)
|
|
|
|
async def fake_get_payment(db, payment_id):
|
|
return payment
|
|
|
|
monkeypatch.setattr(
|
|
payment_service_module,
|
|
"get_yookassa_payment_by_id",
|
|
fake_get_payment,
|
|
)
|
|
|
|
get_info_mock = AsyncMock(
|
|
return_value={
|
|
"id": "yk_cancel",
|
|
"status": "canceled",
|
|
"paid": False,
|
|
"amount_value": 50.0,
|
|
"amount_currency": "RUB",
|
|
}
|
|
)
|
|
service.yookassa_service = SimpleNamespace(get_payment_info=get_info_mock)
|
|
|
|
payload = {
|
|
"object": {
|
|
"id": "yk_cancel",
|
|
"status": "pending",
|
|
"paid": False,
|
|
}
|
|
}
|
|
|
|
result = await service.process_yookassa_webhook(fake_session, payload)
|
|
|
|
assert result is True
|
|
assert payment.status == "canceled"
|
|
assert payment.is_paid is False
|
|
assert fake_session.commits == 1
|
|
assert fake_session.refreshed and fake_session.refreshed[0] is payment
|
|
assert bot.sent_messages == []
|
|
get_info_mock.assert_awaited_once_with("yk_cancel")
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_yookassa_webhook_restores_missing_payment(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
fake_session = FakeSession()
|
|
|
|
restored_payment = SimpleNamespace(
|
|
yookassa_payment_id="yk_456",
|
|
user_id=21,
|
|
amount_kopeks=0,
|
|
status="pending",
|
|
is_paid=False,
|
|
transaction_id=None,
|
|
description="",
|
|
payment_method_type=None,
|
|
confirmation_url=None,
|
|
metadata_json=None,
|
|
test_mode=False,
|
|
refundable=False,
|
|
)
|
|
|
|
get_calls = {"count": 0}
|
|
|
|
async def fake_get_payment(db, payment_id):
|
|
get_calls["count"] += 1
|
|
if get_calls["count"] == 1:
|
|
return None
|
|
return restored_payment
|
|
|
|
async def fake_create_payment(**kwargs: Any):
|
|
restored_payment.user_id = kwargs["user_id"]
|
|
restored_payment.amount_kopeks = kwargs["amount_kopeks"]
|
|
restored_payment.status = kwargs["status"]
|
|
restored_payment.description = kwargs["description"]
|
|
restored_payment.payment_method_type = kwargs["payment_method_type"]
|
|
restored_payment.confirmation_url = kwargs["confirmation_url"]
|
|
restored_payment.metadata_json = kwargs["metadata_json"]
|
|
restored_payment.test_mode = kwargs["test_mode"]
|
|
restored_payment.yookassa_payment_id = kwargs["yookassa_payment_id"]
|
|
restored_payment.yookassa_created_at = kwargs["yookassa_created_at"]
|
|
return restored_payment
|
|
|
|
async def fake_update_status(
|
|
db,
|
|
yookassa_payment_id,
|
|
status,
|
|
is_paid,
|
|
is_captured,
|
|
captured_at,
|
|
payment_method_type,
|
|
):
|
|
restored_payment.status = status
|
|
restored_payment.is_paid = is_paid
|
|
restored_payment.is_captured = is_captured
|
|
restored_payment.captured_at = captured_at
|
|
restored_payment.payment_method_type = payment_method_type
|
|
return restored_payment
|
|
|
|
async def fake_link(db, yookassa_payment_id, transaction_id):
|
|
restored_payment.transaction_id = transaction_id
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_yookassa_payment_by_id", fake_get_payment)
|
|
monkeypatch.setattr(payment_service_module, "create_yookassa_payment", fake_create_payment)
|
|
monkeypatch.setattr(payment_service_module, "update_yookassa_payment_status", fake_update_status)
|
|
monkeypatch.setattr(payment_service_module, "link_yookassa_payment_to_transaction", fake_link)
|
|
|
|
transactions: list[Dict[str, Any]] = []
|
|
|
|
async def fake_create_transaction(db, **kwargs):
|
|
transactions.append(kwargs)
|
|
return SimpleNamespace(id=555, **kwargs)
|
|
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
|
|
user = SimpleNamespace(
|
|
id=21,
|
|
telegram_id=2100,
|
|
balance_kopeks=0,
|
|
has_made_first_topup=False,
|
|
promo_group=None,
|
|
subscription=None,
|
|
referred_by_id=None,
|
|
referrer=None,
|
|
)
|
|
user.get_primary_promo_group = lambda: getattr(user, "promo_group", None)
|
|
|
|
async def fake_get_user(db, user_id):
|
|
return user
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_user_by_id", fake_get_user)
|
|
monkeypatch.setattr(type(settings), "format_price", lambda self, amount: f"{amount / 100:.2f}₽", raising=False)
|
|
|
|
referral_mock = SimpleNamespace(process_referral_topup=AsyncMock())
|
|
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_mock)
|
|
|
|
admin_calls: list[Any] = []
|
|
|
|
class DummyAdminService:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
async def send_balance_topup_notification(self, *args, **kwargs):
|
|
admin_calls.append((args, kwargs))
|
|
|
|
monkeypatch.setitem(sys.modules, "app.services.admin_notification_service", SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminService(bot)))
|
|
service.build_topup_success_keyboard = AsyncMock(return_value=None)
|
|
|
|
payload = {
|
|
"object": {
|
|
"id": "yk_456",
|
|
"status": "succeeded",
|
|
"paid": True,
|
|
"amount": {"value": "150.00", "currency": "RUB"},
|
|
"metadata": {"user_id": "21", "payment_purpose": "balance_topup"},
|
|
"description": "Пополнение",
|
|
"payment_method": {"type": "bank_card"},
|
|
"created_at": "2024-01-02T12:00:00Z",
|
|
"captured_at": "2024-01-02T12:05:00Z",
|
|
"confirmation": {"confirmation_url": "https://pay.example"},
|
|
}
|
|
}
|
|
|
|
result = await service.process_yookassa_webhook(fake_session, payload)
|
|
|
|
assert result is True
|
|
assert get_calls["count"] >= 2 # повторный запрос после восстановления
|
|
assert restored_payment.amount_kopeks == 15000
|
|
assert restored_payment.is_paid is True
|
|
assert transactions and transactions[0]["amount_kopeks"] == 15000
|
|
assert restored_payment.transaction_id == 555
|
|
assert user.balance_kopeks == 15000
|
|
assert bot.sent_messages
|
|
assert admin_calls
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_yookassa_webhook_missing_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
service = _make_service(DummyBot())
|
|
db = FakeSession()
|
|
|
|
async def fake_get_payment(db_session, payment_id):
|
|
return None
|
|
|
|
create_mock = AsyncMock()
|
|
update_mock = AsyncMock()
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_yookassa_payment_by_id", fake_get_payment)
|
|
monkeypatch.setattr(payment_service_module, "create_yookassa_payment", create_mock)
|
|
monkeypatch.setattr(payment_service_module, "update_yookassa_payment_status", update_mock)
|
|
|
|
payload = {"object": {"id": "yk_missing", "status": "succeeded", "paid": True}}
|
|
|
|
result = await service.process_yookassa_webhook(db, payload)
|
|
|
|
assert result is False
|
|
create_mock.assert_not_awaited()
|
|
update_mock.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_yookassa_webhook_missing_id(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
db = FakeSession()
|
|
|
|
result = await service.process_yookassa_webhook(db, {"object": {}})
|
|
assert result is False
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_pal24_callback_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
service.pal24_service = SimpleNamespace(is_configured=True)
|
|
fake_session = FakeSession()
|
|
payment = SimpleNamespace(
|
|
bill_id="BILL-1",
|
|
order_id="order-1",
|
|
amount_kopeks=5000,
|
|
user_id=33,
|
|
transaction_id=None,
|
|
is_paid=False,
|
|
status="NEW",
|
|
metadata_json={},
|
|
payment_method=None,
|
|
paid_at=None,
|
|
)
|
|
|
|
async def fake_get_by_order(db, order_id):
|
|
return payment
|
|
|
|
async def fake_get_by_bill(db, bill_id):
|
|
return payment
|
|
|
|
async def fake_update(db, payment_obj, **kwargs):
|
|
payment.status = kwargs.get("status", payment.status)
|
|
payment.is_paid = kwargs.get("is_paid", payment.is_paid)
|
|
payment.payment_status = kwargs.get("payment_status", payment.status)
|
|
payment.callback_payload = kwargs.get("callback_payload")
|
|
return payment
|
|
|
|
async def fake_link(db, payment_obj, transaction_id):
|
|
payment.transaction_id = transaction_id
|
|
|
|
pal_module = ModuleType("app.database.crud.pal24")
|
|
pal_module.get_pal24_payment_by_order_id = fake_get_by_order
|
|
pal_module.get_pal24_payment_by_bill_id = fake_get_by_bill
|
|
pal_module.update_pal24_payment_status = fake_update
|
|
pal_module.link_pal24_payment_to_transaction = fake_link
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.pal24", pal_module)
|
|
monkeypatch.setattr(payment_service_module, "get_pal24_payment_by_order_id", fake_get_by_order)
|
|
monkeypatch.setattr(payment_service_module, "get_pal24_payment_by_bill_id", fake_get_by_bill)
|
|
monkeypatch.setattr(payment_service_module, "update_pal24_payment_status", fake_update)
|
|
monkeypatch.setattr(payment_service_module, "link_pal24_payment_to_transaction", fake_link)
|
|
|
|
async def fake_create_transaction(db, **kwargs):
|
|
payment.transaction_id = 654
|
|
return SimpleNamespace(id=654, **kwargs)
|
|
|
|
trx_module = ModuleType("app.database.crud.transaction")
|
|
trx_module.create_transaction = fake_create_transaction
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.transaction", trx_module)
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
|
|
user = SimpleNamespace(
|
|
id=33,
|
|
telegram_id=3300,
|
|
balance_kopeks=0,
|
|
has_made_first_topup=False,
|
|
promo_group=None,
|
|
subscription=None,
|
|
referred_by_id=None,
|
|
referrer=None,
|
|
language="ru",
|
|
)
|
|
user.get_primary_promo_group = lambda: getattr(user, "promo_group", None)
|
|
|
|
async def fake_get_user(db, user_id):
|
|
return user
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_user_by_id", fake_get_user)
|
|
monkeypatch.setattr(type(settings), "format_price", lambda self, amount: f"{amount / 100:.2f}₽", raising=False)
|
|
|
|
referral_pal = SimpleNamespace(process_referral_topup=AsyncMock())
|
|
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_pal)
|
|
|
|
admin_calls: list[Any] = []
|
|
|
|
class DummyAdminServicePal:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
async def send_balance_topup_notification(self, *args, **kwargs):
|
|
admin_calls.append((args, kwargs))
|
|
|
|
monkeypatch.setitem(
|
|
sys.modules,
|
|
"app.services.admin_notification_service",
|
|
SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminServicePal(bot)),
|
|
)
|
|
|
|
user_cart_stub = SimpleNamespace(
|
|
user_cart_service=SimpleNamespace(has_user_cart=AsyncMock(return_value=True))
|
|
)
|
|
monkeypatch.setitem(sys.modules, "app.services.user_cart_service", user_cart_stub)
|
|
|
|
class DummyTypes:
|
|
class InlineKeyboardMarkup:
|
|
def __init__(self, inline_keyboard=None, **kwargs):
|
|
self.inline_keyboard = inline_keyboard or []
|
|
self.kwargs = kwargs
|
|
|
|
class InlineKeyboardButton:
|
|
def __init__(self, *args, **kwargs):
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
monkeypatch.setitem(sys.modules, "aiogram", SimpleNamespace(types=DummyTypes))
|
|
monkeypatch.setitem(
|
|
sys.modules,
|
|
"app.localization.texts",
|
|
SimpleNamespace(get_texts=lambda language: SimpleNamespace(t=lambda key, default=None: default)),
|
|
)
|
|
|
|
service.build_topup_success_keyboard = AsyncMock(return_value=None)
|
|
|
|
payload = {
|
|
"InvId": "order-1",
|
|
"OutSum": "50.00",
|
|
"Status": "SUCCESS",
|
|
"TrsId": "trs-1",
|
|
}
|
|
|
|
result = await service.process_pal24_callback(fake_session, payload)
|
|
|
|
assert result is True
|
|
assert payment.transaction_id == 654
|
|
assert user.balance_kopeks == 5000
|
|
assert bot.sent_messages
|
|
saved_cart_message = bot.sent_messages[-1]
|
|
reply_markup = saved_cart_message["kwargs"].get("reply_markup")
|
|
assert reply_markup is not None
|
|
assert reply_markup.inline_keyboard[0][0].kwargs["callback_data"] == "return_to_saved_cart"
|
|
assert admin_calls
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_get_pal24_payment_status_auto_finalize(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
|
|
class DummyPal24Service:
|
|
BILL_SUCCESS_STATES = {"SUCCESS", "OVERPAID"}
|
|
BILL_FAILED_STATES = {"FAIL"}
|
|
BILL_PENDING_STATES = {"NEW", "PROCESS", "UNDERPAID"}
|
|
|
|
async def get_bill_status(self, bill_id: str) -> Dict[str, Any]:
|
|
return {
|
|
"status": "SUCCESS",
|
|
"bill": {
|
|
"status": "SUCCESS",
|
|
"payments": [
|
|
{
|
|
"id": "trs-auto-1",
|
|
"status": "SUCCESS",
|
|
"method": "SBP",
|
|
"balance_amount": "50.00",
|
|
"balance_currency": "RUB",
|
|
}
|
|
],
|
|
},
|
|
}
|
|
|
|
async def get_payment_status(self, payment_id: str) -> Optional[Dict[str, Any]]:
|
|
return None
|
|
|
|
async def get_bill_payments(self, bill_id: str) -> Optional[Dict[str, Any]]:
|
|
return {
|
|
"data": [
|
|
{
|
|
"id": "trs-auto-1",
|
|
"bill_id": bill_id,
|
|
"status": "SUCCESS",
|
|
"payment_method": "SBP",
|
|
}
|
|
]
|
|
}
|
|
|
|
service.pal24_service = DummyPal24Service()
|
|
|
|
fake_session = FakeSession()
|
|
payment = SimpleNamespace(
|
|
id=77,
|
|
bill_id="BILL-AUTO",
|
|
order_id="order-auto",
|
|
amount_kopeks=5000,
|
|
user_id=91,
|
|
transaction_id=None,
|
|
is_paid=False,
|
|
status="NEW",
|
|
metadata_json={},
|
|
payment_id=None,
|
|
payment_method=None,
|
|
paid_at=None,
|
|
)
|
|
|
|
async def fake_get_payment_by_id(db, local_id):
|
|
return payment
|
|
|
|
async def fake_update_payment(db, payment_obj, **kwargs):
|
|
for key, value in kwargs.items():
|
|
setattr(payment, key, value)
|
|
return payment
|
|
|
|
async def fake_link_payment(db, payment_obj, transaction_id):
|
|
payment.transaction_id = transaction_id
|
|
return payment
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_pal24_payment_by_id", fake_get_payment_by_id)
|
|
monkeypatch.setattr(payment_service_module, "update_pal24_payment_status", fake_update_payment)
|
|
monkeypatch.setattr(payment_service_module, "link_pal24_payment_to_transaction", fake_link_payment)
|
|
|
|
transactions: list[Dict[str, Any]] = []
|
|
|
|
async def fake_create_transaction(db, **kwargs):
|
|
transactions.append(kwargs)
|
|
payment.transaction_id = 999
|
|
return SimpleNamespace(id=999, **kwargs)
|
|
|
|
monkeypatch.setattr(payment_service_module, "create_transaction", fake_create_transaction)
|
|
|
|
user = SimpleNamespace(
|
|
id=91,
|
|
telegram_id=9100,
|
|
balance_kopeks=0,
|
|
has_made_first_topup=False,
|
|
promo_group=None,
|
|
subscription=None,
|
|
referred_by_id=None,
|
|
referrer=None,
|
|
language="ru",
|
|
)
|
|
user.get_primary_promo_group = lambda: getattr(user, "promo_group", None)
|
|
|
|
async def fake_get_user(db, user_id):
|
|
return user
|
|
|
|
monkeypatch.setattr(payment_service_module, "get_user_by_id", fake_get_user)
|
|
monkeypatch.setattr(type(settings), "format_price", lambda self, amount: f"{amount / 100:.2f}₽", raising=False)
|
|
|
|
referral_stub = SimpleNamespace(process_referral_topup=AsyncMock())
|
|
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_stub)
|
|
|
|
admin_notifications: list[Any] = []
|
|
|
|
class DummyAdminService:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
async def send_balance_topup_notification(self, *args, **kwargs):
|
|
admin_notifications.append((args, kwargs))
|
|
|
|
monkeypatch.setitem(
|
|
sys.modules,
|
|
"app.services.admin_notification_service",
|
|
SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminService(bot)),
|
|
)
|
|
|
|
user_cart_stub = SimpleNamespace(
|
|
user_cart_service=SimpleNamespace(has_user_cart=AsyncMock(return_value=False))
|
|
)
|
|
monkeypatch.setitem(sys.modules, "app.services.user_cart_service", user_cart_stub)
|
|
|
|
class DummyTypes:
|
|
class InlineKeyboardMarkup:
|
|
def __init__(self, inline_keyboard=None, **kwargs):
|
|
self.inline_keyboard = inline_keyboard or []
|
|
self.kwargs = kwargs
|
|
|
|
class InlineKeyboardButton:
|
|
def __init__(self, *args, **kwargs):
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
monkeypatch.setitem(sys.modules, "aiogram", SimpleNamespace(types=DummyTypes))
|
|
|
|
service.build_topup_success_keyboard = AsyncMock(return_value=None)
|
|
|
|
result = await service.get_pal24_payment_status(fake_session, payment.id)
|
|
|
|
assert result is not None
|
|
assert payment.transaction_id == 999
|
|
assert user.balance_kopeks == 5000
|
|
assert bot.sent_messages
|
|
assert admin_notifications
|
|
assert transactions and transactions[0]["user_id"] == 91
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_process_pal24_callback_payment_not_found(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
bot = DummyBot()
|
|
service = _make_service(bot)
|
|
service.pal24_service = SimpleNamespace(is_configured=True)
|
|
db = FakeSession()
|
|
|
|
async def fake_get_by_order(db, order_id):
|
|
return None
|
|
|
|
async def fake_get_by_bill(db, bill_id):
|
|
return None
|
|
|
|
pal_module = ModuleType("app.database.crud.pal24")
|
|
pal_module.get_pal24_payment_by_order_id = fake_get_by_order
|
|
pal_module.get_pal24_payment_by_bill_id = fake_get_by_bill
|
|
pal_module.update_pal24_payment_status = AsyncMock()
|
|
pal_module.link_pal24_payment_to_transaction = AsyncMock()
|
|
monkeypatch.setitem(sys.modules, "app.database.crud.pal24", pal_module)
|
|
monkeypatch.setattr(payment_service_module, "get_pal24_payment_by_order_id", fake_get_by_order)
|
|
monkeypatch.setattr(payment_service_module, "get_pal24_payment_by_bill_id", fake_get_by_bill)
|
|
|
|
payload = {
|
|
"InvId": "order-unknown",
|
|
"OutSum": "10.00",
|
|
"Status": "SUCCESS",
|
|
}
|
|
|
|
result = await service.process_pal24_callback(db, payload)
|
|
assert result is False
|