From ff4ad07a2b719147a96530d6f1ad893230425831 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Wed, 13 May 2026 11:41:21 +0000 Subject: [PATCH] retry --- backend/epc_client/tests/test_client.py | 86 ++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/backend/epc_client/tests/test_client.py b/backend/epc_client/tests/test_client.py index 0e95a844..70425a92 100644 --- a/backend/epc_client/tests/test_client.py +++ b/backend/epc_client/tests/test_client.py @@ -8,12 +8,13 @@ from datatypes.epc.domain.epc_property_data import EpcPropertyData from backend.epc_client.tests.conftest import make_search_row -def _mock_response(status_code=200, json_data=None): +def _mock_response(status_code=200, json_data=None, headers=None): resp = MagicMock() resp.status_code = status_code resp.is_success = 200 <= status_code < 300 resp.json.return_value = json_data or {} resp.text = str(json_data) + resp.headers = headers or {} return resp @@ -63,6 +64,89 @@ def test_get_by_certificate_number_retries_on_429_and_succeeds( assert isinstance(result, EpcPropertyData) +# --------------------------------------------------------------------------- +# Test 3b: 429 with Retry-After header → sleeps for that value +# --------------------------------------------------------------------------- + + +def test_429_retry_after_header_drives_sleep_duration( + epc_service, rdsap_21_0_1_cert +): + cert_response = {"data": rdsap_21_0_1_cert} + responses = [ + _mock_response(429, headers={"Retry-After": "7"}), + _mock_response(200, cert_response), + ] + with patch("httpx.get", side_effect=responses), patch( + "backend.epc_client._retry.time.sleep" + ) as mock_sleep: + epc_service.get_by_certificate_number("CERT-001") + + mock_sleep.assert_called_once_with(7.0) + + +# --------------------------------------------------------------------------- +# Test 3c: 429 without Retry-After → falls back to exponential backoff +# --------------------------------------------------------------------------- + + +def test_429_without_retry_after_uses_exponential_backoff( + epc_service, rdsap_21_0_1_cert +): + cert_response = {"data": rdsap_21_0_1_cert} + responses = [ + _mock_response(429), + _mock_response(429), + _mock_response(200, cert_response), + ] + with patch("httpx.get", side_effect=responses), patch( + "backend.epc_client._retry.time.sleep" + ) as mock_sleep: + epc_service.get_by_certificate_number("CERT-001") + + assert mock_sleep.call_args_list == [call(1.0), call(2.0)] + + +# --------------------------------------------------------------------------- +# Test 3d: malformed Retry-After header → falls back to exponential backoff +# --------------------------------------------------------------------------- + + +def test_429_malformed_retry_after_falls_back_to_backoff( + epc_service, rdsap_21_0_1_cert +): + cert_response = {"data": rdsap_21_0_1_cert} + responses = [ + _mock_response(429, headers={"Retry-After": "Wed, 21 Oct 2026 07:28:00 GMT"}), + _mock_response(200, cert_response), + ] + with patch("httpx.get", side_effect=responses), patch( + "backend.epc_client._retry.time.sleep" + ) as mock_sleep: + epc_service.get_by_certificate_number("CERT-001") + + mock_sleep.assert_called_once_with(1.0) + + +# --------------------------------------------------------------------------- +# Test 3e: Retry-After capped by max_backoff to avoid hostile/buggy values +# --------------------------------------------------------------------------- + + +def test_429_retry_after_capped_by_max_backoff(epc_service, rdsap_21_0_1_cert): + cert_response = {"data": rdsap_21_0_1_cert} + responses = [ + _mock_response(429, headers={"Retry-After": "9999"}), + _mock_response(200, cert_response), + ] + with patch("httpx.get", side_effect=responses), patch( + "backend.epc_client._retry.time.sleep" + ) as mock_sleep: + epc_service.get_by_certificate_number("CERT-001") + + mock_sleep.assert_called_once_with(60.0) + + # --------------------------------------------------------------------------- # Test 4: get_by_uprn empty search → None # ---------------------------------------------------------------------------