feat: Enhance Booklooker client with webhook signature verification, idempotency handling, and retry logic
This commit is contained in:
parent
1d8ee1bba6
commit
f2e5774204
6 changed files with 232 additions and 50 deletions
50
tests/test_resilience.py
Normal file
50
tests/test_resilience.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import hashlib
|
||||
import hmac
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from booklooker_client import BooklookerConfig, BooklookerWebhookHelper, SyncBooklookerClient
|
||||
|
||||
|
||||
def test_sync_client_retries_transient_timeout() -> None:
|
||||
attempts = {"count": 0}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
attempts["count"] += 1
|
||||
if attempts["count"] == 1:
|
||||
raise httpx.ReadTimeout("temporary timeout", request=request)
|
||||
return httpx.Response(200, json={"status": "OK", "returnValue": "REST_API_TOKEN"})
|
||||
|
||||
config = BooklookerConfig(api_key="demo", max_retries=1, retry_backoff_seconds=0)
|
||||
client = SyncBooklookerClient(config)
|
||||
client._http = httpx.Client(transport=httpx.MockTransport(handler), base_url=config.base_url)
|
||||
|
||||
try:
|
||||
token = client.authenticate()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
assert token.token == "REST_API_TOKEN"
|
||||
assert attempts["count"] == 2
|
||||
|
||||
|
||||
def test_webhook_signature_verification() -> None:
|
||||
payload = b'{"event_type":"order.created","event_id":"evt-10"}'
|
||||
secret = "top-secret"
|
||||
signature = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
|
||||
|
||||
helper = BooklookerWebhookHelper(webhook_secret=secret)
|
||||
assert helper.verify_signature(payload, signature) is True
|
||||
|
||||
|
||||
def test_import_file_rejects_oversized_input(tmp_path) -> None:
|
||||
file_path = tmp_path / "payload.bin"
|
||||
file_path.write_bytes(b"1234")
|
||||
|
||||
client = SyncBooklookerClient(BooklookerConfig(api_key="demo", max_upload_size_bytes=1))
|
||||
try:
|
||||
with pytest.raises(ValueError):
|
||||
client.import_file(file_path, data_type=0)
|
||||
finally:
|
||||
client.close()
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from booklooker_client import BooklookerWebhookHelper
|
||||
from booklooker_client import BooklookerWebhookHelper, InMemoryIdempotencyStore
|
||||
from booklooker_client.models.order import OrderBatch, OrderRecord
|
||||
|
||||
|
||||
|
|
@ -39,3 +39,9 @@ def test_duplicate_detection() -> None:
|
|||
assert first.resource_type == "order"
|
||||
assert second.resource_type == "duplicate"
|
||||
assert second.enriched_data == {"duplicate": True}
|
||||
|
||||
|
||||
def test_idempotency_store_ttl_expiry() -> None:
|
||||
store = InMemoryIdempotencyStore(ttl_seconds=0)
|
||||
store.mark_seen("evt-expire")
|
||||
assert store.has_seen("evt-expire") is False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue