Добавлен поллер накладной
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from app.adapters.delivery_providers.cdek.client import (
|
||||
CDEKClient,
|
||||
CDEKClientError,
|
||||
CDEKRequestError,
|
||||
)
|
||||
from app.adapters.delivery_providers.cdek.order_mapper import (
|
||||
CDEKOrderInfo,
|
||||
CDEKWaybillInfo,
|
||||
)
|
||||
|
||||
|
||||
class StubAuthClient:
|
||||
async def get_access_token(self) -> str:
|
||||
return "test-token"
|
||||
|
||||
|
||||
class SequenceHTTPClient:
|
||||
def __init__(self, results: list[Any]) -> None:
|
||||
self._results = results
|
||||
self.calls: list[dict[str, Any]] = []
|
||||
|
||||
def _next_result(self) -> Any:
|
||||
return self._results[len(self.calls) - 1]
|
||||
|
||||
async def get(
|
||||
self,
|
||||
url: str,
|
||||
*,
|
||||
headers: dict[str, str] | None = None,
|
||||
timeout: float | None = None,
|
||||
) -> httpx.Response:
|
||||
self.calls.append(
|
||||
{"method": "GET", "url": url, "headers": headers, "timeout": timeout}
|
||||
)
|
||||
result = self._next_result()
|
||||
if isinstance(result, Exception):
|
||||
raise result
|
||||
return result
|
||||
|
||||
|
||||
def _make_client(http_client: SequenceHTTPClient, **kwargs: Any) -> CDEKClient:
|
||||
kwargs.setdefault("retry_attempts", 0)
|
||||
return CDEKClient(
|
||||
http_client=http_client, # type: ignore[arg-type]
|
||||
auth_client=StubAuthClient(), # type: ignore[arg-type]
|
||||
base_url="https://api.cdek.test/v2",
|
||||
timeout_seconds=7.5,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def test_get_order_parses_status_and_waybill_uuid() -> None:
|
||||
response = httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"entity": {
|
||||
"uuid": "cdek-order-uuid",
|
||||
"statuses": [
|
||||
{"code": "ACCEPTED", "date_time": "2026-05-24T10:00:00+0000"}
|
||||
],
|
||||
},
|
||||
"related_entities": [
|
||||
{"type": "waybill", "uuid": "waybill-uuid-1"}
|
||||
],
|
||||
},
|
||||
request=httpx.Request("GET", "https://api.cdek.test/v2/orders/cdek-order-uuid"),
|
||||
)
|
||||
http_client = SequenceHTTPClient([response])
|
||||
client = _make_client(http_client)
|
||||
|
||||
result = asyncio.run(client.get_order("cdek-order-uuid"))
|
||||
|
||||
assert result == CDEKOrderInfo(
|
||||
order_uuid="cdek-order-uuid",
|
||||
status_code="ACCEPTED",
|
||||
waybill_uuid="waybill-uuid-1",
|
||||
)
|
||||
assert http_client.calls[0]["url"] == (
|
||||
"https://api.cdek.test/v2/orders/cdek-order-uuid"
|
||||
)
|
||||
assert http_client.calls[0]["headers"] == {"Authorization": "Bearer test-token"}
|
||||
|
||||
|
||||
def test_get_order_retries_on_5xx_and_succeeds() -> None:
|
||||
flaky = httpx.Response(
|
||||
503,
|
||||
json={"message": "temporary"},
|
||||
request=httpx.Request("GET", "https://api.cdek.test/v2/orders/x"),
|
||||
)
|
||||
success = httpx.Response(
|
||||
200,
|
||||
json={"entity": {"uuid": "x", "statuses": []}},
|
||||
request=httpx.Request("GET", "https://api.cdek.test/v2/orders/x"),
|
||||
)
|
||||
http_client = SequenceHTTPClient([flaky, success])
|
||||
sleep_calls: list[float] = []
|
||||
|
||||
async def fake_sleep(seconds: float) -> None:
|
||||
sleep_calls.append(seconds)
|
||||
|
||||
client = _make_client(
|
||||
http_client,
|
||||
retry_attempts=1,
|
||||
retry_backoff_seconds=0.1,
|
||||
sleep=fake_sleep,
|
||||
)
|
||||
|
||||
result = asyncio.run(client.get_order("x"))
|
||||
|
||||
assert result.order_uuid == "x"
|
||||
assert len(http_client.calls) == 2
|
||||
assert sleep_calls == [0.1]
|
||||
|
||||
|
||||
def test_get_order_raises_request_error_on_4xx() -> None:
|
||||
response = httpx.Response(
|
||||
404,
|
||||
json={"requests": [{"errors": [{"code": "v2_not_found"}]}]},
|
||||
request=httpx.Request("GET", "https://api.cdek.test/v2/orders/missing"),
|
||||
)
|
||||
http_client = SequenceHTTPClient([response])
|
||||
client = _make_client(http_client)
|
||||
|
||||
with pytest.raises(CDEKRequestError, match="status 404"):
|
||||
asyncio.run(client.get_order("missing"))
|
||||
|
||||
|
||||
def test_get_waybill_returns_url_when_present() -> None:
|
||||
response = httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"entity": {
|
||||
"uuid": "waybill-uuid-1",
|
||||
"url": "https://cdek.test/waybill/1.pdf",
|
||||
}
|
||||
},
|
||||
request=httpx.Request(
|
||||
"GET", "https://api.cdek.test/v2/print/orders/waybill-uuid-1"
|
||||
),
|
||||
)
|
||||
http_client = SequenceHTTPClient([response])
|
||||
client = _make_client(http_client)
|
||||
|
||||
result = asyncio.run(client.get_waybill("waybill-uuid-1"))
|
||||
|
||||
assert result == CDEKWaybillInfo(
|
||||
waybill_uuid="waybill-uuid-1",
|
||||
url="https://cdek.test/waybill/1.pdf",
|
||||
)
|
||||
assert http_client.calls[0]["url"] == (
|
||||
"https://api.cdek.test/v2/print/orders/waybill-uuid-1"
|
||||
)
|
||||
|
||||
|
||||
def test_get_waybill_raises_client_error_for_invalid_payload() -> None:
|
||||
response = httpx.Response(
|
||||
200,
|
||||
json={"entity": {}},
|
||||
request=httpx.Request(
|
||||
"GET", "https://api.cdek.test/v2/print/orders/waybill-uuid-1"
|
||||
),
|
||||
)
|
||||
http_client = SequenceHTTPClient([response])
|
||||
client = _make_client(http_client)
|
||||
|
||||
with pytest.raises(CDEKClientError, match="response payload is invalid"):
|
||||
asyncio.run(client.get_waybill("waybill-uuid-1"))
|
||||
@@ -1,10 +1,16 @@
|
||||
from app.adapters.delivery_providers.cdek.order_mapper import (
|
||||
CDEKOrderInfo,
|
||||
CDEKOrderMappingError,
|
||||
CDEKOrderRegistrationResult,
|
||||
CDEKWaybillInfo,
|
||||
compose_address_line,
|
||||
map_cdek_existing_order_response,
|
||||
map_cdek_order_info_response,
|
||||
map_cdek_order_request,
|
||||
map_cdek_order_response,
|
||||
map_cdek_waybill_info_response,
|
||||
)
|
||||
import pytest
|
||||
from app.schemas.payment import Address
|
||||
from tests.payment_fixtures import make_init_payment_request
|
||||
|
||||
@@ -207,3 +213,72 @@ def test_compose_address_line_handles_empty_apartment() -> None:
|
||||
)
|
||||
|
||||
assert compose_address_line(address) == "Москва, Lenina, 1"
|
||||
|
||||
|
||||
def test_map_cdek_order_info_response_picks_latest_status_by_date_time() -> None:
|
||||
result = map_cdek_order_info_response(
|
||||
{
|
||||
"entity": {
|
||||
"uuid": "cdek-order-uuid",
|
||||
"statuses": [
|
||||
{"code": "ACCEPTED", "date_time": "2026-05-24T10:00:00+0000"},
|
||||
{"code": "INVALID", "date_time": "2026-05-24T10:00:05+0000"},
|
||||
],
|
||||
},
|
||||
"related_entities": [
|
||||
{"type": "waybill", "uuid": "waybill-uuid-1"}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
assert result == CDEKOrderInfo(
|
||||
order_uuid="cdek-order-uuid",
|
||||
status_code="INVALID",
|
||||
waybill_uuid="waybill-uuid-1",
|
||||
)
|
||||
|
||||
|
||||
def test_map_cdek_order_info_response_returns_none_status_when_statuses_empty() -> None:
|
||||
result = map_cdek_order_info_response(
|
||||
{"entity": {"uuid": "cdek-order-uuid", "statuses": []}}
|
||||
)
|
||||
|
||||
assert result == CDEKOrderInfo(
|
||||
order_uuid="cdek-order-uuid",
|
||||
status_code=None,
|
||||
waybill_uuid=None,
|
||||
)
|
||||
|
||||
|
||||
def test_map_cdek_order_info_response_raises_for_missing_entity() -> None:
|
||||
with pytest.raises(CDEKOrderMappingError):
|
||||
map_cdek_order_info_response({"requests": []})
|
||||
|
||||
|
||||
def test_map_cdek_waybill_info_response_returns_url_when_present() -> None:
|
||||
result = map_cdek_waybill_info_response(
|
||||
{
|
||||
"entity": {
|
||||
"uuid": "waybill-uuid-1",
|
||||
"url": "https://cdek.test/waybill/1.pdf",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert result == CDEKWaybillInfo(
|
||||
waybill_uuid="waybill-uuid-1",
|
||||
url="https://cdek.test/waybill/1.pdf",
|
||||
)
|
||||
|
||||
|
||||
def test_map_cdek_waybill_info_response_returns_none_url_when_missing() -> None:
|
||||
result = map_cdek_waybill_info_response(
|
||||
{"entity": {"uuid": "waybill-uuid-1"}}
|
||||
)
|
||||
|
||||
assert result == CDEKWaybillInfo(waybill_uuid="waybill-uuid-1", url=None)
|
||||
|
||||
|
||||
def test_map_cdek_waybill_info_response_raises_for_missing_uuid() -> None:
|
||||
with pytest.raises(CDEKOrderMappingError):
|
||||
map_cdek_waybill_info_response({"entity": {}})
|
||||
|
||||
Reference in New Issue
Block a user