fix(client): validate Retry-After header (cap, fallback, edge cases)
- Ajoute try/except autour du float() pour gérer les dates HTTP RFC 7231 - Cap à 30s pour éviter un blocage indéfini sur valeur énorme - Plancher à _RETRY_DELAY pour Retry-After: 0 ou négatif (FINDING-R2) - 4 nouveaux tests : date HTTP, valeur zéro, valeur énorme, health check partiel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -269,6 +269,49 @@ class TestGetWithRetry429:
|
||||
|
||||
assert mock_sleep.call_count == 2
|
||||
|
||||
@patch("time.sleep")
|
||||
def test_retry_after_http_date_falls_back_to_backoff(self, mock_sleep):
|
||||
"""Retry-After contenant une date HTTP RFC 7231 (non-numerique) :
|
||||
le parsing echoue silencieusement et on retombe sur le backoff lineaire."""
|
||||
client = self._make_client()
|
||||
# Valeur realiste envoyee par certains serveurs
|
||||
resp_429 = self._make_429_response(retry_after="Wed, 21 Oct 2025 07:28:00 GMT")
|
||||
resp_200 = self._make_200_response()
|
||||
|
||||
with patch.object(client.session, "get", side_effect=[resp_429, resp_200]):
|
||||
result = client._get_with_retry("http://gitea.local:3000/api/v1/test")
|
||||
|
||||
assert result.status_code == 200
|
||||
# Backoff lineaire : attempt=0 → 1 * 1.0 = 1.0s
|
||||
mock_sleep.assert_called_once_with(1.0)
|
||||
|
||||
@patch("time.sleep")
|
||||
def test_retry_after_zero_uses_floor(self, mock_sleep):
|
||||
"""Retry-After: 0 ne provoque pas un retry immediat sans backoff.
|
||||
Le plancher (_RETRY_DELAY = 1.0s) est applique."""
|
||||
client = self._make_client()
|
||||
resp_429 = self._make_429_response(retry_after="0")
|
||||
resp_200 = self._make_200_response()
|
||||
|
||||
with patch.object(client.session, "get", side_effect=[resp_429, resp_200]):
|
||||
result = client._get_with_retry("http://gitea.local:3000/api/v1/test")
|
||||
|
||||
assert result.status_code == 200
|
||||
mock_sleep.assert_called_once_with(1.0) # plancher _RETRY_DELAY
|
||||
|
||||
@patch("time.sleep")
|
||||
def test_retry_after_huge_value_capped_at_30s(self, mock_sleep):
|
||||
"""Retry-After avec une valeur enorme est plafonne a 30s."""
|
||||
client = self._make_client()
|
||||
resp_429 = self._make_429_response(retry_after="3600") # 1 heure
|
||||
resp_200 = self._make_200_response()
|
||||
|
||||
with patch.object(client.session, "get", side_effect=[resp_429, resp_200]):
|
||||
result = client._get_with_retry("http://gitea.local:3000/api/v1/test")
|
||||
|
||||
assert result.status_code == 200
|
||||
mock_sleep.assert_called_once_with(30.0) # cap a 30s
|
||||
|
||||
@patch("time.sleep")
|
||||
def test_retry_on_429_then_timeout(self, mock_sleep):
|
||||
"""429 followed by Timeout: both retry types handled in same loop."""
|
||||
|
||||
Reference in New Issue
Block a user