import asyncio import pytest from app.adapters.tbank.base import ( TBankPaymentAdapterError, TBankPaymentRequestError, ) from app.schemas.payment import InitPaymentRequest, InitPaymentResponse from app.services.aggregator import ( AggregatorService, InitPaymentUnavailableError, InvalidInitPaymentRequestError, ) class StubPaymentAdapter: def __init__( self, *, response: str | None = None, error: Exception | None = None, ) -> None: self._response = response self._error = error self.calls: list[tuple[str, int]] = [] async def create_payment_link(self, order_uuid: str, amount_kopecks: int) -> str: self.calls.append((order_uuid, amount_kopecks)) if self._error is not None: raise self._error if self._response is None: raise RuntimeError("Stub payment adapter has no response configured.") return self._response def _make_init_payment_request(**overrides: object) -> InitPaymentRequest: payload: dict[str, object] = { "order_uuid": "order-uuid-1", "price": 125000, "type": 2, "tariff_code": 535, "comment": "Test payment", "sender": { "name": "Petr Petrov", "email": "sender@example.com", "phone": {"number": "+79009876543"}, }, "recipient": { "name": "Ivan Ivanov", "email": "ivan@example.com", "phone": {"number": "+79001234567"}, }, "from_location": { "address": "Lenina 1", "city": "Moscow", "country_code": "RU", }, "to_location": { "address": "Pushkina 10", "city": "Novosibirsk", "country_code": "RU", }, "services": [{"code": "INSURANCE", "parameter": "1000"}], "packages": [ { "number": "1", "weight": 1, "length": 20, "width": 15, "height": 10, "comment": "Package 1", } ], } payload.update(overrides) return InitPaymentRequest(**payload) def test_init_payment_delegates_to_adapter_and_returns_payment_url() -> None: request = _make_init_payment_request() adapter = StubPaymentAdapter(response="https://pay.test/payment/1") service = AggregatorService(providers=[], payment_adapter=adapter) result = asyncio.run(service.init_payment(request)) assert result == InitPaymentResponse(payment_url="https://pay.test/payment/1") assert adapter.calls == [("order-uuid-1", 125000)] def test_init_payment_maps_provider_request_errors_to_invalid_payment_error() -> None: request = _make_init_payment_request() adapter = StubPaymentAdapter(error=TBankPaymentRequestError("bad payload")) service = AggregatorService(providers=[], payment_adapter=adapter) with pytest.raises(InvalidInitPaymentRequestError): asyncio.run(service.init_payment(request)) assert adapter.calls == [("order-uuid-1", 125000)] def test_init_payment_delegates_request_with_services_none_without_new_logic() -> None: request = _make_init_payment_request(services=None) adapter = StubPaymentAdapter(response="https://pay.test/payment/2") service = AggregatorService(providers=[], payment_adapter=adapter) result = asyncio.run(service.init_payment(request)) assert result == InitPaymentResponse(payment_url="https://pay.test/payment/2") assert adapter.calls == [("order-uuid-1", 125000)] assert request.services is None def test_init_payment_maps_client_failures_to_unavailable_error() -> None: request = _make_init_payment_request() adapter = StubPaymentAdapter(error=TBankPaymentAdapterError("transport down")) service = AggregatorService(providers=[], payment_adapter=adapter) with pytest.raises(InitPaymentUnavailableError): asyncio.run(service.init_payment(request)) assert adapter.calls == [("order-uuid-1", 125000)] def test_init_payment_without_configured_adapter_raises_unavailable_error() -> None: service = AggregatorService(providers=[]) with pytest.raises(InitPaymentUnavailableError): asyncio.run(service.init_payment(_make_init_payment_request()))