From e23d236614037eb8da96527d4f6afab43bd188e5 Mon Sep 17 00:00:00 2001 From: Quentin BEY Date: Wed, 2 Apr 2025 16:58:31 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=85(pytest)=20fail=20on=20tests=20externa?= =?UTF-8?q?l=20calls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend tests must not try to call the real world. --- src/backend/conftest.py | 30 +++++++++ .../test_api_team_accesses_delete.py | 63 +++++++++---------- .../core/tests/test_models_team_accesses.py | 56 ++++++++--------- 3 files changed, 83 insertions(+), 66 deletions(-) create mode 100644 src/backend/conftest.py diff --git a/src/backend/conftest.py b/src/backend/conftest.py new file mode 100644 index 0000000..2d03362 --- /dev/null +++ b/src/backend/conftest.py @@ -0,0 +1,30 @@ +"""Global fixtures for the backend tests.""" + +import pytest +from urllib3.connectionpool import HTTPConnectionPool + + +@pytest.fixture(autouse=True) +def no_http_requests(monkeypatch): + """ + Prevents HTTP requests from being made during tests. + This is useful for tests that do not require actual HTTP requests + and helps to avoid network-related issues. + + Credits: https://blog.jerrycodes.com/no-http-requests/ + """ + + allowed_hosts = {"localhost"} + original_urlopen = HTTPConnectionPool.urlopen + + def urlopen_mock(self, method, url, *args, **kwargs): + if self.host in allowed_hosts: + return original_urlopen(self, method, url, *args, **kwargs) + + raise RuntimeError( + f"The test was about to {method} {self.scheme}://{self.host}{url}" + ) + + monkeypatch.setattr( + "urllib3.connectionpool.HTTPConnectionPool.urlopen", urlopen_mock + ) diff --git a/src/backend/core/tests/team_accesses/test_api_team_accesses_delete.py b/src/backend/core/tests/team_accesses/test_api_team_accesses_delete.py index e9a44fb..9066e49 100644 --- a/src/backend/core/tests/team_accesses/test_api_team_accesses_delete.py +++ b/src/backend/core/tests/team_accesses/test_api_team_accesses_delete.py @@ -4,7 +4,6 @@ Test for team accesses API endpoints in People's core app : delete import json import random -import re import pytest import responses @@ -157,16 +156,18 @@ def test_api_team_accesses_delete_owners_last_owner(): assert models.TeamAccess.objects.count() == 1 +@responses.activate def test_api_team_accesses_delete_webhook(): """ When the team has a webhook, deleting a team access should fire a call. """ user = factories.UserFactory() team = factories.TeamFactory(users=[(user, "administrator")]) - webhook = factories.TeamWebhookFactory(team=team) access = factories.TeamAccessFactory( team=team, role=random.choice(["member", "administrator"]) ) + # add webhook after access to prevent webhook from being triggered + webhook = factories.TeamWebhookFactory(team=team) assert models.TeamAccess.objects.count() == 2 assert models.TeamAccess.objects.filter(user=access.user).exists() @@ -174,42 +175,34 @@ def test_api_team_accesses_delete_webhook(): client = APIClient() client.force_login(user) - with responses.RequestsMock() as rsps: - # Ensure successful response by scim provider using "responses": - rsp = rsps.add( - rsps.PATCH, - re.compile(r".*/Groups/.*"), - body="{}", - status=200, - content_type="application/json", - ) + patch_response = responses.patch(webhook.url, status=200, json={}) - response = client.delete( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - ) - assert response.status_code == 204 + response = client.delete( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + assert response.status_code == 204 - assert rsp.call_count == 1 - assert rsps.calls[0].request.url == webhook.url + # Check the request was made + assert patch_response.call_count == 1 - # Payload sent to scim provider - payload = json.loads(rsps.calls[0].request.body) - assert payload == { - "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], - "Operations": [ - { - "op": "remove", - "path": "members", - "value": [ - { - "value": str(access.user.id), - "email": access.user.email, - "type": "User", - } - ], - } - ], - } + # Payload sent to scim provider + scim_payload = json.loads(patch_response.calls[0].request.body) + assert scim_payload == { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], + "Operations": [ + { + "op": "remove", + "path": "members", + "value": [ + { + "value": str(access.user.id), + "email": access.user.email, + "type": "User", + } + ], + } + ], + } assert models.TeamAccess.objects.count() == 1 assert models.TeamAccess.objects.filter(user=access.user).exists() is False diff --git a/src/backend/core/tests/test_models_team_accesses.py b/src/backend/core/tests/test_models_team_accesses.py index c628016..f2930e6 100644 --- a/src/backend/core/tests/test_models_team_accesses.py +++ b/src/backend/core/tests/test_models_team_accesses.py @@ -83,47 +83,41 @@ def test_models_team_accesses_create_webhook(): } +@responses.activate def test_models_team_accesses_delete_webhook(): """ When the team has a webhook, deleting a team access should fire a call. """ team = factories.TeamFactory() - webhook = factories.TeamWebhookFactory(team=team) access = factories.TeamAccessFactory(team=team) + # add webhook after access to prevent webhook from being triggered + webhook = factories.TeamWebhookFactory(team=team) - with responses.RequestsMock() as rsps: - # Ensure successful response by scim provider using "responses": - rsp = rsps.add( - rsps.PATCH, - re.compile(r".*/Groups/.*"), - body="{}", - status=200, - content_type="application/json", - ) + # Ensure successful response by scim provider using "responses": + rsp = responses.patch(webhook.url, status=200, json={}) - access.delete() + access.delete() - assert rsp.call_count == 1 - assert rsps.calls[0].request.url == webhook.url + assert rsp.call_count == 1 - # Payload sent to scim provider - payload = json.loads(rsps.calls[0].request.body) - assert payload == { - "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], - "Operations": [ - { - "op": "remove", - "path": "members", - "value": [ - { - "value": str(access.user.id), - "email": access.user.email, - "type": "User", - } - ], - } - ], - } + # Payload sent to scim provider + payload = json.loads(rsp.calls[0].request.body) + assert payload == { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], + "Operations": [ + { + "op": "remove", + "path": "members", + "value": [ + { + "value": str(access.user.id), + "email": access.user.email, + "type": "User", + } + ], + } + ], + } assert models.TeamAccess.objects.exists() is False