You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iTi-Flask/tests/test_service_client.py

109 lines
3.1 KiB
Python

from __future__ import annotations
import httpx
import pytest
from iti.service_client import (
ServiceClient,
ServiceConfig,
ServiceHTTPError,
ServiceUnavailableError,
)
from iti.service_client.config import CircuitBreakerConfig, RetryConfig
def _json_response(status_code: int, payload: dict) -> httpx.Response:
return httpx.Response(status_code, json=payload)
def test_service_client_sends_json_headers_token_trace_and_path():
seen: dict[str, object] = {}
def handler(request: httpx.Request) -> httpx.Response:
seen["url"] = str(request.url)
seen["authorization"] = request.headers.get("Authorization")
seen["trace_id"] = request.headers.get("X-Trace-Id")
return _json_response(200, {"ok": True})
client = ServiceClient(
ServiceConfig(name="erp", base_url="http://erp.local", token="token-a"),
transport=httpx.MockTransport(handler),
)
result = client.get("/users/{id}", path={"id": 12}, params={"active": "1"})
assert result == {"ok": True}
assert seen["url"] == "http://erp.local/users/12?active=1"
assert seen["authorization"] == "Bearer token-a"
assert isinstance(seen["trace_id"], str)
assert seen["trace_id"]
def test_service_client_retries_idempotent_statuses():
calls = {"count": 0}
def handler(request: httpx.Request) -> httpx.Response:
calls["count"] += 1
if calls["count"] == 1:
return _json_response(503, {"error": "busy"})
return _json_response(200, {"ok": True})
client = ServiceClient(
ServiceConfig(
name="erp",
base_url="http://erp.local",
retry=RetryConfig(attempts=2, backoff=0),
),
transport=httpx.MockTransport(handler),
)
assert client.get("/health") == {"ok": True}
assert calls["count"] == 2
def test_service_client_does_not_retry_post_by_default():
calls = {"count": 0}
def handler(request: httpx.Request) -> httpx.Response:
calls["count"] += 1
return _json_response(503, {"error": "busy"})
client = ServiceClient(
ServiceConfig(
name="erp",
base_url="http://erp.local",
retry=RetryConfig(attempts=3, backoff=0),
),
transport=httpx.MockTransport(handler),
)
with pytest.raises(ServiceHTTPError) as exc_info:
client.post("/jobs", json={"kind": "users"})
assert exc_info.value.status_code == 503
assert calls["count"] == 1
def test_service_client_opens_circuit_breaker_after_failures():
def handler(request: httpx.Request) -> httpx.Response:
return _json_response(500, {"error": "failed"})
client = ServiceClient(
ServiceConfig(
name="erp",
base_url="http://erp.local",
circuit_breaker=CircuitBreakerConfig(
enabled=True,
fail_max=1,
reset_timeout=30,
),
),
transport=httpx.MockTransport(handler),
)
with pytest.raises(ServiceHTTPError):
client.get("/health")
with pytest.raises(ServiceUnavailableError, match="circuit breaker is open"):
client.get("/health")