mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-20 03:40:26 +00:00
332 lines
10 KiB
Python
332 lines
10 KiB
Python
"""Тесты Pal24 сценариев PaymentService."""
|
|
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
import sys
|
|
from datetime import datetime
|
|
from types import SimpleNamespace
|
|
|
|
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
|
|
from app.services.pal24_service import Pal24APIError # noqa: E402
|
|
|
|
|
|
@pytest.fixture
|
|
def anyio_backend() -> str:
|
|
return "asyncio"
|
|
|
|
|
|
class DummySession:
|
|
async def commit(self) -> None: # pragma: no cover
|
|
return None
|
|
|
|
|
|
class DummyLocalPayment:
|
|
def __init__(self, payment_id: int = 404) -> None:
|
|
self.id = payment_id
|
|
self.created_at = datetime(2024, 1, 2, 10, 0, 0)
|
|
|
|
|
|
class StubPal24Service:
|
|
def __init__(
|
|
self,
|
|
*,
|
|
configured: bool = True,
|
|
response: Optional[Dict[str, Any]] = None,
|
|
) -> None:
|
|
self.is_configured = configured
|
|
self.response = response or {
|
|
"success": True,
|
|
"bill_id": "BILL-1",
|
|
"transfer_url": "https://pal24/sbp",
|
|
"link_url": "https://pal24/card",
|
|
"status": "NEW",
|
|
}
|
|
self.calls: list[Dict[str, Any]] = []
|
|
self.raise_error: Optional[Exception] = None
|
|
self.status_response: Optional[Dict[str, Any]] = {"status": "NEW"}
|
|
self.payment_status_response: Optional[Dict[str, Any]] = None
|
|
self.bill_payments_response: Optional[Dict[str, Any]] = None
|
|
self.status_calls: list[str] = []
|
|
self.payment_status_calls: list[str] = []
|
|
self.bill_payments_calls: list[str] = []
|
|
|
|
async def create_bill(self, **kwargs: Any) -> Dict[str, Any]:
|
|
self.calls.append(kwargs)
|
|
if self.raise_error:
|
|
raise self.raise_error
|
|
return self.response
|
|
|
|
async def get_bill_status(self, bill_id: str) -> Optional[Dict[str, Any]]:
|
|
self.status_calls.append(bill_id)
|
|
return self.status_response
|
|
|
|
async def get_payment_status(self, payment_id: str) -> Optional[Dict[str, Any]]:
|
|
self.payment_status_calls.append(payment_id)
|
|
return self.payment_status_response
|
|
|
|
async def get_bill_payments(self, bill_id: str) -> Optional[Dict[str, Any]]:
|
|
self.bill_payments_calls.append(bill_id)
|
|
return self.bill_payments_response
|
|
|
|
|
|
def _make_service(stub: Optional[StubPal24Service]) -> PaymentService:
|
|
service = PaymentService.__new__(PaymentService) # type: ignore[call-arg]
|
|
service.bot = None
|
|
service.pal24_service = stub
|
|
service.mulenpay_service = None
|
|
service.yookassa_service = None
|
|
service.cryptobot_service = None
|
|
service.stars_service = None
|
|
service.heleket_service = None
|
|
return service
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_create_pal24_payment_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
stub = StubPal24Service()
|
|
service = _make_service(stub)
|
|
db = DummySession()
|
|
|
|
captured_args: Dict[str, Any] = {}
|
|
|
|
async def fake_create_pal24_payment(*args: Any, **kwargs: Any) -> DummyLocalPayment:
|
|
captured_args.update(kwargs)
|
|
if args:
|
|
captured_args["db_arg"] = args[0]
|
|
return DummyLocalPayment(payment_id=321)
|
|
|
|
monkeypatch.setattr(
|
|
payment_service_module,
|
|
"create_pal24_payment",
|
|
fake_create_pal24_payment,
|
|
raising=False,
|
|
)
|
|
monkeypatch.setattr(settings, "PAL24_MIN_AMOUNT_KOPEKS", 1000, raising=False)
|
|
monkeypatch.setattr(settings, "PAL24_MAX_AMOUNT_KOPEKS", 1_000_000, raising=False)
|
|
|
|
result = await service.create_pal24_payment(
|
|
db=db,
|
|
user_id=15,
|
|
amount_kopeks=50000,
|
|
description="Оплата подписки",
|
|
language="ru",
|
|
ttl_seconds=600,
|
|
payer_email="user@example.com",
|
|
payment_method="card",
|
|
)
|
|
|
|
assert result is not None
|
|
assert result["local_payment_id"] == 321
|
|
assert result["bill_id"] == "BILL-1"
|
|
assert result["payment_method"] == "card"
|
|
assert result["link_url"] == "https://pal24/sbp"
|
|
assert result["card_url"] == "https://pal24/card"
|
|
assert stub.calls and stub.calls[0]["payment_method"] == "BANK_CARD"
|
|
assert stub.calls and stub.calls[0]["amount_kopeks"] == 50000
|
|
assert "links" in captured_args["metadata"]
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_create_pal24_payment_default_method(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
stub = StubPal24Service()
|
|
service = _make_service(stub)
|
|
db = DummySession()
|
|
|
|
async def fake_create_pal24_payment(*args: Any, **kwargs: Any) -> DummyLocalPayment:
|
|
return DummyLocalPayment(payment_id=111)
|
|
|
|
monkeypatch.setattr(
|
|
payment_service_module,
|
|
"create_pal24_payment",
|
|
fake_create_pal24_payment,
|
|
raising=False,
|
|
)
|
|
|
|
monkeypatch.setattr(settings, "PAL24_MIN_AMOUNT_KOPEKS", 1000, raising=False)
|
|
monkeypatch.setattr(settings, "PAL24_MAX_AMOUNT_KOPEKS", 1_000_000, raising=False)
|
|
|
|
result = await service.create_pal24_payment(
|
|
db=db,
|
|
user_id=42,
|
|
amount_kopeks=10_000,
|
|
description="Пополнение",
|
|
language="ru",
|
|
)
|
|
|
|
assert result is not None
|
|
assert stub.calls and stub.calls[0]["payment_method"] == "SBP"
|
|
assert result["payment_method"] == "sbp"
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_create_pal24_payment_limits_and_configuration(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
stub = StubPal24Service()
|
|
service = _make_service(stub)
|
|
db = DummySession()
|
|
|
|
monkeypatch.setattr(settings, "PAL24_MIN_AMOUNT_KOPEKS", 5000, raising=False)
|
|
monkeypatch.setattr(settings, "PAL24_MAX_AMOUNT_KOPEKS", 20_000, raising=False)
|
|
|
|
result_low = await service.create_pal24_payment(
|
|
db=db,
|
|
user_id=1,
|
|
amount_kopeks=1000,
|
|
description="Пополнение",
|
|
language="ru",
|
|
)
|
|
assert result_low is None
|
|
|
|
result_high = await service.create_pal24_payment(
|
|
db=db,
|
|
user_id=1,
|
|
amount_kopeks=50_000,
|
|
description="Пополнение",
|
|
language="ru",
|
|
)
|
|
assert result_high is None
|
|
|
|
service_not_configured = _make_service(StubPal24Service(configured=False))
|
|
result_config = await service_not_configured.create_pal24_payment(
|
|
db=db,
|
|
user_id=1,
|
|
amount_kopeks=10_000,
|
|
description="Пополнение",
|
|
language="ru",
|
|
)
|
|
assert result_config is None
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_create_pal24_payment_handles_api_errors(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
stub = StubPal24Service()
|
|
stub.raise_error = Pal24APIError("api failed")
|
|
service = _make_service(stub)
|
|
db = DummySession()
|
|
|
|
monkeypatch.setattr(settings, "PAL24_MIN_AMOUNT_KOPEKS", 1000, raising=False)
|
|
monkeypatch.setattr(settings, "PAL24_MAX_AMOUNT_KOPEKS", 10_000, raising=False)
|
|
|
|
result = await service.create_pal24_payment(
|
|
db=db,
|
|
user_id=5,
|
|
amount_kopeks=2000,
|
|
description="Пополнение",
|
|
language="ru",
|
|
)
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.anyio("asyncio")
|
|
async def test_get_pal24_payment_status_updates_from_remote(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
stub = StubPal24Service()
|
|
stub.status_response = {"status": "SUCCESS"}
|
|
stub.payment_status_response = {
|
|
"success": True,
|
|
"id": "PAY-1",
|
|
"bill_id": "BILL-1",
|
|
"status": "SUCCESS",
|
|
"payment_method": "SBP",
|
|
"account_amount": "700.00",
|
|
"from_card": "676754******1234",
|
|
}
|
|
stub.bill_payments_response = {
|
|
"data": [
|
|
{
|
|
"id": "PAY-1",
|
|
"bill_id": "BILL-1",
|
|
"status": "SUCCESS",
|
|
"from_card": "676754******1234",
|
|
"payment_method": "SBP",
|
|
}
|
|
]
|
|
}
|
|
|
|
service = _make_service(stub)
|
|
db = DummySession()
|
|
|
|
payment = SimpleNamespace(
|
|
id=99,
|
|
bill_id="BILL-1",
|
|
payment_id=None,
|
|
payment_status="NEW",
|
|
payment_method=None,
|
|
balance_amount=None,
|
|
balance_currency=None,
|
|
payer_account=None,
|
|
status="NEW",
|
|
is_paid=False,
|
|
paid_at=None,
|
|
transaction_id=None,
|
|
user_id=1,
|
|
)
|
|
|
|
async def fake_get_by_id(db: DummySession, payment_id: int) -> SimpleNamespace:
|
|
assert payment_id == payment.id
|
|
return payment
|
|
|
|
async def fake_update_status(
|
|
db: DummySession,
|
|
payment_obj: SimpleNamespace,
|
|
*,
|
|
status: str,
|
|
**kwargs: Any,
|
|
) -> SimpleNamespace:
|
|
payment_obj.status = status
|
|
payment_obj.last_status = status
|
|
for key, value in kwargs.items():
|
|
setattr(payment_obj, key, value)
|
|
if "is_paid" in kwargs:
|
|
payment_obj.is_paid = kwargs["is_paid"]
|
|
await db.commit()
|
|
return payment_obj
|
|
|
|
async def fake_finalize(
|
|
self: PaymentService,
|
|
db: DummySession,
|
|
payment_obj: Any,
|
|
*,
|
|
payment_id: Optional[str] = None,
|
|
trigger: str,
|
|
) -> bool:
|
|
return False
|
|
|
|
monkeypatch.setattr(
|
|
payment_service_module,
|
|
"get_pal24_payment_by_id",
|
|
fake_get_by_id,
|
|
raising=False,
|
|
)
|
|
monkeypatch.setattr(
|
|
payment_service_module,
|
|
"update_pal24_payment_status",
|
|
fake_update_status,
|
|
raising=False,
|
|
)
|
|
monkeypatch.setattr(
|
|
PaymentService,
|
|
"_finalize_pal24_payment",
|
|
fake_finalize,
|
|
raising=False,
|
|
)
|
|
|
|
result = await service.get_pal24_payment_status(db, local_payment_id=payment.id)
|
|
|
|
assert result is not None
|
|
assert payment.status == "SUCCESS"
|
|
assert payment.payment_id == "PAY-1"
|
|
assert payment.payment_status == "SUCCESS"
|
|
assert payment.payment_method == "sbp"
|
|
assert payment.is_paid is True
|
|
assert stub.status_calls == ["BILL-1"]
|
|
assert stub.payment_status_calls in ([], ["PAY-1"])
|
|
assert result["remote_status"] == "SUCCESS"
|
|
assert result["remote_data"] and "bill_status" in result["remote_data"]
|