215 lines
6.8 KiB
Python
215 lines
6.8 KiB
Python
import asyncio
|
|
|
|
import httpx
|
|
|
|
from app.controllers.v1.delivery import get_aggregator_service
|
|
from app.main import create_app
|
|
from app.schemas.order import OrderCreateRequest, OrderCreateResponse
|
|
from app.services.aggregator import (
|
|
AggregatorServiceError,
|
|
InvalidOrderCreateRequestError,
|
|
)
|
|
|
|
|
|
class StubAggregatorService:
|
|
def __init__(self, *, response: object, error: Exception | None = None) -> None:
|
|
self._response = response
|
|
self._error = error
|
|
self.calls: list[OrderCreateRequest] = []
|
|
|
|
async def create_order(self, request: OrderCreateRequest) -> 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 {
|
|
"type": 2,
|
|
"tariff_code": 535,
|
|
"comment": "Test order",
|
|
"sender": {
|
|
"name": "Petr Petrov",
|
|
"email": "sender@example.com",
|
|
"phones": [{"number": "+79009876543"}],
|
|
},
|
|
"recipient": {
|
|
"name": "Ivan Ivanov",
|
|
"email": "ivan@example.com",
|
|
"phones": [{"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": 1000,
|
|
"length": 20,
|
|
"width": 15,
|
|
"height": 10,
|
|
"comment": "Package 1",
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
def test_post_delivery_order_returns_response_and_delegates_to_service() -> None:
|
|
expected_response = OrderCreateResponse(provider="cdek", order_uuid="order-uuid-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/order", 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 == [OrderCreateRequest(**_valid_payload())]
|
|
|
|
|
|
def test_post_delivery_order_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/order", json=invalid_payload)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 422
|
|
assert service.calls == []
|
|
|
|
|
|
def test_post_delivery_order_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/order", json=invalid_payload)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 422
|
|
assert service.calls == []
|
|
|
|
|
|
def test_post_delivery_order_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/order", json=invalid_payload)
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 422
|
|
assert service.calls == []
|
|
|
|
|
|
def test_post_delivery_order_maps_invalid_request_to_400() -> None:
|
|
service = StubAggregatorService(
|
|
response=None,
|
|
error=InvalidOrderCreateRequestError("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/order", json=_valid_payload())
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 400
|
|
assert response.json() == {
|
|
"detail": {
|
|
"code": "invalid_order_create_request",
|
|
"message": "Order request contains invalid or unsupported CDEK data.",
|
|
}
|
|
}
|
|
|
|
|
|
def test_post_delivery_order_maps_service_exception_to_503() -> None:
|
|
service = StubAggregatorService(
|
|
response=None,
|
|
error=AggregatorServiceError("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/order", json=_valid_payload())
|
|
|
|
response = asyncio.run(run_request())
|
|
|
|
assert response.status_code == 503
|
|
assert response.json() == {
|
|
"detail": {
|
|
"code": "order_creation_unavailable",
|
|
"message": "CDEK order creation is temporarily unavailable.",
|
|
}
|
|
}
|