192 lines
5.7 KiB
Python
192 lines
5.7 KiB
Python
import asyncio
|
|
from dataclasses import dataclass, field
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
|
|
from app.schemas.payment import InitPaymentRequest, TBankPaymentNotification
|
|
from app.schemas.response import DeliveryPrice
|
|
from app.services.aggregator import AggregatorService
|
|
from tests.payment_fixtures import make_init_payment_payload, make_init_payment_request
|
|
|
|
|
|
class StubPaymentAdapter:
|
|
async def create_payment_link(self, order_uuid: str, amount_kopecks: int) -> str:
|
|
return "https://pay.test/payment/1"
|
|
|
|
def verify_payment_notification(self, notification: TBankPaymentNotification) -> None:
|
|
return None
|
|
|
|
|
|
class StubValidationAdapter:
|
|
def __init__(self, response: DeliveryPrice | None) -> None:
|
|
self._response = response
|
|
self.calls: list[InitPaymentRequest] = []
|
|
|
|
async def get_payment_price(
|
|
self, request: InitPaymentRequest
|
|
) -> DeliveryPrice | None:
|
|
self.calls.append(request)
|
|
return self._response
|
|
|
|
|
|
@dataclass
|
|
class CSEResult:
|
|
order_number: str = "CSE-000123"
|
|
|
|
|
|
class StubCSERegistrationAdapter:
|
|
def __init__(self) -> None:
|
|
self.calls: list[tuple[InitPaymentRequest, str]] = []
|
|
|
|
async def register_order(
|
|
self, request: InitPaymentRequest, order_uuid: str
|
|
) -> CSEResult:
|
|
self.calls.append((request, order_uuid))
|
|
return CSEResult()
|
|
|
|
|
|
class StubCDEKRegistrationAdapter:
|
|
def __init__(self) -> None:
|
|
self.calls: list[tuple[InitPaymentRequest, str]] = []
|
|
|
|
async def register_order(
|
|
self, request: InitPaymentRequest, order_uuid: str
|
|
) -> object:
|
|
self.calls.append((request, order_uuid))
|
|
raise AssertionError("CDEK adapter must not be called for a CSE order.")
|
|
|
|
|
|
def _cse_price() -> DeliveryPrice:
|
|
return DeliveryPrice.model_construct(
|
|
provider="cse",
|
|
service_name="CSE tariff",
|
|
price=Decimal("1250.00"),
|
|
currency="RUB",
|
|
delivery_days_min=1,
|
|
delivery_days_max=2,
|
|
tariff_code="cse-guid",
|
|
)
|
|
|
|
|
|
def _cse_request() -> InitPaymentRequest:
|
|
return make_init_payment_request(
|
|
systemData={
|
|
"tariff": {
|
|
"provider": "cse",
|
|
"serviceName": "CSE tariff",
|
|
"price": 125000,
|
|
"deliveryDaysMin": 1,
|
|
"deliveryDaysMax": 2,
|
|
"tariffCode": "cse-guid",
|
|
},
|
|
"parcelType": "parcel",
|
|
"weight": "1.0",
|
|
"dimensions": {"length": "10", "width": "10", "height": "10"},
|
|
}
|
|
)
|
|
|
|
|
|
def test_init_payment_routes_validation_to_tariff_provider() -> None:
|
|
cse_validation = StubValidationAdapter(_cse_price())
|
|
cdek_validation = StubValidationAdapter(None)
|
|
service = AggregatorService(
|
|
providers=[],
|
|
payment_adapter=StubPaymentAdapter(),
|
|
payment_price_validation_adapters={
|
|
"cdek": cdek_validation,
|
|
"cse": cse_validation,
|
|
},
|
|
provider_price_multiplier=Decimal("1"),
|
|
order_uuid_factory=lambda: "order-uuid-1",
|
|
)
|
|
|
|
asyncio.run(service.init_payment(_cse_request()))
|
|
|
|
assert len(cse_validation.calls) == 1
|
|
assert cdek_validation.calls == []
|
|
|
|
|
|
@dataclass
|
|
class StoredOrder:
|
|
order_uuid: str = "order-uuid-1"
|
|
provider: str = "cse"
|
|
payload: dict[str, Any] = field(
|
|
default_factory=lambda: _cse_request().model_dump(mode="json", by_alias=True)
|
|
)
|
|
payment_status: str | None = None
|
|
tbank_payment_id: int | None = None
|
|
cdek_order_uuid: str | None = None
|
|
cse_order_number: str | None = None
|
|
payment_email_sent_at: object | None = None
|
|
account_email: str | None = None
|
|
|
|
|
|
class StubSession:
|
|
async def __aenter__(self) -> object:
|
|
return object()
|
|
|
|
async def __aexit__(self, *args: object) -> None:
|
|
return None
|
|
|
|
|
|
class StubOrderRepository:
|
|
def __init__(self, order: StoredOrder) -> None:
|
|
self._order = order
|
|
|
|
def session(self) -> StubSession:
|
|
return StubSession()
|
|
|
|
async def get_order_by_order_uuid(self, session: object, order_uuid: str) -> StoredOrder:
|
|
return self._order
|
|
|
|
async def mark_payment_status(
|
|
self, session: object, order_uuid: str, status: str, payment_id: int
|
|
) -> StoredOrder:
|
|
self._order.payment_status = status
|
|
self._order.tbank_payment_id = payment_id
|
|
return self._order
|
|
|
|
async def mark_cse_order_registered(
|
|
self, session: object, order_uuid: str, cse_order_number: str
|
|
) -> StoredOrder:
|
|
self._order.cse_order_number = cse_order_number
|
|
return self._order
|
|
|
|
async def mark_cdek_order_registered(
|
|
self, session: object, order_uuid: str, cdek_order_uuid: str
|
|
) -> StoredOrder:
|
|
raise AssertionError("CDEK persistence must not be used for a CSE order.")
|
|
|
|
|
|
def _notification() -> TBankPaymentNotification:
|
|
return TBankPaymentNotification(
|
|
TerminalKey="TestTerminal",
|
|
OrderId="order-uuid-1",
|
|
Success=True,
|
|
Status="CONFIRMED",
|
|
PaymentId=8347568144,
|
|
ErrorCode="0",
|
|
Amount=125000,
|
|
Token="signed-token",
|
|
)
|
|
|
|
|
|
def test_notification_routes_registration_to_order_provider() -> None:
|
|
order = StoredOrder()
|
|
cse_registration = StubCSERegistrationAdapter()
|
|
service = AggregatorService(
|
|
providers=[],
|
|
payment_adapter=StubPaymentAdapter(),
|
|
order_repository=StubOrderRepository(order),
|
|
order_registration_adapters={
|
|
"cdek": StubCDEKRegistrationAdapter(),
|
|
"cse": cse_registration,
|
|
},
|
|
)
|
|
|
|
result = asyncio.run(service.handle_tbank_payment_notification(_notification()))
|
|
|
|
assert result == "OK"
|
|
assert len(cse_registration.calls) == 1
|
|
assert order.cse_order_number == "CSE-000123"
|