From cf4c4392776fe604e7036e618b933cf6767dde61 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 15 Aug 2026 16:03:47 +0530 Subject: [PATCH 1/6] feat: wire up the edge_proxy private app Detects the edge_proxy app and mounts its two URL trees: proxy key management for organisation admins, and the environment inventory that Edge Proxy instances poll. The feature is for private cloud only. SaaS images ship the private wheel, so the mount is gated on is_saas() at runtime rather than on the app being absent; the app's own views carry a matching permission as defence in depth. --- api/app/settings/common.py | 4 ++++ api/app/urls.py | 11 +++++++++++ api/pyproject.toml | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/api/app/settings/common.py b/api/app/settings/common.py index 219e6aba9437..ad7434644c62 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -1160,6 +1160,10 @@ "USER_FILTER_PARSER": "scim.filters.UserFilterQuery", } +EDGE_PROXY_INSTALLED = importlib.util.find_spec("edge_proxy") is not None +if EDGE_PROXY_INSTALLED: + INSTALLED_APPS.append("edge_proxy") + DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # Used to keep edge identities in sync by forwarding the http requests diff --git a/api/app/urls.py b/api/app/urls.py index 1b45a36d8732..5be04f462455 100644 --- a/api/app/urls.py +++ b/api/app/urls.py @@ -1,6 +1,7 @@ import importlib from common.core.urls import urlpatterns as core_urlpatterns +from common.core.utils import is_saas from django.conf import settings from django.contrib import admin from django.urls import include, path, re_path @@ -124,6 +125,16 @@ ), ] +# SaaS images ship the private wheel, so is_saas() is the gate, not module presence. +if settings.EDGE_PROXY_INSTALLED and not is_saas(): # pragma: no cover + urlpatterns += [ + path( + "api/v1/organisations//edge-proxy/", + include("edge_proxy.management_urls"), + ), + path("api/v1/proxy/", include("edge_proxy.urls")), + ] + if settings.WORKFLOWS_LOGIC_INSTALLED: # pragma: no cover workflow_views = importlib.import_module("workflows_logic.views") urlpatterns += [ diff --git a/api/pyproject.toml b/api/pyproject.toml index aa4ebcd30818..b6530ccca85a 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -242,6 +242,10 @@ ignore_missing_imports = true module = ["rbac.*"] ignore_missing_imports = true +[[tool.mypy.overrides]] +module = ["edge_proxy.*"] +ignore_missing_imports = true + [[tool.mypy.overrides]] module = ["saml.*"] ignore_missing_imports = true From 4330617bd59ad7b423b90749e768ecb4260e79d1 Mon Sep 17 00:00:00 2001 From: "flagsmith-engineering[bot]" Date: Sat, 29 Aug 2026 05:23:53 +0000 Subject: [PATCH 2/6] chore: Update documentation artefacts --- openapi.yaml | 311 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index 78578f6e6a84..1853cdc33f36 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -9819,6 +9819,179 @@ paths: - Master API Key: [] tags: - Audit + '/api/v1/organisations/{organisation_pk}/edge-proxy/keys/': + get: + operationId: api_v1_organisations_edge_proxy_keys_list + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + post: + operationId: api_v1_organisations_edge_proxy_keys_create + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProxyKey' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProxyKey' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + '/api/v1/organisations/{organisation_pk}/edge-proxy/keys/{prefix}/': + get: + operationId: api_v1_organisations_edge_proxy_keys_retrieve + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + - name: prefix + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + put: + operationId: api_v1_organisations_edge_proxy_keys_update + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + - name: prefix + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProxyKey' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProxyKey' + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + patch: + operationId: api_v1_organisations_edge_proxy_keys_partial_update + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + - name: prefix + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedProxyKey' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedProxyKey' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedProxyKey' + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + delete: + operationId: api_v1_organisations_edge_proxy_keys_destroy + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + - name: prefix + in: path + required: true + schema: + type: string + responses: + '204': + description: No response body + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations '/api/v1/organisations/{organisation_pk}/github/create-cleanup-issue/': post: operationId: api_v1_organisations_github_create_cleanup_issue_create @@ -18644,6 +18817,20 @@ paths: - Master API Key: [] tags: - Projects + /api/v1/proxy/config/: + get: + operationId: api_v1_proxy_config_list + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EdgeProxyEnvironment' + tags: + - Other '/api/v1/segments/get-by-uuid/{uuid}/': get: operationId: api_v1_segments_get_by_uuid_retrieve @@ -20542,6 +20729,48 @@ components: required: - multivariate_feature_option - percentage_allocation + EdgeProxyEnvironment: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 2000 + client_side_key: + type: string + server_side_keys: + type: array + items: + $ref: '#/components/schemas/EdgeProxyServerSideKey' + updated_at: + description: 'Tracks changes to self and related entities, e.g. FeatureStates.' + type: string + format: date-time + project_id: + type: integer + readOnly: true + organisation_id: + type: integer + readOnly: true + required: + - client_side_key + - name + - server_side_keys + EdgeProxyServerSideKey: + type: object + properties: + key: + type: string + maxLength: 100 + active: + type: boolean + expires_at: + type: + - string + - 'null' + format: date-time EdgeV2MigrationStatusEnum: description: |- * `NOT_STARTED` - Not Started @@ -25549,6 +25778,47 @@ components: enforce_feature_owners: description: Require at least one user or group owner when creating a feature. type: boolean + PatchedProxyKey: + type: object + properties: + prefix: + type: string + readOnly: true + created: + type: string + format: date-time + readOnly: true + name: + description: A free-form name for the API key. Need not be unique. 50 characters max. + type: string + maxLength: 50 + revoked: + description: 'If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)' + type: boolean + expiry_date: + description: 'Once API key expires, clients cannot use it anymore.' + type: + - string + - 'null' + format: date-time + title: Expires + key: + description: 'Only the key hash is stored, so the full key is returned on create and is unavailable from every other endpoint.' + type: string + readOnly: true + has_expired: + type: boolean + readOnly: true + created_by_user: + type: + - integer + - 'null' + readOnly: true + created_by_master_api_key: + type: + - string + - 'null' + readOnly: true PatchedReleasePipeline: type: object properties: @@ -27080,6 +27350,47 @@ components: type: boolean required: - name + ProxyKey: + type: object + properties: + prefix: + type: string + readOnly: true + created: + type: string + format: date-time + readOnly: true + name: + description: A free-form name for the API key. Need not be unique. 50 characters max. + type: string + maxLength: 50 + revoked: + description: 'If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)' + type: boolean + expiry_date: + description: 'Once API key expires, clients cannot use it anymore.' + type: + - string + - 'null' + format: date-time + title: Expires + key: + description: 'Only the key hash is stored, so the full key is returned on create and is unavailable from every other endpoint.' + type: string + readOnly: true + has_expired: + type: boolean + readOnly: true + created_by_user: + type: + - integer + - 'null' + readOnly: true + created_by_master_api_key: + type: + - string + - 'null' + readOnly: true ReleasePipeline: type: object properties: From e71dcce33d5721ca6d746730e54bac4c6c684f61 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 29 Aug 2026 12:07:39 +0530 Subject: [PATCH 3/6] feat: exclude verified Edge Proxy requests from API usage tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Edge Proxy sends X-Proxy-Key on its environment-document fetches and reports the requests it serves through its own usage endpoint, so counting its fetches here would double-count. The private edge_proxy app's is_edge_proxy_request() decides the exemption — it verifies the key and that its grants cover the presented environment, so a spoofed header never dodges metering. Wired only on non-SaaS deployments with the edge_proxy app installed; requires the flagsmith-private release that ships is_edge_proxy_request (bump the lock before merge). --- api/app_analytics/middleware.py | 17 +++- .../unit/app_analytics/test_middleware.py | 98 +++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/api/app_analytics/middleware.py b/api/app_analytics/middleware.py index 99a072747321..fa2e6909677f 100644 --- a/api/app_analytics/middleware.py +++ b/api/app_analytics/middleware.py @@ -1,5 +1,7 @@ from typing import Callable +from common.core.utils import is_saas +from django.conf import settings from django.http import HttpRequest, HttpResponse from app_analytics.mappers import map_request_to_labels @@ -30,9 +32,22 @@ def __init__( get_response: Callable[[HttpRequest], HttpResponse], ) -> None: self.get_response = get_response + # An Edge Proxy reports the requests it serves itself, so its own + # requests to core must not also be counted here. The private + # edge_proxy app decides what counts as the proxy's own: a verified + # X-Proxy-Key whose grants cover the presented environment — bare + # header presence is never trusted. + self.is_edge_proxy_request: Callable[[HttpRequest], bool] | None = None + if settings.EDGE_PROXY_INSTALLED and not is_saas(): + from edge_proxy.authentication import is_edge_proxy_request + + self.is_edge_proxy_request = is_edge_proxy_request def __call__(self, request: HttpRequest) -> HttpResponse: - if environment_key := request.headers.get("X-Environment-Key"): + if (environment_key := request.headers.get("X-Environment-Key")) and not ( + self.is_edge_proxy_request is not None + and self.is_edge_proxy_request(request) + ): track_usage_by_resource_host_and_environment( resource=get_resource_from_uri(request.path), host=request.get_host(), diff --git a/api/tests/unit/app_analytics/test_middleware.py b/api/tests/unit/app_analytics/test_middleware.py index 165a679ad587..5a3f0c5ec593 100644 --- a/api/tests/unit/app_analytics/test_middleware.py +++ b/api/tests/unit/app_analytics/test_middleware.py @@ -1,3 +1,5 @@ +import sys + import pytest from django.test import RequestFactory from pytest_django.fixtures import SettingsWrapper @@ -8,6 +10,13 @@ from tests.types import EnableFeaturesFixture +@pytest.fixture(autouse=True) +def edge_proxy_not_installed(settings: SettingsWrapper) -> None: + # Keep these tests hermetic: whether the private edge_proxy wheel is + # installed in the test environment must not change middleware wiring. + settings.EDGE_PROXY_INSTALLED = False + + @pytest.mark.parametrize( "path, resource_name", [ @@ -131,3 +140,92 @@ def test_api_usage_middleware__request_not_tracked__not_calls_expected( # Then mocked_track_request.delay.assert_not_called() + + +@pytest.mark.parametrize( + "edge_proxy_installed, saas, expect_wired", + [ + (True, False, True), + (True, True, False), + (False, False, False), + ], +) +def test_api_usage_middleware__edge_proxy_check__wired_only_where_expected( + mocker: MockerFixture, + settings: SettingsWrapper, + edge_proxy_installed: bool, + saas: bool, + expect_wired: bool, +) -> None: + # Given a deployment with/without the private edge_proxy app + settings.EDGE_PROXY_INSTALLED = edge_proxy_installed + mocker.patch("app_analytics.middleware.is_saas", return_value=saas) + is_edge_proxy_request = mocker.MagicMock() + mocker.patch.dict( + sys.modules, + { + "edge_proxy": mocker.MagicMock(), + "edge_proxy.authentication": mocker.MagicMock( + is_edge_proxy_request=is_edge_proxy_request + ), + }, + ) + + # When + middleware = APIUsageMiddleware(mocker.MagicMock()) + + # Then the verifier is wired only where the proxy reports usage + # itself: a non-SaaS deployment with the edge_proxy app installed + assert middleware.is_edge_proxy_request is ( + is_edge_proxy_request if expect_wired else None + ) + + +@pytest.mark.parametrize("is_verified_proxy_request", [True, False]) +def test_api_usage_middleware__edge_proxy_check_wired__tracks_unverified_only( + rf: RequestFactory, + mocker: MockerFixture, + settings: SettingsWrapper, + is_verified_proxy_request: bool, +) -> None: + # Given a request bearing proxy headers the edge_proxy app does or + # does not verify + headers = {"HTTP_X-Environment-Key": "test", "HTTP_X-Proxy-Key": "pk.key"} + request = rf.get("/api/v1/environment-document", **headers) # type: ignore[arg-type] + settings.EDGE_PROXY_INSTALLED = False + mocked_track_usage = mocker.patch( + "app_analytics.middleware.track_usage_by_resource_host_and_environment" + ) + middleware = APIUsageMiddleware(mocker.MagicMock()) + middleware.is_edge_proxy_request = mocker.MagicMock( + return_value=is_verified_proxy_request + ) + + # When + middleware(request) + + # Then only a verified proxy request is exempt — a spoofed header is not + middleware.is_edge_proxy_request.assert_called_once_with(request) + assert mocked_track_usage.called is not is_verified_proxy_request + + +def test_api_usage_middleware__edge_proxy_check_not_wired__proxy_header_still_tracked( + rf: RequestFactory, + mocker: MockerFixture, + settings: SettingsWrapper, +) -> None: + # Given a request bearing an X-Proxy-Key on a deployment with no + # edge_proxy app to verify it + headers = {"HTTP_X-Environment-Key": "test", "HTTP_X-Proxy-Key": "pk.key"} + request = rf.get("/api/v1/environment-document", **headers) # type: ignore[arg-type] + settings.EDGE_PROXY_INSTALLED = False + mocked_track_usage = mocker.patch( + "app_analytics.middleware.track_usage_by_resource_host_and_environment" + ) + + # When + middleware = APIUsageMiddleware(mocker.MagicMock()) + middleware(request) + + # Then + mocked_track_usage.assert_called_once() From b0a2a51ad3cf0166fdb03765e4d1c4c110c03c15 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 29 Aug 2026 15:51:07 +0530 Subject: [PATCH 4/6] fix: assert on the mock binding, not the middleware attribute --- api/tests/unit/app_analytics/test_middleware.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/api/tests/unit/app_analytics/test_middleware.py b/api/tests/unit/app_analytics/test_middleware.py index 5a3f0c5ec593..ad96425571c5 100644 --- a/api/tests/unit/app_analytics/test_middleware.py +++ b/api/tests/unit/app_analytics/test_middleware.py @@ -196,16 +196,15 @@ def test_api_usage_middleware__edge_proxy_check_wired__tracks_unverified_only( mocked_track_usage = mocker.patch( "app_analytics.middleware.track_usage_by_resource_host_and_environment" ) + is_edge_proxy_request = mocker.MagicMock(return_value=is_verified_proxy_request) middleware = APIUsageMiddleware(mocker.MagicMock()) - middleware.is_edge_proxy_request = mocker.MagicMock( - return_value=is_verified_proxy_request - ) + middleware.is_edge_proxy_request = is_edge_proxy_request # When middleware(request) # Then only a verified proxy request is exempt — a spoofed header is not - middleware.is_edge_proxy_request.assert_called_once_with(request) + is_edge_proxy_request.assert_called_once_with(request) assert mocked_track_usage.called is not is_verified_proxy_request From 93225c5d46b37ffb6e81a51c6b5b3676f0b267ef Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 29 Aug 2026 16:02:52 +0530 Subject: [PATCH 5/6] fix: tolerate an edge_proxy app that predates is_edge_proxy_request Core's lock pins a flagsmith-private wheel that does not ship the helper yet, so a hard import broke every request in CI. Feature-detect instead: on an older wheel the proxy's fetches are simply counted as before, and the exclusion activates when the pin catches up. --- api/app_analytics/middleware.py | 12 +++++++--- .../unit/app_analytics/test_middleware.py | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/api/app_analytics/middleware.py b/api/app_analytics/middleware.py index fa2e6909677f..076680d6e9c2 100644 --- a/api/app_analytics/middleware.py +++ b/api/app_analytics/middleware.py @@ -1,3 +1,4 @@ +import importlib from typing import Callable from common.core.utils import is_saas @@ -39,9 +40,14 @@ def __init__( # header presence is never trusted. self.is_edge_proxy_request: Callable[[HttpRequest], bool] | None = None if settings.EDGE_PROXY_INSTALLED and not is_saas(): - from edge_proxy.authentication import is_edge_proxy_request - - self.is_edge_proxy_request = is_edge_proxy_request + # getattr, not a hard import: the installed edge_proxy app may + # predate the helper — the proxy's fetches are then counted as + # before. + self.is_edge_proxy_request = getattr( + importlib.import_module("edge_proxy.authentication"), + "is_edge_proxy_request", + None, + ) def __call__(self, request: HttpRequest) -> HttpResponse: if (environment_key := request.headers.get("X-Environment-Key")) and not ( diff --git a/api/tests/unit/app_analytics/test_middleware.py b/api/tests/unit/app_analytics/test_middleware.py index ad96425571c5..75ee3b0067e9 100644 --- a/api/tests/unit/app_analytics/test_middleware.py +++ b/api/tests/unit/app_analytics/test_middleware.py @@ -181,6 +181,28 @@ def test_api_usage_middleware__edge_proxy_check__wired_only_where_expected( ) +def test_api_usage_middleware__edge_proxy_app_predates_helper__not_wired( + mocker: MockerFixture, + settings: SettingsWrapper, +) -> None: + # Given an installed edge_proxy app without is_edge_proxy_request + settings.EDGE_PROXY_INSTALLED = True + mocker.patch("app_analytics.middleware.is_saas", return_value=False) + mocker.patch.dict( + sys.modules, + { + "edge_proxy": mocker.MagicMock(), + "edge_proxy.authentication": mocker.MagicMock(spec=[]), + }, + ) + + # When + middleware = APIUsageMiddleware(mocker.MagicMock()) + + # Then requests are counted as before + assert middleware.is_edge_proxy_request is None + + @pytest.mark.parametrize("is_verified_proxy_request", [True, False]) def test_api_usage_middleware__edge_proxy_check_wired__tracks_unverified_only( rf: RequestFactory, From b2b876ec4af00fdb09ffae16f4e2eb757aca4400 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 5 Sep 2026 07:35:55 +0530 Subject: [PATCH 6/6] refactor: rename to is_valid_edge_proxy_request Follows the rename in the edge_proxy app; the getattr feature detection keeps an older wheel degrading to counting as before. --- api/app_analytics/middleware.py | 10 +++++----- .../unit/app_analytics/test_middleware.py | 20 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/api/app_analytics/middleware.py b/api/app_analytics/middleware.py index 076680d6e9c2..77b23e0ebee8 100644 --- a/api/app_analytics/middleware.py +++ b/api/app_analytics/middleware.py @@ -38,21 +38,21 @@ def __init__( # edge_proxy app decides what counts as the proxy's own: a verified # X-Proxy-Key whose grants cover the presented environment — bare # header presence is never trusted. - self.is_edge_proxy_request: Callable[[HttpRequest], bool] | None = None + self.is_valid_edge_proxy_request: Callable[[HttpRequest], bool] | None = None if settings.EDGE_PROXY_INSTALLED and not is_saas(): # getattr, not a hard import: the installed edge_proxy app may # predate the helper — the proxy's fetches are then counted as # before. - self.is_edge_proxy_request = getattr( + self.is_valid_edge_proxy_request = getattr( importlib.import_module("edge_proxy.authentication"), - "is_edge_proxy_request", + "is_valid_edge_proxy_request", None, ) def __call__(self, request: HttpRequest) -> HttpResponse: if (environment_key := request.headers.get("X-Environment-Key")) and not ( - self.is_edge_proxy_request is not None - and self.is_edge_proxy_request(request) + self.is_valid_edge_proxy_request is not None + and self.is_valid_edge_proxy_request(request) ): track_usage_by_resource_host_and_environment( resource=get_resource_from_uri(request.path), diff --git a/api/tests/unit/app_analytics/test_middleware.py b/api/tests/unit/app_analytics/test_middleware.py index 75ee3b0067e9..08534ec1ebb7 100644 --- a/api/tests/unit/app_analytics/test_middleware.py +++ b/api/tests/unit/app_analytics/test_middleware.py @@ -160,13 +160,13 @@ def test_api_usage_middleware__edge_proxy_check__wired_only_where_expected( # Given a deployment with/without the private edge_proxy app settings.EDGE_PROXY_INSTALLED = edge_proxy_installed mocker.patch("app_analytics.middleware.is_saas", return_value=saas) - is_edge_proxy_request = mocker.MagicMock() + is_valid_edge_proxy_request = mocker.MagicMock() mocker.patch.dict( sys.modules, { "edge_proxy": mocker.MagicMock(), "edge_proxy.authentication": mocker.MagicMock( - is_edge_proxy_request=is_edge_proxy_request + is_valid_edge_proxy_request=is_valid_edge_proxy_request ), }, ) @@ -176,8 +176,8 @@ def test_api_usage_middleware__edge_proxy_check__wired_only_where_expected( # Then the verifier is wired only where the proxy reports usage # itself: a non-SaaS deployment with the edge_proxy app installed - assert middleware.is_edge_proxy_request is ( - is_edge_proxy_request if expect_wired else None + assert middleware.is_valid_edge_proxy_request is ( + is_valid_edge_proxy_request if expect_wired else None ) @@ -185,7 +185,7 @@ def test_api_usage_middleware__edge_proxy_app_predates_helper__not_wired( mocker: MockerFixture, settings: SettingsWrapper, ) -> None: - # Given an installed edge_proxy app without is_edge_proxy_request + # Given an installed edge_proxy app without is_valid_edge_proxy_request settings.EDGE_PROXY_INSTALLED = True mocker.patch("app_analytics.middleware.is_saas", return_value=False) mocker.patch.dict( @@ -200,7 +200,7 @@ def test_api_usage_middleware__edge_proxy_app_predates_helper__not_wired( middleware = APIUsageMiddleware(mocker.MagicMock()) # Then requests are counted as before - assert middleware.is_edge_proxy_request is None + assert middleware.is_valid_edge_proxy_request is None @pytest.mark.parametrize("is_verified_proxy_request", [True, False]) @@ -218,15 +218,17 @@ def test_api_usage_middleware__edge_proxy_check_wired__tracks_unverified_only( mocked_track_usage = mocker.patch( "app_analytics.middleware.track_usage_by_resource_host_and_environment" ) - is_edge_proxy_request = mocker.MagicMock(return_value=is_verified_proxy_request) + is_valid_edge_proxy_request = mocker.MagicMock( + return_value=is_verified_proxy_request + ) middleware = APIUsageMiddleware(mocker.MagicMock()) - middleware.is_edge_proxy_request = is_edge_proxy_request + middleware.is_valid_edge_proxy_request = is_valid_edge_proxy_request # When middleware(request) # Then only a verified proxy request is exempt — a spoofed header is not - is_edge_proxy_request.assert_called_once_with(request) + is_valid_edge_proxy_request.assert_called_once_with(request) assert mocked_track_usage.called is not is_verified_proxy_request