Files
g2s-aggregator/tests/services/test_provider_routing.py
T
Раис Юсупалиев aeee641c6c
Deploy / deploy (push) Successful in 48s
поправлена гонка статусов от tabnk
2026-06-20 01:12:16 +03:00

194 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, *, for_update: bool = False
) -> 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"