"""Retry behavior: only ever on connection failures or a `503`, never on a `4xx`, and never inside a started SSE stream. See `ragcore_sdk.client`'s module docstring for the reasoning. """ from __future__ import annotations import httpx import pytest from ragcore_sdk import RAGCoreAPIError, RAGCoreClient, RAGCoreConnectionError from conftest import build_search_app, run_app class FlakyTransport(httpx.BaseTransport): """Returns a synthetic 503 for the first `fail_times` requests, then delegates to a real HTTP transport pointed at the real running server. Counts every request it sees. """ def __init__(self, *, fail_times: int) -> None: self._inner = httpx.HTTPTransport() self._fail_times = fail_times self.call_count = 0 def handle_request(self, request: httpx.Request) -> httpx.Response: self.call_count += 1 if self.call_count <= self._fail_times: return httpx.Response( 503, json={ "type": "https://ragcore.example.test/problems/search_unavailable", "title": "Search unavailable", "status": 503, "code": "SEARCH_UNAVAILABLE", "detail": "synthetic failure for retry test", "trace_id": "test-trace", }, ) return self._inner.handle_request(request) class AlwaysDownTransport(httpx.BaseTransport): def __init__(self) -> None: self.call_count = 0 def handle_request(self, request: httpx.Request) -> httpx.Response: self.call_count += 1 raise httpx.ConnectError("synthetic connection failure", request=request) def test_retries_on_503_then_succeeds() -> None: fixture = build_search_app() with run_app(fixture.app) as base_url: transport = FlakyTransport(fail_times=2) client = RAGCoreClient( base_url=base_url, token="sdk-test-token", transport=transport, max_retries=3, ) try: response = client.search(query="refund", requested_space_ids=[fixture.space_id]) finally: client.close() assert transport.call_count == 3 assert response.results def test_does_not_retry_a_client_error() -> None: fixture = build_search_app() with run_app(fixture.app) as base_url: # fail_times=0: every request goes straight through to the real app, # so this only counts calls -- the 422 below comes from the app's # own request validation, not from the transport. counting = FlakyTransport(fail_times=0) client = RAGCoreClient( base_url=base_url, token="sdk-test-token", transport=counting, max_retries=3, ) try: with pytest.raises(RAGCoreAPIError) as excinfo: client.search(query=" ") finally: client.close() assert excinfo.value.problem.status == 422 assert counting.call_count == 1 def test_connection_failure_exhausts_retries_then_raises() -> None: transport = AlwaysDownTransport() client = RAGCoreClient( base_url="http://127.0.0.1:1", token="sdk-test-token", transport=transport, max_retries=2, ) try: with pytest.raises(RAGCoreConnectionError): client.search(query="refund") finally: client.close() assert transport.call_count == 3