|
1 | | -from unittest.mock import MagicMock, patch |
2 | 1 | from urllib.parse import urlencode |
3 | 2 |
|
4 | 3 | import httpx |
5 | 4 | from asgiref.sync import async_to_sync |
6 | 5 | from django.core.files.uploadedfile import SimpleUploadedFile |
7 | | -from django.http import HttpResponse |
8 | 6 | from django.test.client import RequestFactory |
9 | | -from requests.exceptions import Timeout |
10 | 7 |
|
11 | 8 | from sentry.hybridcloud.apigateway.proxy import proxy_request as _proxy_request |
12 | 9 | from sentry.silo.util import ( |
13 | 10 | INVALID_OUTBOUND_HEADERS, |
14 | 11 | PROXY_APIGATEWAY_HEADER, |
15 | 12 | PROXY_DIRECT_LOCATION_HEADER, |
16 | 13 | ) |
17 | | -from sentry.testutils.helpers import override_options |
18 | 14 | from sentry.testutils.helpers.apigateway import ( |
19 | 15 | ApiGatewayTestCase, |
20 | 16 | verify_file_body, |
@@ -254,125 +250,3 @@ def test_strip_request_headers(self) -> None: |
254 | 250 |
|
255 | 251 | resp = proxy_request(request, self.organization.slug, url_name) |
256 | 252 | assert not any([header in resp for header in INVALID_OUTBOUND_HEADERS]) |
257 | | - |
258 | | - |
259 | | -CB_ENABLED = { |
260 | | - "apigateway.proxy.circuit-breaker.enabled": True, |
261 | | - "apigateway.proxy.circuit-breaker.enforce": True, |
262 | | -} |
263 | | - |
264 | | - |
265 | | -@control_silo_test(regions=[ApiGatewayTestCase.REGION]) |
266 | | -class ProxyCircuitBreakerTestCase(ApiGatewayTestCase): |
267 | | - def _make_breaker_mock(self, *, allow_request: bool) -> MagicMock: |
268 | | - mock_breaker = MagicMock() |
269 | | - mock_breaker.should_allow_request.return_value = allow_request |
270 | | - return mock_breaker |
271 | | - |
272 | | - @responses.activate |
273 | | - @override_options(CB_ENABLED) |
274 | | - def test_open_circuit_returns_503(self) -> None: |
275 | | - with patch("sentry.hybridcloud.apigateway.proxy.CircuitBreaker") as mock_breaker_class: |
276 | | - mock_breaker_class.return_value = self._make_breaker_mock(allow_request=False) |
277 | | - request = RequestFactory().get("http://sentry.io/get") |
278 | | - resp = proxy_request(request, self.organization.slug, url_name) |
279 | | - assert isinstance(resp, HttpResponse) |
280 | | - assert resp.status_code == 503 |
281 | | - assert json.loads(resp.content) == { |
282 | | - "error": "apigateway", |
283 | | - "detail": "Downstream service temporarily unavailable", |
284 | | - } |
285 | | - |
286 | | - @responses.activate |
287 | | - @override_options(CB_ENABLED) |
288 | | - def test_circuit_breaker_keyed_per_cell(self) -> None: |
289 | | - with patch("sentry.hybridcloud.apigateway.proxy.CircuitBreaker") as mock_breaker_class: |
290 | | - mock_breaker_class.return_value = self._make_breaker_mock(allow_request=False) |
291 | | - request = RequestFactory().get("http://sentry.io/get") |
292 | | - proxy_request(request, self.organization.slug, url_name) |
293 | | - key_used = mock_breaker_class.call_args[0][0] |
294 | | - assert key_used == f"apigateway.proxy.{self.REGION.name}" |
295 | | - |
296 | | - @responses.activate |
297 | | - def test_circuit_breaker_disabled_by_default(self) -> None: |
298 | | - with patch("sentry.hybridcloud.apigateway.proxy.CircuitBreaker") as mock_breaker_class: |
299 | | - request = RequestFactory().get("http://sentry.io/get") |
300 | | - proxy_request(request, self.organization.slug, url_name) |
301 | | - mock_breaker_class.assert_not_called() |
302 | | - |
303 | | - @responses.activate |
304 | | - @override_options( |
305 | | - { |
306 | | - "apigateway.proxy.circuit-breaker.enabled": True, |
307 | | - "apigateway.proxy.circuit-breaker.enforce": False, |
308 | | - } |
309 | | - ) |
310 | | - def test_open_circuit_not_enforced(self) -> None: |
311 | | - with patch("sentry.hybridcloud.apigateway.proxy.CircuitBreaker") as mock_breaker_class: |
312 | | - mock_breaker_class.return_value = self._make_breaker_mock(allow_request=False) |
313 | | - request = RequestFactory().get("http://sentry.io/get") |
314 | | - resp = proxy_request(request, self.organization.slug, url_name) |
315 | | - assert resp.status_code == 200 |
316 | | - |
317 | | - @responses.activate |
318 | | - @override_options({"apigateway.proxy.circuit-breaker.config": "invalid-lol", **CB_ENABLED}) |
319 | | - def test_handles_invalid_config(self) -> None: |
320 | | - request = RequestFactory().get("http://sentry.io/get") |
321 | | - res = proxy_request(request, self.organization.slug, url_name) |
322 | | - assert res.status_code == 200 |
323 | | - |
324 | | - @responses.activate |
325 | | - @override_options(CB_ENABLED) |
326 | | - def test_timeout_records_error(self) -> None: |
327 | | - responses.add( |
328 | | - responses.GET, |
329 | | - f"{self.REGION.address}/timeout", |
330 | | - body=Timeout(), |
331 | | - ) |
332 | | - with patch("sentry.hybridcloud.apigateway.proxy.CircuitBreaker") as mock_breaker_class: |
333 | | - mock_breaker = self._make_breaker_mock(allow_request=True) |
334 | | - mock_breaker_class.return_value = mock_breaker |
335 | | - request = RequestFactory().get("http://sentry.io/timeout") |
336 | | - with pytest.raises(RequestTimeout): |
337 | | - proxy_request(request, self.organization.slug, url_name) |
338 | | - mock_breaker.record_error.assert_called_once() |
339 | | - |
340 | | - @responses.activate |
341 | | - @override_options(CB_ENABLED) |
342 | | - def test_5xx_response_records_error(self) -> None: |
343 | | - responses.add( |
344 | | - responses.GET, |
345 | | - f"{self.REGION.address}/server-error", |
346 | | - status=500, |
347 | | - body=json.dumps({"detail": "internal server error"}), |
348 | | - content_type="application/json", |
349 | | - ) |
350 | | - with patch("sentry.hybridcloud.apigateway.proxy.CircuitBreaker") as mock_breaker_class: |
351 | | - mock_breaker = self._make_breaker_mock(allow_request=True) |
352 | | - mock_breaker_class.return_value = mock_breaker |
353 | | - request = RequestFactory().get("http://sentry.io/server-error") |
354 | | - resp = proxy_request(request, self.organization.slug, url_name) |
355 | | - assert resp.status_code == 500 |
356 | | - mock_breaker.record_error.assert_called_once() |
357 | | - |
358 | | - @responses.activate |
359 | | - @override_options(CB_ENABLED) |
360 | | - def test_4xx_response_does_not_record_error(self) -> None: |
361 | | - with patch("sentry.hybridcloud.apigateway.proxy.CircuitBreaker") as mock_breaker_class: |
362 | | - mock_breaker = self._make_breaker_mock(allow_request=True) |
363 | | - mock_breaker_class.return_value = mock_breaker |
364 | | - request = RequestFactory().get("http://sentry.io/error") |
365 | | - resp = proxy_request(request, self.organization.slug, url_name) |
366 | | - assert resp.status_code == 400 |
367 | | - mock_breaker.record_error.assert_not_called() |
368 | | - |
369 | | - @responses.activate |
370 | | - @override_options(CB_ENABLED) |
371 | | - def test_2xx_response_does_not_record_error(self) -> None: |
372 | | - with patch("sentry.hybridcloud.apigateway.proxy.CircuitBreaker") as mock_breaker_class: |
373 | | - mock_breaker = self._make_breaker_mock(allow_request=True) |
374 | | - mock_breaker_class.return_value = mock_breaker |
375 | | - request = RequestFactory().get("http://sentry.io/get") |
376 | | - resp = proxy_request(request, self.organization.slug, url_name) |
377 | | - assert resp.status_code == 200 |
378 | | - mock_breaker.record_error.assert_not_called() |
0 commit comments