017 fix returning all tariffs

This commit is contained in:
Раис Юсупалиев
2026-03-16 00:05:10 +03:00
parent 9b9cca0a7c
commit 54160a0e38
11 changed files with 188 additions and 81 deletions
@@ -1,4 +1,5 @@
import asyncio
from decimal import Decimal
from typing import Any
import httpx
@@ -134,6 +135,13 @@ class RecordingHTTPClient:
"currency": "RUB",
"period_min": 1,
"period_max": 2,
},
{
"tariff_name": "Economy",
"delivery_sum": "499.00",
"currency": "RUB",
"period_min": 3,
"period_max": 5,
}
]
},
@@ -341,13 +349,15 @@ observability:
adapter_config=settings.adapter,
)
result = asyncio.run(provider.get_price(_make_request()))
result = asyncio.run(provider.get_prices(_make_request()))
assert provider.cache_ttl_seconds == 777
assert http_client.auth_timeouts == [7.5]
assert http_client.city_lookup_timeouts == [7.5, 7.5]
assert http_client.tariff_timeouts == [7.5]
assert result.provider == "cdek"
assert [price.provider for price in result] == ["cdek", "cdek"]
assert [price.service_name for price in result] == ["Express", "Economy"]
assert [price.price for price in result] == [Decimal("899.00"), Decimal("499.00")]
def test_provider_uses_default_adapter_timeout_and_cache_ttl() -> None:
@@ -361,9 +371,10 @@ def test_provider_uses_default_adapter_timeout_and_cache_ttl() -> None:
adapter_config=adapter_config,
)
asyncio.run(provider.get_price(_make_request()))
result = asyncio.run(provider.get_prices(_make_request()))
assert provider.cache_ttl_seconds == 900
assert http_client.auth_timeouts == [10.0]
assert http_client.city_lookup_timeouts == [10.0, 10.0]
assert http_client.tariff_timeouts == [10.0]
assert len(result) == 2
@@ -5,8 +5,9 @@ import pytest
from app.adapters.delivery_providers.cdek.mapper import CDEKMappingError, map_cdek_response
def test_map_cdek_response_maps_first_tariff_to_unified_model() -> None:
def test_map_cdek_response_maps_all_tariffs_to_unified_model() -> None:
payload = {
"currency": "usd",
"tariff_codes": [
{
"tariff_name": "Express",
@@ -14,18 +15,24 @@ def test_map_cdek_response_maps_first_tariff_to_unified_model() -> None:
"currency": "rub",
"period_min": 2,
"period_max": 4,
},
{
"tariff_code": 136,
"delivery_sum": "999.00",
"period_min": 5,
"period_max": 7,
}
]
}
result = map_cdek_response(payload)
assert result.provider == "cdek"
assert result.service_name == "Express"
assert result.price == Decimal("1234.50")
assert result.currency == "RUB"
assert result.delivery_days_min == 2
assert result.delivery_days_max == 4
assert [price.provider for price in result] == ["cdek", "cdek"]
assert [price.service_name for price in result] == ["Express", "136"]
assert [price.price for price in result] == [Decimal("1234.50"), Decimal("999.00")]
assert [price.currency for price in result] == ["RUB", "USD"]
assert [price.delivery_days_min for price in result] == [2, 5]
assert [price.delivery_days_max for price in result] == [4, 7]
def test_map_cdek_response_raises_for_missing_tariff_codes() -> None:
+48 -20
View File
@@ -64,16 +64,26 @@ def test_post_delivery_price_uses_registered_provider_in_default_dependency(
def __init__(self) -> None:
self.calls: list[DeliveryRequest] = []
async def get_price(self, request: DeliveryRequest) -> DeliveryPrice:
async def get_prices(self, request: DeliveryRequest) -> list[DeliveryPrice]:
self.calls.append(request)
return DeliveryPrice(
provider=self.name,
service_name="stub-service",
price=Decimal("123.50"),
currency="RUB",
delivery_days_min=2,
delivery_days_max=3,
)
return [
DeliveryPrice(
provider=self.name,
service_name="slow",
price=Decimal("223.50"),
currency="RUB",
delivery_days_min=4,
delivery_days_max=5,
),
DeliveryPrice(
provider=self.name,
service_name="fast",
price=Decimal("123.50"),
currency="RUB",
delivery_days_min=2,
delivery_days_max=3,
),
]
class StubCDEKProvider:
@classmethod
@@ -135,11 +145,19 @@ def test_post_delivery_price_uses_registered_provider_in_default_dependency(
assert first_response.json() == [
{
"provider": "stub-provider",
"service_name": "stub-service",
"service_name": "fast",
"price": "124",
"currency": "RUB",
"delivery_days_min": 2,
"delivery_days_max": 3,
},
{
"provider": "stub-provider",
"service_name": "slow",
"price": "224",
"currency": "RUB",
"delivery_days_min": 4,
"delivery_days_max": 5,
}
]
assert second_response.json() == first_response.json()
@@ -148,15 +166,25 @@ def test_post_delivery_price_uses_registered_provider_in_default_dependency(
def test_post_delivery_price_returns_prices_and_delegates_to_service() -> None:
expected_price = DeliveryPrice(
provider="cdek",
service_name="express",
price=Decimal("100.50"),
currency="RUB",
delivery_days_min=1,
delivery_days_max=2,
)
service = StubAggregatorService(response=[expected_price])
expected_prices = [
DeliveryPrice(
provider="cdek",
service_name="economy",
price=Decimal("150.00"),
currency="RUB",
delivery_days_min=3,
delivery_days_max=5,
),
DeliveryPrice(
provider="cdek",
service_name="express",
price=Decimal("100.50"),
currency="RUB",
delivery_days_min=1,
delivery_days_max=2,
),
]
service = StubAggregatorService(response=expected_prices)
app = create_app()
_install_service_override(app, service)
@@ -171,7 +199,7 @@ def test_post_delivery_price_returns_prices_and_delegates_to_service() -> None:
response = asyncio.run(run_request())
assert response.status_code == 200
assert response.json() == [expected_price.model_dump(mode="json")]
assert response.json() == [price.model_dump(mode="json") for price in expected_prices]
assert len(service.calls) == 1
assert service.calls[0] == DeliveryRequest(
entity=DeliveryEntity.INDIVIDUAL,
+53 -18
View File
@@ -17,7 +17,7 @@ class StubProvider:
self,
name: str,
*,
response: DeliveryPrice | None = None,
response: list[DeliveryPrice] | None = None,
error: Exception | None = None,
cache_ttl_seconds: int = 900,
) -> None:
@@ -27,7 +27,7 @@ class StubProvider:
self.cache_ttl_seconds = cache_ttl_seconds
self.calls: list[DeliveryRequest] = []
async def get_price(self, request: DeliveryRequest) -> DeliveryPrice:
async def get_prices(self, request: DeliveryRequest) -> list[DeliveryPrice]:
self.calls.append(request)
if self._error is not None:
raise self._error
@@ -73,10 +73,10 @@ def _make_request(**overrides: object) -> DeliveryRequest:
return DeliveryRequest(**payload)
def _make_price(provider: str, price: str) -> DeliveryPrice:
def _make_price(provider: str, price: str, *, service_name: str = "standard") -> DeliveryPrice:
return DeliveryPrice(
provider=provider,
service_name="standard",
service_name=service_name,
price=Decimal(price),
currency="RUB",
delivery_days_min=2,
@@ -85,8 +85,19 @@ def _make_price(provider: str, price: str) -> DeliveryPrice:
def test_get_all_prices_full_success_returns_sorted_and_updates_cache() -> None:
provider_a = StubProvider(name="a", response=_make_price("a", "300.49"), cache_ttl_seconds=111)
provider_b = StubProvider(name="b", response=_make_price("b", "100.40"), cache_ttl_seconds=222)
provider_a = StubProvider(
name="a",
response=[
_make_price("a", "300.49", service_name="economy"),
_make_price("a", "200.49", service_name="express"),
],
cache_ttl_seconds=111,
)
provider_b = StubProvider(
name="b",
response=[_make_price("b", "100.40")],
cache_ttl_seconds=222,
)
cache = StubCache()
service = AggregatorService(
[provider_a, provider_b],
@@ -96,16 +107,18 @@ def test_get_all_prices_full_success_returns_sorted_and_updates_cache() -> None:
result = asyncio.run(service.get_all_prices(_make_request()))
assert [price.provider for price in result] == ["b", "a"]
assert [price.price for price in result] == [Decimal("110"), Decimal("331")]
assert [price.provider for price in result] == ["b", "a", "a"]
assert [price.service_name for price in result] == ["standard", "express", "economy"]
assert [price.price for price in result] == [Decimal("110"), Decimal("221"), Decimal("331")]
assert len(provider_a.calls) == 1
assert len(provider_b.calls) == 1
assert len(cache.get_calls) == 2
assert [len(payload) for _, payload, _ in cache.set_calls] == [2, 1]
assert [ttl for _, _, ttl in cache.set_calls] == [111, 222]
def test_get_all_prices_normalizes_and_forwards_country_code_to_providers() -> None:
provider = StubProvider(name="cdek", response=_make_price("cdek", "150.00"))
provider = StubProvider(name="cdek", response=[_make_price("cdek", "150.00")])
service = AggregatorService([provider], cache=StubCache())
result = asyncio.run(service.get_all_prices(_make_request(country_code=" kz ")))
@@ -116,13 +129,20 @@ def test_get_all_prices_normalizes_and_forwards_country_code_to_providers() -> N
def test_get_all_prices_partial_failure_excludes_failed_provider() -> None:
provider_ok = StubProvider(name="ok", response=_make_price("ok", "150.00"))
provider_ok = StubProvider(
name="ok",
response=[
_make_price("ok", "150.00", service_name="economy"),
_make_price("ok", "120.00", service_name="express"),
],
)
provider_failed = StubProvider(name="failed", error=RuntimeError("provider unavailable"))
service = AggregatorService([provider_ok, provider_failed], cache=StubCache())
result = asyncio.run(service.get_all_prices(_make_request()))
assert [price.provider for price in result] == ["ok"]
assert [price.provider for price in result] == ["ok", "ok"]
assert [price.service_name for price in result] == ["express", "economy"]
assert len(provider_ok.calls) == 1
assert len(provider_failed.calls) == 1
@@ -153,9 +173,12 @@ def test_get_all_prices_raises_invalid_request_for_provider_request_errors() ->
def test_get_all_prices_cache_hit_skips_provider_call() -> None:
cached_payload = _make_price("cdek", "100.40").model_dump(mode="json")
cached_payload = [
_make_price("cdek", "100.40", service_name="express").model_dump(mode="json"),
_make_price("cdek", "200.40", service_name="economy").model_dump(mode="json"),
]
cache = StubCache(forced_get_value=cached_payload)
provider = StubProvider(name="cdek", response=_make_price("cdek", "150.00"))
provider = StubProvider(name="cdek", response=[_make_price("cdek", "150.00")])
service = AggregatorService(
[provider],
cache=cache,
@@ -164,16 +187,23 @@ def test_get_all_prices_cache_hit_skips_provider_call() -> None:
result = asyncio.run(service.get_all_prices(_make_request()))
assert [price.provider for price in result] == ["cdek"]
assert result[0].price == Decimal("110")
assert [price.provider for price in result] == ["cdek", "cdek"]
assert [price.service_name for price in result] == ["express", "economy"]
assert [price.price for price in result] == [Decimal("110"), Decimal("220")]
assert provider.calls == []
assert len(cache.get_calls) == 1
assert cache.set_calls == []
def test_get_all_prices_delegates_filtering_and_sorting_to_domain_logic() -> None:
provider_a = StubProvider(name="a", response=_make_price("a", "300.00"))
provider_b = StubProvider(name="b", response=_make_price("b", "100.00"))
provider_a = StubProvider(
name="a",
response=[
_make_price("a", "300.00", service_name="economy"),
_make_price("a", "200.00", service_name="express"),
],
)
provider_b = StubProvider(name="b", response=[_make_price("b", "100.00")])
delegated_inputs: list[list[DeliveryPrice]] = []
delegated_multipliers: list[Decimal] = []
@@ -192,6 +222,11 @@ def test_get_all_prices_delegates_filtering_and_sorting_to_domain_logic() -> Non
result = asyncio.run(service.get_all_prices(_make_request()))
assert [price.provider for price in delegated_inputs[0]] == ["a", "b"]
assert [price.provider for price in delegated_inputs[0]] == ["a", "a", "b"]
assert [price.service_name for price in delegated_inputs[0]] == [
"economy",
"express",
"standard",
]
assert delegated_multipliers == [Decimal("1.23")]
assert [price.provider for price in result] == ["a"]