- All frontend pages, labels, notices and errors; html lang=uk, uk-UA money format - Brand "Assistant System" in the top bar and page title - Backend error details returned to the UI (auth, orders, receipts, cash registers, Checkbox/CRM/NP errors) and CLI output - Tests updated for the new messages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
4.4 KiB
Python
117 lines
4.4 KiB
Python
"""Тесты NpTrackingClient. Сеть замокана через respx — реальных запросов не делает."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
import respx
|
|
from httpx import Response
|
|
|
|
from app.schemas.tracking import TrackingStatusOut
|
|
from app.services.nova_poshta.client import NovaPoshtaError
|
|
from app.services.nova_poshta.np_client import _API_URL, NpTrackingClient
|
|
|
|
STATUS_PAYLOAD = {
|
|
"Number": "20451540916703",
|
|
"Status": "Відправник самостійно вказав цю накладну, але ще не надав до відправки",
|
|
"StatusCode": "1",
|
|
"PaymentMethod": "Cash",
|
|
"ScheduledDeliveryDate": "22-09-2026 18:00:00",
|
|
"ActualDeliveryDate": "",
|
|
"AmountToPay": "",
|
|
"ExpressWaybillAmountToPay": "827.48",
|
|
"AfterpaymentOnGoodsCost": 699,
|
|
"PaymentStatus": "",
|
|
"ExpressWaybillPaymentStatus": "NeedPayment",
|
|
}
|
|
|
|
|
|
API_KEY = "np-apikey-123"
|
|
|
|
|
|
class TestNpTrackingClientGetStatuses:
|
|
@respx.mock
|
|
async def test_sends_expected_request_body(self) -> None:
|
|
route = respx.post(_API_URL).mock(
|
|
return_value=Response(200, json={"success": True, "data": [], "errors": []})
|
|
)
|
|
client = NpTrackingClient()
|
|
|
|
await client.get_statuses(api_key=API_KEY, waybill_numbers=["20451540916703"])
|
|
|
|
sent = route.calls.last.request
|
|
body = json.loads(sent.content)
|
|
assert body["apiKey"] == API_KEY
|
|
assert body["modelName"] == "TrackingDocument"
|
|
assert body["calledMethod"] == "getStatusDocuments"
|
|
assert body["methodProperties"]["Documents"] == [
|
|
{"DocumentNumber": "20451540916703", "Phone": ""}
|
|
]
|
|
|
|
@respx.mock
|
|
async def test_parses_successful_response_and_prefers_afterpayment_on_goods_cost(
|
|
self,
|
|
) -> None:
|
|
respx.post(_API_URL).mock(
|
|
return_value=Response(
|
|
200, json={"success": True, "data": [STATUS_PAYLOAD], "errors": []}
|
|
)
|
|
)
|
|
client = NpTrackingClient()
|
|
|
|
statuses = await client.get_statuses(api_key=API_KEY, waybill_numbers=["20451540916703"])
|
|
|
|
assert len(statuses) == 1
|
|
status = statuses[0]
|
|
assert status.number == "20451540916703"
|
|
assert status.status_code == "1"
|
|
# "Контроль оплати" — сумма за товар без стоимости доставки и комиссии НП,
|
|
# не "сколько заплатить сейчас" (`ExpressWaybillAmountToPay` = 827.48).
|
|
assert status.cod_amount == "699"
|
|
assert status.payment_status == "NeedPayment"
|
|
assert not status.is_own # в STATUS_PAYLOAD нет PhoneSender
|
|
|
|
def test_is_own_by_sender_phone(self) -> None:
|
|
own = TrackingStatusOut.model_validate({**STATUS_PAYLOAD, "PhoneSender": "380961112233"})
|
|
foreign = TrackingStatusOut.model_validate({**STATUS_PAYLOAD, "PhoneSender": ""})
|
|
assert own.is_own
|
|
assert not foreign.is_own
|
|
|
|
@respx.mock
|
|
async def test_falls_back_to_amount_to_pay_when_afterpayment_missing(self) -> None:
|
|
payload = {**STATUS_PAYLOAD, "AfterpaymentOnGoodsCost": 0}
|
|
respx.post(_API_URL).mock(
|
|
return_value=Response(200, json={"success": True, "data": [payload], "errors": []})
|
|
)
|
|
client = NpTrackingClient()
|
|
|
|
statuses = await client.get_statuses(api_key=API_KEY, waybill_numbers=["20451540916703"])
|
|
|
|
assert statuses[0].cod_amount == "827.48"
|
|
|
|
@respx.mock
|
|
async def test_raises_nova_poshta_error_on_failure(self) -> None:
|
|
respx.post(_API_URL).mock(
|
|
return_value=Response(
|
|
200, json={"success": False, "data": [], "errors": ["Invalid apiKey"]}
|
|
)
|
|
)
|
|
client = NpTrackingClient()
|
|
|
|
with pytest.raises(NovaPoshtaError, match="Invalid apiKey"):
|
|
await client.get_statuses(api_key=API_KEY, waybill_numbers=["20451540916703"])
|
|
|
|
async def test_returns_empty_list_for_no_documents(self) -> None:
|
|
client = NpTrackingClient()
|
|
|
|
assert await client.get_statuses(api_key=API_KEY, waybill_numbers=[]) == []
|
|
|
|
async def test_rejects_too_many_documents(self) -> None:
|
|
client = NpTrackingClient()
|
|
|
|
with pytest.raises(NovaPoshtaError, match="Забагато"):
|
|
await client.get_statuses(
|
|
api_key=API_KEY, waybill_numbers=[str(i) for i in range(101)]
|
|
)
|