From f8533b7d29c4aeebf8693b994c2394485051153d Mon Sep 17 00:00:00 2001 From: Rob Hudson Date: Mon, 30 Dec 2024 11:34:33 -0800 Subject: [PATCH] Add admin_dsar testing --- basket/base/tests/test_forms.py | 34 ++++++++ basket/base/tests/test_view_admin_dsar.py | 101 ++++++++++++++++++++++ basket/settings.py | 10 ++- 3 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 basket/base/tests/test_forms.py create mode 100644 basket/base/tests/test_view_admin_dsar.py diff --git a/basket/base/tests/test_forms.py b/basket/base/tests/test_forms.py new file mode 100644 index 00000000..222f2c58 --- /dev/null +++ b/basket/base/tests/test_forms.py @@ -0,0 +1,34 @@ +from django.core.exceptions import ValidationError + +import pytest + +from basket.base.forms import EmailListField + + +class TestEmailListField: + def setup_method(self): + self.field = EmailListField() + + def test_to_python_empty(self): + assert self.field.to_python("") == [] + + def test_to_python_single_email(self): + assert self.field.to_python("test@example.com") == ["test@example.com"] + + def test_to_python_multiple_emails(self): + value = "test1@example.com\ntest2@example.com\n" + assert self.field.to_python(value) == ["test1@example.com", "test2@example.com"] + + def test_to_python_with_whitespace(self): + value = " test1@example.com \n test2@example.com \n" + assert self.field.to_python(value) == ["test1@example.com", "test2@example.com"] + + def test_to_python_with_empty(self): + value = " test1@example.com \n\n test2@example.com \n" + assert self.field.to_python(value) == ["test1@example.com", "test2@example.com"] + + def test_validate_invalid_emails(self): + value = ["test1@example.com", "invalid-email"] + with pytest.raises(ValidationError) as excinfo: + self.field.validate(value) + assert "Invalid email: invalid-email" in str(excinfo.value) diff --git a/basket/base/tests/test_view_admin_dsar.py b/basket/base/tests/test_view_admin_dsar.py new file mode 100644 index 00000000..0c322b70 --- /dev/null +++ b/basket/base/tests/test_view_admin_dsar.py @@ -0,0 +1,101 @@ +from unittest.mock import patch + +from django.conf import settings +from django.contrib.auth.models import Permission, User +from django.test import Client +from django.urls import reverse + +import pytest + +from basket.base.forms import EmailListForm +from basket.news.backends.ctms import CTMSNotFoundByEmailError + + +@pytest.mark.django_db +class TestAdminDSARView: + def setup_method(self, method): + self.client = Client() + self.url = reverse("admin.dsar") + + def _create_admin_user(self, with_perm=True): + user = User.objects.create_user(username="admin", password="password") + user.is_staff = True + if with_perm: + user.user_permissions.add(Permission.objects.get(codename="dsar_access")) + user.save() + return user + + def _login_admin_user(self): + self.client.login(username="admin", password="password") + + def test_get_requires_login(self): + self._create_admin_user() + response = self.client.get(self.url) + assert response.status_code == 302 + assert response.url.startswith(settings.LOGIN_URL) + + def test_get_requires_perm(self): + self._create_admin_user(with_perm=False) + self._login_admin_user() + response = self.client.get(self.url) + assert response.status_code == 302 + assert response.url.startswith(settings.LOGIN_URL) + + def test_get(self): + self._create_admin_user() + self._login_admin_user() + response = self.client.get(self.url) + assert response.status_code == 200 + assert isinstance(response.context["form"], EmailListForm) + assert response.context["output"] is None + + def test_post_valid_emails(self): + self._create_admin_user() + self._login_admin_user() + with patch("basket.base.views.ctms", spec_set=["delete"]) as mock_ctms: + mock_ctms.delete.side_effect = [ + [{"email_id": "123", "fxa_id": "", "mofo_contact_id": ""}], + [{"email_id": "456", "fxa_id": "string", "mofo_contact_id": ""}], + [{"email_id": "789", "fxa_id": "string", "mofo_contact_id": "string"}], + ] + response = self.client.post(self.url, {"emails": "test1@example.com\ntest2@example.com\ntest3@example.com"}, follow=True) + + assert response.status_code == 200 + assert mock_ctms.delete.call_count == 3 + assert "DELETED test1@example.com (ctms id: 123)." in response.context["output"] + assert "DELETED test2@example.com (ctms id: 456). fxa: YES." in response.context["output"] + assert "DELETED test3@example.com (ctms id: 789). fxa: YES. mofo: YES." in response.context["output"] + + def test_post_valid_email(self): + self._create_admin_user() + self._login_admin_user() + with patch("basket.base.views.ctms", spec_set=["delete"]) as mock_ctms: + mock_ctms.delete.return_value = [{"email_id": "123", "fxa_id": "", "mofo_contact_id": ""}] + response = self.client.post(self.url, {"emails": "test@example.com"}, follow=True) + + assert response.status_code == 200 + assert mock_ctms.delete.called + assert "DELETED test@example.com (ctms id: 123)." in response.context["output"] + + def test_post_unknown_ctms_user(self, mocker): + self._create_admin_user() + self._login_admin_user() + with patch("basket.base.views.ctms", spec_set=["delete"]) as mock_ctms: + mock_ctms.delete.side_effect = CTMSNotFoundByEmailError("unknown@example.com") + response = self.client.post(self.url, {"emails": "unknown@example.com"}, follow=True) + + assert response.status_code == 200 + assert mock_ctms.delete.called + assert "unknown@example.com not found in CTMS" in response.context["output"] + + def test_post_invalid_email(self, mocker): + self._create_admin_user() + self._login_admin_user() + with patch("basket.base.views.ctms", spec_set=["delete"]) as mock_ctms: + mock_ctms.delete.side_effect = CTMSNotFoundByEmailError + response = self.client.post(self.url, {"emails": "invalid@email"}, follow=True) + + assert response.status_code == 200 + assert not mock_ctms.delete.called + assert response.context["output"] is None + assert response.context["form"].errors == {"emails": ["Invalid email: invalid@email"]} diff --git a/basket/settings.py b/basket/settings.py index 0f90ce85..fc0f120d 100644 --- a/basket/settings.py +++ b/basket/settings.py @@ -125,11 +125,11 @@ def path(*args): "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { - "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage" + if (DEBUG or UNITTEST) + else "whitenoise.storage.CompressedManifestStaticFilesStorage", }, } -if DEBUG: - STORAGES["staticfiles"]["BACKEND"] = "django.contrib.staticfiles.storage.StaticFilesStorage" try: # Make this unique, and don't share it with anybody. @@ -448,6 +448,9 @@ def before_send(event, hint): COMMON_VOICE_NEWSLETTER = config("COMMON_VOICE_NEWSLETTER", default="common-voice") +LOGIN_URL = "/admin/" +LOGIN_REDIRECT_URL = "/admin/" + OIDC_ENABLE = config("OIDC_ENABLE", parser=bool, default="false") if OIDC_ENABLE: AUTHENTICATION_BACKENDS = ("basket.base.authentication.OIDCModelBackend",) @@ -458,7 +461,6 @@ def before_send(event, hint): OIDC_RP_CLIENT_ID = config("OIDC_RP_CLIENT_ID", default="") OIDC_RP_CLIENT_SECRET = config("OIDC_RP_CLIENT_SECRET", default="") OIDC_CREATE_USER = config("OIDC_CREATE_USER", parser=bool, default="false") - LOGIN_REDIRECT_URL = "/admin/" OIDC_EXEMPT_URLS = [ "/", "/fxa/",