From bbd3083a1536e61c8d14de96f76d66f7e27d52f2 Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Wed, 24 Jan 2024 12:34:10 -0600 Subject: [PATCH 1/8] Add tests for ResponseMetrics middleware --- privaterelay/tests/middleware_tests.py | 81 ++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 privaterelay/tests/middleware_tests.py diff --git a/privaterelay/tests/middleware_tests.py b/privaterelay/tests/middleware_tests.py new file mode 100644 index 0000000000..c2a0c6a87f --- /dev/null +++ b/privaterelay/tests/middleware_tests.py @@ -0,0 +1,81 @@ +"""Tests for Relay middlewares used in API and other server requests.""" + +from django.test import Client +from markus.testing import MetricsMock +from pytest_django.fixtures import SettingsWrapper +import pytest + + +@pytest.fixture +def response_metrics_settings(settings: SettingsWrapper) -> SettingsWrapper: + # Use some middleware in the declared order + settings.MIDDLEWARE = [ + "privaterelay.middleware.ResponseMetrics", + "privaterelay.middleware.RelayStaticFilesMiddleware", + "dockerflow.django.middleware.DockerflowMiddleware", + ] + settings.STATSD_ENABLED = True + return settings + + +def test_response_metrics_django_view( + client: Client, response_metrics_settings: SettingsWrapper +) -> None: + with MetricsMock() as mm: + response = client.get("/metrics-event") + assert response.status_code == 405 + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:405", "view:privaterelay.views.metrics_event", "method:GET"], + ) + + +def test_response_metrics_dockerflow_view( + client: Client, response_metrics_settings: SettingsWrapper +) -> None: + with MetricsMock() as mm: + response = client.get("/__lbheartbeat__") + assert response.status_code == 200 + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:", "method:GET"], + ) + + +@pytest.mark.django_db +def test_response_metrics_api_view( + client: Client, response_metrics_settings: SettingsWrapper +) -> None: + with MetricsMock() as mm: + response = client.get("/api/v1/runtime_data") + assert response.status_code == 200 + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:api.views.privaterelay.view", "method:GET"], + ) + + +@pytest.mark.django_db +def test_response_metrics_frontend_view( + client: Client, response_metrics_settings: SettingsWrapper +) -> None: + with MetricsMock() as mm: + response = client.get("/faq/") + assert response.status_code == 200 + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:", "method:GET"], + ) + + +@pytest.mark.django_db +def test_response_metrics_frontend_file( + client: Client, response_metrics_settings: SettingsWrapper +) -> None: + with MetricsMock() as mm: + response = client.get("/favicon.svg") + assert response.status_code == 200 + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:", "method:GET"], + ) From 5ccee5200a05f9e2fbb6cdeeea9e5e92cb5ed494 Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Wed, 24 Jan 2024 12:34:36 -0600 Subject: [PATCH 2/8] Disable response metrics at runtime --- privaterelay/middleware.py | 27 ++++++++++++----------- privaterelay/settings.py | 11 +--------- privaterelay/tests/middleware_tests.py | 18 +++++++++++++--- privaterelay/tests/views_tests.py | 30 ++++++++++++++++++++------ 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/privaterelay/middleware.py b/privaterelay/middleware.py index 02d2bf1a13..7203645945 100644 --- a/privaterelay/middleware.py +++ b/privaterelay/middleware.py @@ -1,7 +1,9 @@ import time +from collections.abc import Callable from datetime import UTC, datetime from django.conf import settings +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect import markus @@ -51,24 +53,18 @@ def __call__(self, request): return response -def _get_metric_view_name(request): - if request.resolver_match: - view = request.resolver_match.func - return f"{view.__module__}.{view.__name__}" - return "" - - class ResponseMetrics: - def __init__(self, get_response): + def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None: self.get_response = get_response - def __call__(self, request): + def __call__(self, request: HttpRequest) -> HttpResponse: + if not settings.STATSD_ENABLED: + return self.get_response(request) + start_time = time.time() response = self.get_response(request) delta = time.time() - start_time - - view_name = _get_metric_view_name(request) - + view_name = self._get_metric_view_name(request) metrics.timing( "response", value=delta * 1000.0, @@ -78,9 +74,14 @@ def __call__(self, request): f"method:{request.method}", ], ) - return response + def _get_metric_view_name(self, request: HttpRequest) -> str: + if request.resolver_match: + view = request.resolver_match.func + return f"{view.__module__}.{view.__name__}" + return "" + class StoreFirstVisit: def __init__(self, get_response): diff --git a/privaterelay/settings.py b/privaterelay/settings.py index 0ccb9e5766..e914afa0fc 100644 --- a/privaterelay/settings.py +++ b/privaterelay/settings.py @@ -349,16 +349,7 @@ ] -# statsd middleware has to be first to catch errors in everything else -def _get_initial_middleware() -> list[str]: - if STATSD_ENABLED: - return [ - "privaterelay.middleware.ResponseMetrics", - ] - return [] - - -MIDDLEWARE = _get_initial_middleware() +MIDDLEWARE = ["privaterelay.middleware.ResponseMetrics"] if USE_SILK: MIDDLEWARE.append("silk.middleware.SilkyMiddleware") diff --git a/privaterelay/tests/middleware_tests.py b/privaterelay/tests/middleware_tests.py index c2a0c6a87f..a2bf3a446b 100644 --- a/privaterelay/tests/middleware_tests.py +++ b/privaterelay/tests/middleware_tests.py @@ -1,19 +1,21 @@ """Tests for Relay middlewares used in API and other server requests.""" from django.test import Client + +import pytest from markus.testing import MetricsMock from pytest_django.fixtures import SettingsWrapper -import pytest @pytest.fixture def response_metrics_settings(settings: SettingsWrapper) -> SettingsWrapper: # Use some middleware in the declared order - settings.MIDDLEWARE = [ + use_middleware = { "privaterelay.middleware.ResponseMetrics", "privaterelay.middleware.RelayStaticFilesMiddleware", "dockerflow.django.middleware.DockerflowMiddleware", - ] + } + settings.MIDDLEWARE = [mw for mw in settings.MIDDLEWARE if mw in use_middleware] settings.STATSD_ENABLED = True return settings @@ -79,3 +81,13 @@ def test_response_metrics_frontend_file( "fx.private.relay.response", tags=["status:200", "view:", "method:GET"], ) + + +def test_response_metrics_disabled( + client: Client, response_metrics_settings: SettingsWrapper +) -> None: + response_metrics_settings.STATSD_ENABLED = False + with MetricsMock() as mm: + response = client.get("/metrics-event") + assert response.status_code == 405 + assert not mm.get_records() diff --git a/privaterelay/tests/views_tests.py b/privaterelay/tests/views_tests.py index 185c3c8caa..67614f82c4 100644 --- a/privaterelay/tests/views_tests.py +++ b/privaterelay/tests/views_tests.py @@ -376,7 +376,10 @@ def test_fxa_rp_events_password_change( with MetricsMock() as mm: response = client.get("/fxa-rp-events", HTTP_AUTHORIZATION=auth_header) - assert mm.get_records() == [] + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:privaterelay.views.fxa_rp_events", "method:GET"], + ) assert caplog.record_tuples == [ ("request.summary", logging.INFO, ""), ] @@ -403,7 +406,10 @@ def test_fxa_rp_events_password_change_slight_future_iat( with MetricsMock() as mm: response = client.get("/fxa-rp-events", HTTP_AUTHORIZATION=auth_header) - assert mm.get_records() == [] + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:privaterelay.views.fxa_rp_events", "method:GET"], + ) assert caplog.record_tuples == [ ("request.summary", logging.INFO, ""), ] @@ -434,7 +440,10 @@ def test_fxa_rp_events_password_change_far_future_iat( with MetricsMock() as mm, pytest.raises(jwt.ImmatureSignatureError): client.get("/fxa-rp-events", HTTP_AUTHORIZATION=auth_header) - assert mm.get_records() == [] + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:500", "view:privaterelay.views.fxa_rp_events", "method:GET"], + ) assert caplog.record_tuples == [ ("eventsinfo", logging.WARNING, "fxa_rp_event.future_iat"), ("request.summary", logging.ERROR, "The token is not yet valid (iat)"), @@ -462,7 +471,10 @@ def test_fxa_rp_events_profile_change( with MetricsMock() as mm: response = client.get("/fxa-rp-events", HTTP_AUTHORIZATION=auth_header) - assert mm.get_records() == [] + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:privaterelay.views.fxa_rp_events", "method:GET"], + ) assert caplog.record_tuples == [ ("eventsinfo", logging.INFO, "fxa_rp_event"), ("request.summary", logging.INFO, ""), @@ -500,7 +512,10 @@ def test_fxa_rp_events_subscription_change( with MetricsMock() as mm: response = client.get("/fxa-rp-events", HTTP_AUTHORIZATION=auth_header) - assert mm.get_records() == [] + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:privaterelay.views.fxa_rp_events", "method:GET"], + ) assert caplog.record_tuples == [ ("eventsinfo", logging.INFO, "fxa_rp_event"), ("request.summary", logging.INFO, ""), @@ -538,7 +553,10 @@ def test_fxa_rp_events_delete_user( with MetricsMock() as mm: response = client.get("/fxa-rp-events", HTTP_AUTHORIZATION=auth_header) - assert mm.get_records() == [] + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:privaterelay.views.fxa_rp_events", "method:GET"], + ) assert caplog.record_tuples == [ ("eventsinfo", logging.INFO, "fxa_rp_event"), ("request.summary", logging.INFO, ""), From 0647760b41bea8afec9d47532c667a8fd36ffd5e Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Wed, 29 May 2024 17:21:15 -0500 Subject: [PATCH 3/8] Handle dockerflow views --- privaterelay/middleware.py | 6 ++++++ privaterelay/tests/middleware_tests.py | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/privaterelay/middleware.py b/privaterelay/middleware.py index 7203645945..92d0e61f6f 100644 --- a/privaterelay/middleware.py +++ b/privaterelay/middleware.py @@ -1,3 +1,4 @@ +import re import time from collections.abc import Callable from datetime import UTC, datetime @@ -54,6 +55,9 @@ def __call__(self, request): class ResponseMetrics: + + re_dockerflow = re.compile(r"/__(version|heartbeat|lbheartbeat)__/?$") + def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None: self.get_response = get_response @@ -80,6 +84,8 @@ def _get_metric_view_name(self, request: HttpRequest) -> str: if request.resolver_match: view = request.resolver_match.func return f"{view.__module__}.{view.__name__}" + if match := self.re_dockerflow.match(request.path_info): + return f"dockerflow.django.views.{match[1]}" return "" diff --git a/privaterelay/tests/middleware_tests.py b/privaterelay/tests/middleware_tests.py index a2bf3a446b..a860293a0f 100644 --- a/privaterelay/tests/middleware_tests.py +++ b/privaterelay/tests/middleware_tests.py @@ -23,6 +23,7 @@ def response_metrics_settings(settings: SettingsWrapper) -> SettingsWrapper: def test_response_metrics_django_view( client: Client, response_metrics_settings: SettingsWrapper ) -> None: + """Django views emit the expected metrics.""" with MetricsMock() as mm: response = client.get("/metrics-event") assert response.status_code == 405 @@ -32,15 +33,23 @@ def test_response_metrics_django_view( ) +@pytest.mark.django_db +@pytest.mark.parametrize("viewname", ["version", "heartbeat", "lbheartbeat"]) def test_response_metrics_dockerflow_view( - client: Client, response_metrics_settings: SettingsWrapper + client: Client, response_metrics_settings: SettingsWrapper, viewname: str ) -> None: + """Dockerflow views are handled by the DockerflowMiddleware.""" with MetricsMock() as mm: - response = client.get("/__lbheartbeat__") - assert response.status_code == 200 + response = client.get(f"/__{viewname}__") + expected_status_code = 500 if viewname == "heartbeat" else 200 + assert response.status_code == expected_status_code mm.assert_timing_once( "fx.private.relay.response", - tags=["status:200", "view:", "method:GET"], + tags=[ + f"status:{expected_status_code}", + f"view:dockerflow.django.views.{viewname}", + "method:GET", + ], ) @@ -57,10 +66,10 @@ def test_response_metrics_api_view( ) -@pytest.mark.django_db -def test_response_metrics_frontend_view( +def test_response_metrics_frontend_path( client: Client, response_metrics_settings: SettingsWrapper ) -> None: + """Frontend views do not return through the ResponseMetrics middleware.""" with MetricsMock() as mm: response = client.get("/faq/") assert response.status_code == 200 @@ -86,6 +95,7 @@ def test_response_metrics_frontend_file( def test_response_metrics_disabled( client: Client, response_metrics_settings: SettingsWrapper ) -> None: + """ResponseMetrics does not emit metrics when metrics are disabled.""" response_metrics_settings.STATSD_ENABLED = False with MetricsMock() as mm: response = client.get("/metrics-event") From 3579f6618461b33e8f2b3b8e6969bdae7f69ca4b Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Wed, 29 May 2024 17:33:01 -0500 Subject: [PATCH 4/8] Better names for @api_view functional views --- privaterelay/middleware.py | 3 +++ privaterelay/tests/middleware_tests.py | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/privaterelay/middleware.py b/privaterelay/middleware.py index 92d0e61f6f..f2ceefa1d8 100644 --- a/privaterelay/middleware.py +++ b/privaterelay/middleware.py @@ -83,6 +83,9 @@ def __call__(self, request: HttpRequest) -> HttpResponse: def _get_metric_view_name(self, request: HttpRequest) -> str: if request.resolver_match: view = request.resolver_match.func + if hasattr(view, "view_class"): + # Wrapped with rest_framework.decorators.api_view + return f"{view.__module__}.{view.view_class.__name__}" return f"{view.__module__}.{view.__name__}" if match := self.re_dockerflow.match(request.path_info): return f"dockerflow.django.views.{match[1]}" diff --git a/privaterelay/tests/middleware_tests.py b/privaterelay/tests/middleware_tests.py index a860293a0f..88aa65dd85 100644 --- a/privaterelay/tests/middleware_tests.py +++ b/privaterelay/tests/middleware_tests.py @@ -53,6 +53,19 @@ def test_response_metrics_dockerflow_view( ) +@pytest.mark.django_db +def test_response_metrics_api_viewset( + client: Client, response_metrics_settings: SettingsWrapper +) -> None: + with MetricsMock() as mm: + response = client.get("/api/v1/users/") + assert response.status_code == 401 + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:401", "view:api.views.privaterelay.UserViewSet", "method:GET"], + ) + + @pytest.mark.django_db def test_response_metrics_api_view( client: Client, response_metrics_settings: SettingsWrapper @@ -62,7 +75,7 @@ def test_response_metrics_api_view( assert response.status_code == 200 mm.assert_timing_once( "fx.private.relay.response", - tags=["status:200", "view:api.views.privaterelay.view", "method:GET"], + tags=["status:200", "view:api.views.privaterelay.runtime_data", "method:GET"], ) From 95ffb36b61a0ce706d315076db77db74e5af32fe Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Thu, 30 May 2024 09:11:25 -0500 Subject: [PATCH 5/8] Identify static files --- privaterelay/middleware.py | 16 ++++++++++++++++ privaterelay/tests/middleware_tests.py | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/privaterelay/middleware.py b/privaterelay/middleware.py index f2ceefa1d8..44743e7a27 100644 --- a/privaterelay/middleware.py +++ b/privaterelay/middleware.py @@ -60,6 +60,7 @@ class ResponseMetrics: def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None: self.get_response = get_response + self.middleware = RelayStaticFilesMiddleware() def __call__(self, request: HttpRequest) -> HttpResponse: if not settings.STATSD_ENABLED: @@ -89,6 +90,8 @@ def _get_metric_view_name(self, request: HttpRequest) -> str: return f"{view.__module__}.{view.__name__}" if match := self.re_dockerflow.match(request.path_info): return f"dockerflow.django.views.{match[1]}" + if self.middleware.is_staticfile(request.path_info): + return "" return "" @@ -130,3 +133,16 @@ def immutable_file_test(self, path, url): return True else: return super().immutable_file_test(path, url) + + def is_staticfile(self, path_info: str) -> bool: + """ + Returns True if this file is served by the middleware. + + This uses the logic from whitenoise.middleware.WhiteNoiseMiddleware.__call__: + https://github.com/evansd/whitenoise/blob/220a98894495d407424e80d85d49227a5cf97e1b/src/whitenoise/middleware.py#L117-L124 + """ + if self.autorefresh: + static_file = self.find_file(path_info) + else: + static_file = self.files.get(path_info) + return static_file is not None diff --git a/privaterelay/tests/middleware_tests.py b/privaterelay/tests/middleware_tests.py index 88aa65dd85..bd2f87300c 100644 --- a/privaterelay/tests/middleware_tests.py +++ b/privaterelay/tests/middleware_tests.py @@ -88,7 +88,7 @@ def test_response_metrics_frontend_path( assert response.status_code == 200 mm.assert_timing_once( "fx.private.relay.response", - tags=["status:200", "view:", "method:GET"], + tags=["status:200", "view:", "method:GET"], ) @@ -101,7 +101,7 @@ def test_response_metrics_frontend_file( assert response.status_code == 200 mm.assert_timing_once( "fx.private.relay.response", - tags=["status:200", "view:", "method:GET"], + tags=["status:200", "view:", "method:GET"], ) From 134bc9066107de10c28ec982f69793166d586fe2 Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Thu, 30 May 2024 10:42:09 -0500 Subject: [PATCH 6/8] Split heartbeat into separate test --- privaterelay/tests/middleware_tests.py | 29 +++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/privaterelay/tests/middleware_tests.py b/privaterelay/tests/middleware_tests.py index bd2f87300c..27596988b6 100644 --- a/privaterelay/tests/middleware_tests.py +++ b/privaterelay/tests/middleware_tests.py @@ -34,19 +34,38 @@ def test_response_metrics_django_view( @pytest.mark.django_db -@pytest.mark.parametrize("viewname", ["version", "heartbeat", "lbheartbeat"]) -def test_response_metrics_dockerflow_view( +def test_response_metrics_dockerflow_heartbeat( + client: Client, response_metrics_settings: SettingsWrapper +) -> None: + """Dockerflow views are handled by the DockerflowMiddleware.""" + with MetricsMock() as mm: + response = client.get("/__heartbeat__") + # __heartbeat__ runs some security and connection tests + # In some environments, a critical error results in a 500 + # In others, a warning or better results in a 200 + assert response.status_code in [200, 500] + mm.assert_timing_once( + "fx.private.relay.response", + tags=[ + f"status:{response.status_code}", + "view:dockerflow.django.views.heartbeat", + "method:GET", + ], + ) + + +@pytest.mark.parametrize("viewname", ["version", "lbheartbeat"]) +def test_response_metrics_other_dockerflow_view( client: Client, response_metrics_settings: SettingsWrapper, viewname: str ) -> None: """Dockerflow views are handled by the DockerflowMiddleware.""" with MetricsMock() as mm: response = client.get(f"/__{viewname}__") - expected_status_code = 500 if viewname == "heartbeat" else 200 - assert response.status_code == expected_status_code + assert response.status_code == 200 mm.assert_timing_once( "fx.private.relay.response", tags=[ - f"status:{expected_status_code}", + "status:200", f"view:dockerflow.django.views.{viewname}", "method:GET", ], From de59cc2814981021294a994aad07c921e963bd01 Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Thu, 30 May 2024 10:45:23 -0500 Subject: [PATCH 7/8] Put heartbeat in own test, fixup docstrings --- privaterelay/tests/middleware_tests.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/privaterelay/tests/middleware_tests.py b/privaterelay/tests/middleware_tests.py index 27596988b6..4e590fe6a7 100644 --- a/privaterelay/tests/middleware_tests.py +++ b/privaterelay/tests/middleware_tests.py @@ -9,6 +9,7 @@ @pytest.fixture def response_metrics_settings(settings: SettingsWrapper) -> SettingsWrapper: + """Setup settings for ResponseMetrics tests.""" # Use some middleware in the declared order use_middleware = { "privaterelay.middleware.ResponseMetrics", @@ -23,7 +24,7 @@ def response_metrics_settings(settings: SettingsWrapper) -> SettingsWrapper: def test_response_metrics_django_view( client: Client, response_metrics_settings: SettingsWrapper ) -> None: - """Django views emit the expected metrics.""" + """Django views emit the expected metric.""" with MetricsMock() as mm: response = client.get("/metrics-event") assert response.status_code == 405 @@ -37,7 +38,7 @@ def test_response_metrics_django_view( def test_response_metrics_dockerflow_heartbeat( client: Client, response_metrics_settings: SettingsWrapper ) -> None: - """Dockerflow views are handled by the DockerflowMiddleware.""" + """The Dockerflow __heartbeat__ endpoint emits the expected metric.""" with MetricsMock() as mm: response = client.get("/__heartbeat__") # __heartbeat__ runs some security and connection tests @@ -58,7 +59,7 @@ def test_response_metrics_dockerflow_heartbeat( def test_response_metrics_other_dockerflow_view( client: Client, response_metrics_settings: SettingsWrapper, viewname: str ) -> None: - """Dockerflow views are handled by the DockerflowMiddleware.""" + """The other Dockerflow views emit the expected metrics.""" with MetricsMock() as mm: response = client.get(f"/__{viewname}__") assert response.status_code == 200 @@ -76,6 +77,7 @@ def test_response_metrics_other_dockerflow_view( def test_response_metrics_api_viewset( client: Client, response_metrics_settings: SettingsWrapper ) -> None: + """API viewsets emit the expected metrics.""" with MetricsMock() as mm: response = client.get("/api/v1/users/") assert response.status_code == 401 @@ -89,6 +91,7 @@ def test_response_metrics_api_viewset( def test_response_metrics_api_view( client: Client, response_metrics_settings: SettingsWrapper ) -> None: + """API functions wrapped in @api_view emit the expected metrics.""" with MetricsMock() as mm: response = client.get("/api/v1/runtime_data") assert response.status_code == 200 @@ -101,7 +104,7 @@ def test_response_metrics_api_view( def test_response_metrics_frontend_path( client: Client, response_metrics_settings: SettingsWrapper ) -> None: - """Frontend views do not return through the ResponseMetrics middleware.""" + """Frontend views emit the expected metrics.""" with MetricsMock() as mm: response = client.get("/faq/") assert response.status_code == 200 @@ -115,6 +118,7 @@ def test_response_metrics_frontend_path( def test_response_metrics_frontend_file( client: Client, response_metrics_settings: SettingsWrapper ) -> None: + """Frontend files emit the expected metrics.""" with MetricsMock() as mm: response = client.get("/favicon.svg") assert response.status_code == 200 From 480236ed2f6b96175104630d83d430413717aa67 Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Thu, 30 May 2024 10:50:13 -0500 Subject: [PATCH 8/8] Be flexible about staticfile tests --- privaterelay/tests/middleware_tests.py | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/privaterelay/tests/middleware_tests.py b/privaterelay/tests/middleware_tests.py index 4e590fe6a7..66e4992027 100644 --- a/privaterelay/tests/middleware_tests.py +++ b/privaterelay/tests/middleware_tests.py @@ -107,11 +107,15 @@ def test_response_metrics_frontend_path( """Frontend views emit the expected metrics.""" with MetricsMock() as mm: response = client.get("/faq/") - assert response.status_code == 200 - mm.assert_timing_once( - "fx.private.relay.response", - tags=["status:200", "view:", "method:GET"], - ) + # The file is found if the frontend build was done, the DEBUG setting, + # if files were collected, etc. + assert response.status_code in [200, 404] + # Metrics are only emitted if found. + if response.status_code == 200: + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:", "method:GET"], + ) @pytest.mark.django_db @@ -121,11 +125,15 @@ def test_response_metrics_frontend_file( """Frontend files emit the expected metrics.""" with MetricsMock() as mm: response = client.get("/favicon.svg") - assert response.status_code == 200 - mm.assert_timing_once( - "fx.private.relay.response", - tags=["status:200", "view:", "method:GET"], - ) + # The file is found if the frontend build was done, the DEBUG setting, + # if files were collected, etc. + assert response.status_code in [200, 404] + # Metrics are only emitted if found. + if response.status_code == 200: + mm.assert_timing_once( + "fx.private.relay.response", + tags=["status:200", "view:", "method:GET"], + ) def test_response_metrics_disabled(