Add typed Pydantic models for positions and document payments, enhance EasybillClient with pagination and retry handling, and implement unit tests for workflow helpers.

This commit is contained in:
claudi 2026-04-17 11:13:33 +02:00
parent b324671286
commit 57d6a49986
6 changed files with 375 additions and 5 deletions

View file

@ -0,0 +1,155 @@
import httpx
import pytest
from easybill_client import AsyncEasybillClient, EasybillClient
class WorkflowTransport(httpx.MockTransport):
def __init__(self):
self.rate_limit_hits = 0
super().__init__(self._handler)
def _handler(self, request: httpx.Request) -> httpx.Response:
if request.url.path == "/positions/5":
return httpx.Response(
200,
json={
"id": 5,
"number": "ART-5",
"type": "PRODUCT",
"description": "Premium Support",
"price": 4900,
},
)
if request.url.path == "/document-payments":
if request.method == "POST":
body = request.read().decode()
assert "INV-1" in body
return httpx.Response(
201,
json={
"id": 88,
"document_id": 10,
"amount": 1999,
"reference": "INV-1",
"type": "CASH",
},
)
return httpx.Response(
200,
json={
"page": 1,
"pages": 1,
"limit": 100,
"total": 1,
"items": [
{
"id": 88,
"document_id": 10,
"amount": 1999,
"reference": "INV-1",
"type": "CASH",
}
],
},
)
if request.url.path == "/customers":
page = request.url.params.get("page", "1")
if page == "1":
return httpx.Response(
200,
json={
"page": 1,
"pages": 2,
"limit": 1,
"total": 2,
"items": [{"id": 1, "company_name": "ACME GmbH"}],
},
)
return httpx.Response(
200,
json={
"page": 2,
"pages": 2,
"limit": 1,
"total": 2,
"items": [{"id": 2, "company_name": "Example AG"}],
},
)
if request.url.path == "/documents":
if self.rate_limit_hits == 0:
self.rate_limit_hits += 1
return httpx.Response(429, headers={"Retry-After": "0"}, json={"error": "slow down"})
return httpx.Response(
200,
json={
"page": 1,
"pages": 1,
"limit": 100,
"total": 1,
"items": [{"id": 10, "number": "RE-10", "amount": 1999}],
},
)
return httpx.Response(404, json={"error": "not found"})
class AsyncWorkflowTransport(httpx.MockTransport):
def __init__(self):
super().__init__(self._handler)
@staticmethod
def _handler(request: httpx.Request) -> httpx.Response:
if request.url.path == "/positions":
return httpx.Response(
200,
json={
"page": 1,
"pages": 1,
"limit": 100,
"total": 1,
"items": [{"id": 7, "number": "ART-7", "description": "Hosting"}],
},
)
return httpx.Response(404, json={"error": "not found"})
def test_sync_workflow_helpers_cover_positions_payments_pagination_and_retry():
transport = WorkflowTransport()
client = EasybillClient(api_key="token", transport=transport, max_retries=1, retry_backoff=0)
try:
position = client.get_position(5)
payments = client.list_document_payments(document_id=10)
created_payment = client.create_document_payment(document_id=10, amount=1999, reference="INV-1")
all_customers = list(client.iter_all_customers(limit=1))
all_payments = list(client.iter_all_document_payments(document_id=10))
documents = client.list_documents()
finally:
client.close()
assert position.id == 5
assert position.price == 4900
assert payments.items[0].reference == "INV-1"
assert created_payment.id == 88
assert [customer.id for customer in all_customers] == [1, 2]
assert [payment.id for payment in all_payments] == [88]
assert documents.items[0].number == "RE-10"
@pytest.mark.asyncio
async def test_async_workflow_helpers_list_positions_as_models():
client = AsyncEasybillClient(api_key="token", transport=AsyncWorkflowTransport())
try:
positions = await client.list_positions()
iterated = [item async for item in client.iter_all_positions()]
finally:
await client.aclose()
assert positions.total == 1
assert positions.items[0].number == "ART-7"
assert iterated[0].id == 7