Добавлен заказ накладной

This commit is contained in:
Раис Юсупалиев
2026-05-23 17:29:56 +03:00
parent 39cd5ddc7a
commit c494d50566
21 changed files with 1384 additions and 1126 deletions
+25 -135
View File
@@ -16,6 +16,7 @@ from app.services.aggregator import (
InitPaymentUnavailableError,
InvalidInitPaymentRequestError,
)
from tests.payment_fixtures import make_init_payment_payload, make_init_payment_request
class StubPaymentAdapter:
@@ -94,49 +95,6 @@ class StubOrderRepository:
return object()
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 _make_cdek_price(**overrides: object) -> DeliveryPrice:
payload = {
"provider": "cdek",
@@ -151,7 +109,7 @@ def _make_cdek_price(**overrides: object) -> DeliveryPrice:
def test_init_payment_validates_cdek_price_before_tbank_and_returns_payment_url() -> None:
request = _make_init_payment_request()
request = make_init_payment_request()
events: list[str] = []
validation_adapter = StubPaymentPriceValidationAdapter(
response=_make_cdek_price(),
@@ -172,8 +130,9 @@ def test_init_payment_validates_cdek_price_before_tbank_and_returns_payment_url(
assert events == ["cdek", "tbank"]
def test_init_payment_persists_order_after_successful_payment_link() -> None:
request = _make_init_payment_request()
def test_init_payment_persists_order_payload_after_successful_payment_link() -> None:
payload = make_init_payment_payload()
request = InitPaymentRequest.model_validate(payload)
adapter = StubPaymentAdapter(response="https://pay.test/payment/1")
validation_adapter = StubPaymentPriceValidationAdapter(response=_make_cdek_price())
order_repository = StubOrderRepository()
@@ -184,59 +143,21 @@ def test_init_payment_persists_order_after_successful_payment_link() -> None:
order_repository=order_repository,
)
result = asyncio.run(service.init_payment(request))
asyncio.run(service.init_payment(request))
assert result == InitPaymentResponse(payment_url="https://pay.test/payment/1")
assert validation_adapter.calls == [request]
assert adapter.calls == [("order-uuid-1", 125000)]
assert order_repository.calls == [
(
order_repository.session_value,
OrderData(
order_uuid="order-uuid-1",
payment_url="https://pay.test/payment/1",
price=125000,
delivery_type=2,
tariff_code=535,
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",
},
packages=[
{
"number": "1",
"weight": 1,
"length": 20,
"width": 15,
"height": 10,
"comment": "Package 1",
}
],
services=[{"code": "INSURANCE", "parameter": "1000"}],
comment="Test payment",
),
)
]
assert len(order_repository.calls) == 1
session, order_data = order_repository.calls[0]
assert session is order_repository.session_value
assert order_data.order_uuid == "order-uuid-1"
assert order_data.payment_url == "https://pay.test/payment/1"
assert order_data.price == 125000
assert order_data.tariff_code == 535
assert order_data.account_email == "client@example.com"
assert order_data.payload == request.model_dump(mode="json", by_alias=True)
def test_init_payment_returns_payment_url_when_order_persistence_fails() -> None:
request = _make_init_payment_request()
request = make_init_payment_request()
adapter = StubPaymentAdapter(response="https://pay.test/payment/1")
validation_adapter = StubPaymentPriceValidationAdapter(response=_make_cdek_price())
order_repository = StubOrderRepository(error=RuntimeError("database down"))
@@ -250,13 +171,12 @@ def test_init_payment_returns_payment_url_when_order_persistence_fails() -> None
result = asyncio.run(service.init_payment(request))
assert result == InitPaymentResponse(payment_url="https://pay.test/payment/1")
assert validation_adapter.calls == [request]
assert adapter.calls == [("order-uuid-1", 125000)]
assert len(order_repository.calls) == 1
def test_init_payment_maps_provider_request_errors_to_invalid_payment_error() -> None:
request = _make_init_payment_request()
request = make_init_payment_request()
adapter = StubPaymentAdapter(error=TBankPaymentRequestError("bad payload"))
validation_adapter = StubPaymentPriceValidationAdapter(response=_make_cdek_price())
service = AggregatorService(
@@ -268,30 +188,9 @@ def test_init_payment_maps_provider_request_errors_to_invalid_payment_error() ->
with pytest.raises(InvalidInitPaymentRequestError):
asyncio.run(service.init_payment(request))
assert validation_adapter.calls == [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")
validation_adapter = StubPaymentPriceValidationAdapter(response=_make_cdek_price())
service = AggregatorService(
providers=[],
payment_adapter=adapter,
payment_price_validation_adapter=validation_adapter,
)
result = asyncio.run(service.init_payment(request))
assert result == InitPaymentResponse(payment_url="https://pay.test/payment/2")
assert validation_adapter.calls == [request]
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()
request = make_init_payment_request()
adapter = StubPaymentAdapter(error=TBankPaymentAdapterError("transport down"))
validation_adapter = StubPaymentPriceValidationAdapter(response=_make_cdek_price())
service = AggregatorService(
@@ -303,19 +202,18 @@ def test_init_payment_maps_client_failures_to_unavailable_error() -> None:
with pytest.raises(InitPaymentUnavailableError):
asyncio.run(service.init_payment(request))
assert validation_adapter.calls == [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()))
asyncio.run(service.init_payment(make_init_payment_request()))
def test_init_payment_rejects_cdek_price_mismatch_without_tbank_or_repository_calls() -> None:
request = _make_init_payment_request(price=124999)
payload = make_init_payment_payload()
payload["systemData"]["tariff"]["price"] = 124999
request = InitPaymentRequest.model_validate(payload)
adapter = StubPaymentAdapter(response="https://pay.test/payment/1")
validation_adapter = StubPaymentPriceValidationAdapter(response=_make_cdek_price())
order_repository = StubOrderRepository()
@@ -329,13 +227,12 @@ def test_init_payment_rejects_cdek_price_mismatch_without_tbank_or_repository_ca
with pytest.raises(InvalidInitPaymentRequestError):
asyncio.run(service.init_payment(request))
assert validation_adapter.calls == [request]
assert adapter.calls == []
assert order_repository.calls == []
def test_init_payment_rejects_when_cdek_does_not_return_requested_tariff() -> None:
request = _make_init_payment_request()
request = make_init_payment_request()
adapter = StubPaymentAdapter(response="https://pay.test/payment/1")
validation_adapter = StubPaymentPriceValidationAdapter(response=None)
service = AggregatorService(
@@ -347,12 +244,11 @@ def test_init_payment_rejects_when_cdek_does_not_return_requested_tariff() -> No
with pytest.raises(InvalidInitPaymentRequestError):
asyncio.run(service.init_payment(request))
assert validation_adapter.calls == [request]
assert adapter.calls == []
def test_init_payment_maps_cdek_request_error_to_invalid_payment_error() -> None:
request = _make_init_payment_request()
request = make_init_payment_request()
adapter = StubPaymentAdapter(response="https://pay.test/payment/1")
validation_adapter = StubPaymentPriceValidationAdapter(
error=ProviderRequestError("bad CDEK payload")
@@ -366,12 +262,9 @@ def test_init_payment_maps_cdek_request_error_to_invalid_payment_error() -> None
with pytest.raises(InvalidInitPaymentRequestError):
asyncio.run(service.init_payment(request))
assert validation_adapter.calls == [request]
assert adapter.calls == []
def test_init_payment_maps_cdek_client_error_to_unavailable_error() -> None:
request = _make_init_payment_request()
request = make_init_payment_request()
adapter = StubPaymentAdapter(response="https://pay.test/payment/1")
validation_adapter = StubPaymentPriceValidationAdapter(
error=ProviderClientError("CDEK unavailable")
@@ -384,6 +277,3 @@ def test_init_payment_maps_cdek_client_error_to_unavailable_error() -> None:
with pytest.raises(InitPaymentUnavailableError):
asyncio.run(service.init_payment(request))
assert validation_adapter.calls == [request]
assert adapter.calls == []