403 lines
12 KiB
Python
403 lines
12 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,
|
|
)
|
|
|
|
|
|
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 _valid_payload() -> dict[str, object]:
|
|
return {
|
|
"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",
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=_valid_payload(),
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == expected_response.model_dump(mode="json")
|
|
assert service.calls == [InitPaymentRequest(**_valid_payload())]
|
|
|
|
|
|
def test_post_init_payment_rejects_invalid_payload() -> None:
|
|
service = StubAggregatorService(response=None)
|
|
app = create_app()
|
|
_install_service_override(app, service)
|
|
invalid_payload = _valid_payload()
|
|
invalid_payload["tariff_code"] = 136
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=invalid_payload,
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
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 = _valid_payload()
|
|
del invalid_payload["price"]
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=invalid_payload,
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
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 = _valid_payload()
|
|
invalid_payload["price"] = 0
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=invalid_payload,
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
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 = _valid_payload()
|
|
invalid_payload["price"] = 125000.5
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=invalid_payload,
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 422
|
|
assert service.calls == []
|
|
|
|
|
|
def test_post_init_payment_rejects_sender_company_field() -> None:
|
|
service = StubAggregatorService(response=None)
|
|
app = create_app()
|
|
_install_service_override(app, service)
|
|
invalid_payload = _valid_payload()
|
|
invalid_payload["sender"] = {
|
|
**invalid_payload["sender"], # type: ignore[arg-type]
|
|
"company": "Romashka LLC",
|
|
}
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=invalid_payload,
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 422
|
|
assert service.calls == []
|
|
|
|
|
|
def test_post_init_payment_rejects_sender_phones_field() -> None:
|
|
service = StubAggregatorService(response=None)
|
|
app = create_app()
|
|
_install_service_override(app, service)
|
|
invalid_payload = _valid_payload()
|
|
invalid_payload["sender"] = {
|
|
**invalid_payload["sender"], # type: ignore[arg-type]
|
|
"phones": [{"number": "+79009876543"}],
|
|
}
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=invalid_payload,
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 422
|
|
assert service.calls == []
|
|
|
|
|
|
def test_post_init_payment_rejects_recipient_company_field() -> None:
|
|
service = StubAggregatorService(response=None)
|
|
app = create_app()
|
|
_install_service_override(app, service)
|
|
invalid_payload = _valid_payload()
|
|
invalid_payload["recipient"] = {
|
|
**invalid_payload["recipient"], # type: ignore[arg-type]
|
|
"company": "Romashka LLC",
|
|
}
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=invalid_payload,
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 422
|
|
assert service.calls == []
|
|
|
|
|
|
def test_post_init_payment_rejects_recipient_phones_field() -> None:
|
|
service = StubAggregatorService(response=None)
|
|
app = create_app()
|
|
_install_service_override(app, service)
|
|
invalid_payload = _valid_payload()
|
|
invalid_payload["recipient"] = {
|
|
**invalid_payload["recipient"], # type: ignore[arg-type]
|
|
"phones": [{"number": "+79001234567"}],
|
|
}
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post(
|
|
"/api/v1/delivery/init-payment",
|
|
json=invalid_payload,
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 422
|
|
assert service.calls == []
|
|
|
|
|
|
def test_post_init_payment_accepts_request_without_services() -> 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 = _valid_payload()
|
|
del payload["services"]
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post("/api/v1/delivery/init-payment", json=payload)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == expected_response.model_dump(mode="json")
|
|
assert service.calls == [InitPaymentRequest(**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)
|
|
|
|
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/init-payment",
|
|
json=_valid_payload(),
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
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)
|
|
|
|
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/init-payment",
|
|
json=_valid_payload(),
|
|
)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 503
|
|
assert response.json() == {
|
|
"detail": {
|
|
"code": "init_payment_unavailable",
|
|
"message": "Payment initialization is temporarily unavailable.",
|
|
}
|
|
}
|
|
|
|
|
|
def test_delivery_order_endpoint_is_removed() -> None:
|
|
service = StubAggregatorService(response=None)
|
|
app = create_app()
|
|
_install_service_override(app, service)
|
|
|
|
async def run_request() -> httpx.Response:
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(
|
|
transport=transport,
|
|
base_url="http://testserver",
|
|
) as client:
|
|
return await client.post("/api/v1/delivery/order", json=_valid_payload())
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 404
|
|
assert service.calls == []
|