Files
g2s-aggregator/tests/controllers/v1/test_init_payment.py
T
2026-05-23 19:27:32 +03:00

279 lines
8.9 KiB
Python

import asyncio
import httpx
from app.controllers.v1.delivery import get_aggregator_service
from app.main import create_app
from app.schemas.payment import InitPaymentRequest, InitPaymentResponse
from app.services.aggregator import (
InitPaymentUnavailableError,
InvalidInitPaymentRequestError,
)
from tests.payment_fixtures import make_init_payment_payload
class StubAggregatorService:
def __init__(self, *, response: object, error: Exception | None = None) -> None:
self._response = response
self._error = error
self.calls: list[InitPaymentRequest] = []
async def init_payment(self, request: InitPaymentRequest) -> object:
self.calls.append(request)
if self._error is not None:
raise self._error
return self._response
def _install_service_override(app, service: StubAggregatorService) -> None:
async def override_service() -> StubAggregatorService:
return service
app.dependency_overrides[get_aggregator_service] = override_service
def _post(app, payload: dict[str, object]) -> httpx.Response:
async def run_request() -> httpx.Response:
transport = httpx.ASGITransport(app=app, raise_app_exceptions=False)
async with httpx.AsyncClient(
transport=transport,
base_url="http://testserver",
) as client:
return await client.post("/api/v1/delivery/order", json=payload)
return asyncio.run(run_request())
def test_post_init_payment_returns_response_and_delegates_to_service() -> None:
expected_response = InitPaymentResponse(payment_url="https://pay.test/payment/1")
service = StubAggregatorService(response=expected_response)
app = create_app()
_install_service_override(app, service)
payload = make_init_payment_payload()
response = _post(app, payload)
assert response.status_code == 200
assert response.json() == expected_response.model_dump(mode="json")
assert service.calls == [InitPaymentRequest.model_validate(payload)]
def test_post_init_payment_rejects_invalid_tariff_code() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
invalid_payload["systemData"]["tariff"]["tariffCode"] = "not-a-number"
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_rejects_snake_case_top_level_field() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
invalid_payload["order_uuid"] = invalid_payload.pop("orderUuid")
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_rejects_snake_case_nested_address_field() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
sender_address = invalid_payload["senderAddress"]
sender_address["city_id"] = sender_address.pop("cityId")
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_rejects_snake_case_nested_tariff_field() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
tariff = invalid_payload["systemData"]["tariff"]
tariff["tariff_code"] = tariff.pop("tariffCode")
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_rejects_missing_price() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
del invalid_payload["systemData"]["tariff"]["price"]
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_rejects_non_positive_price() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
invalid_payload["systemData"]["tariff"]["price"] = 0
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_rejects_non_integer_price() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
invalid_payload["systemData"]["tariff"]["price"] = 125000.5
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_requires_company_fields_when_is_company_true() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
invalid_payload["senderContact"]["isCompany"] = True
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_accepts_company_with_full_requisites() -> None:
expected_response = InitPaymentResponse(payment_url="https://pay.test/payment/1")
service = StubAggregatorService(response=expected_response)
app = create_app()
_install_service_override(app, service)
payload = make_init_payment_payload()
payload["senderContact"].update(
{
"isCompany": True,
"companyName": "Romashka LLC",
"inn": "7707083893",
"kpp": "770701001",
}
)
response = _post(app, payload)
assert response.status_code == 200
assert service.calls == [InitPaymentRequest.model_validate(payload)]
def test_post_init_payment_rejects_non_numeric_weight() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
invalid_payload["systemData"]["weight"] = "string"
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_rejects_non_numeric_dimension() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
invalid_payload["systemData"]["dimensions"]["length"] = "string"
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_rejects_parcel_without_dimensions() -> None:
service = StubAggregatorService(response=None)
app = create_app()
_install_service_override(app, service)
invalid_payload = make_init_payment_payload()
invalid_payload["systemData"]["dimensions"] = None
response = _post(app, invalid_payload)
assert response.status_code == 422
assert service.calls == []
def test_post_init_payment_accepts_doc_without_dimensions() -> None:
expected_response = InitPaymentResponse(payment_url="https://pay.test/payment/1")
service = StubAggregatorService(response=expected_response)
app = create_app()
_install_service_override(app, service)
payload = make_init_payment_payload()
payload["systemData"]["parcelType"] = "doc"
payload["systemData"]["docPackaging"] = "envelope"
payload["systemData"]["dimensions"] = None
response = _post(app, payload)
assert response.status_code == 200
assert service.calls == [InitPaymentRequest.model_validate(payload)]
def test_post_init_payment_maps_invalid_request_to_400() -> None:
service = StubAggregatorService(
response=None,
error=InvalidInitPaymentRequestError("invalid payload"),
)
app = create_app()
_install_service_override(app, service)
response = _post(app, make_init_payment_payload())
assert response.status_code == 400
assert response.json() == {
"detail": {
"code": "invalid_init_payment_request",
"message": "Payment request contains invalid or unsupported TBank data.",
}
}
def test_post_init_payment_maps_service_exception_to_503() -> None:
service = StubAggregatorService(
response=None,
error=InitPaymentUnavailableError("service unavailable"),
)
app = create_app()
_install_service_override(app, service)
response = _post(app, make_init_payment_payload())
assert response.status_code == 503
assert response.json() == {
"detail": {
"code": "init_payment_unavailable",
"message": "Payment initialization is temporarily unavailable.",
}
}