diff --git a/concordia/tests/test_account_views.py b/concordia/tests/test_account_views.py index bac87a7fe..836635c78 100644 --- a/concordia/tests/test_account_views.py +++ b/concordia/tests/test_account_views.py @@ -2,26 +2,32 @@ Tests for user account-related views """ +from smtplib import SMTPException from unittest.mock import patch from django import forms +from django.contrib.messages import get_messages from django.core import mail, signing from django.core.cache import cache from django.test import TestCase, override_settings from django.urls import reverse +from django.utils.timezone import now -from concordia.models import ConcordiaUser, User +from concordia.models import ConcordiaUser, Transcription, User +from concordia.utils import get_anonymous_user from .utils import ( CacheControlAssertions, CreateTestUsers, JSONAssertMixin, + create_asset, create_campaign, + create_transcription, ) @override_settings(RATELIMIT_ENABLE=False) -class ConcordiaViewTests( +class ConcordiaAccountViewTests( CreateTestUsers, JSONAssertMixin, CacheControlAssertions, TestCase ): """ @@ -42,15 +48,53 @@ def test_AccountProfileView_get(self): self.login_user() response = self.client.get(reverse("user-profile")) - self.assertEqual(response.status_code, 200) self.assertUncacheable(response) self.assertTemplateUsed(response, template_name="account/profile.html") - self.assertEqual(response.context["user"], self.user) self.assertContains(response, self.user.username) self.assertContains(response, self.user.email) + response = self.client.get(reverse("user-profile"), {"activity": "transcribed"}) + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, template_name="account/profile.html") + self.assertEqual(response.context["user"], self.user) + self.assertEqual(response.context["active_tab"], "recent") + + response = self.client.get(reverse("user-profile"), {"status": "submitted"}) + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, template_name="account/profile.html") + self.assertEqual(response.context["user"], self.user) + self.assertEqual(response.context["active_tab"], "recent") + self.assertEqual(response.context["status_list"], ["submitted"]) + + response = self.client.get( + reverse("user-profile"), {"start": "1970-01-01", "end": "1970-01-02"} + ) + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, template_name="account/profile.html") + self.assertEqual(response.context["user"], self.user) + self.assertEqual(response.context["end"], "1970-01-02") + self.assertEqual(response.context["start"], "1970-01-01") + + anon = get_anonymous_user() + asset = create_asset() + t = asset.transcription_set.create(asset=asset, user=anon) + t.submitted = now() + t.accepted = now() + t.reviewed_by = self.user + t.save() + response = self.client.get(reverse("user-profile")) + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, template_name="account/profile.html") + self.assertEqual(response.context["user"], self.user) + self.assertEqual(response.context["totalReviews"], 1) + self.assertEqual(response.context["totalCount"], 1) + def test_AccountProfileView_post(self): """ This unit test tests the post entry for the route account/profile @@ -65,14 +109,43 @@ def test_AccountProfileView_post(self): reverse("user-profile"), {"email": test_email, "username": "tester"} ) - self.assertEqual(response.status_code, 302) - self.assertUncacheable(response) - index = response.url.find("#") - self.assertEqual(response.url[:index], reverse("user-profile")) + self.assertEqual(response.status_code, 302) + self.assertUncacheable(response) + index = response.url.find("#") + self.assertEqual(response.url[:index], reverse("user-profile")) + + # Verify the User was correctly updated + updated_user = User.objects.get(email=test_email) + self.assertEqual(updated_user.email, test_email) + + # Test first/last name can be updated + self.assertNotEqual(updated_user.first_name, "Test") + self.assertNotEqual(updated_user.last_name, "User") + response = self.client.post( + reverse("user-profile"), + {"submit_name": True, "first_name": "Test", "last_name": "User"}, + ) + + self.assertRedirects(response, reverse("user-profile")) + self.assertUncacheable(response) - # Verify the User was correctly updated - updated_user = User.objects.get(email=test_email) - self.assertEqual(updated_user.email, test_email) + updated_user = User.objects.get(email=test_email) + first_name = updated_user.first_name + last_name = updated_user.last_name + self.assertEqual(first_name, "Test") + self.assertEqual(last_name, "User") + + # Test name form submission without valid data + # First/last names should stay the same after post + # The form can't really be invalid since even blank + # values just set the names to empty strings, + # so we need to mock an invalid response + with patch("concordia.forms.UserNameForm.is_valid") as mock: + mock.return_value = False + response = self.client.post(reverse("user-profile"), {"submit_name": True}) + updated_user = User.objects.get(email=test_email) + self.assertEqual(updated_user.first_name, first_name) + self.assertEqual(updated_user.last_name, last_name) def test_AccountProfileView_post_invalid_form(self): """ @@ -145,6 +218,21 @@ def test_email_reconfirmation(self): with self.settings(REQUIRE_EMAIL_RECONFIRMATION=True): email_data = {"email": "change@example.com"} + with patch("django.core.mail.EmailMultiAlternatives.send") as mock: + mock.side_effect = SMTPException() + response = self.client.post(reverse("user-profile"), email_data) + self.assertRedirects( + response, "{}#account".format(reverse("user-profile")) + ) + messages = [ + str(message) for message in get_messages(response.wsgi_request) + ] + self.assertIn( + "Email confirmation could not be sent.", + messages, + ) + self.assertEqual(len(mail.outbox), 0) + response = self.client.post(reverse("user-profile"), email_data) self.assertRedirects(response, "{}#account".format(reverse("user-profile"))) self.assertTemplateUsed(response, "emails/email_reconfirmation_subject.txt") @@ -262,7 +350,7 @@ def test_AccountLetterView(self): def test_get_pages(self): self.login_user() - create_campaign() + campaign = create_campaign() url = reverse("get_pages") response = self.client.get(url, {"activity": "transcribed"}) @@ -274,7 +362,9 @@ def test_get_pages(self): self.assertEqual(response.status_code, 200) self.assertUncacheable(response) - response = self.client.get(url, {"status": ["completed"], "campaign": 1}) + response = self.client.get( + url, {"status": ["completed"], "campaign": campaign.id} + ) self.assertEqual(response.status_code, 200) self.assertUncacheable(response) @@ -295,3 +385,51 @@ def test_get_pages(self): response = self.client.get(url, {"start": "1900-01-01", "end": "1999-12-31"}) self.assertEqual(response.status_code, 200) self.assertUncacheable(response) + + def test_AccountDeletionView(self): + self.login_user() + + response = self.client.get(reverse("account-deletion")) + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, template_name="account/account_deletion.html") + self.assertEqual(response.context["user"], self.user) + + response = self.client.post(reverse("account-deletion")) + self.assertRedirects(response, reverse("homepage")) + with self.assertRaises(User.DoesNotExist): + User.objects.get(id=self.user.id) + self.assertEqual(len(mail.outbox), 1) + + mail.outbox = [] + self.user = None + self.login_user() + with patch("django.core.mail.EmailMultiAlternatives.send") as mock: + mock.side_effect = SMTPException() + response = self.client.post(reverse("account-deletion")) + self.assertRedirects(response, reverse("homepage")) + messages = [str(message) for message in get_messages(response.wsgi_request)] + self.assertIn( + "Email confirmation of deletion could not be sent.", + messages, + ) + self.assertEqual(len(mail.outbox), 0) + + mail.outbox = [] + self.user = None + self.login_user() + transcription = create_transcription(user=self.user) + response = self.client.post(reverse("account-deletion")) + self.assertRedirects(response, reverse("homepage")) + user = User.objects.get(id=self.user.id) + transcription = Transcription.objects.get(id=transcription.id) + self.assertEqual(transcription.user, user) + self.assertIn("Anonymized", user.username) + self.assertEqual(user.first_name, "") + self.assertEqual(user.last_name, "") + self.assertEqual(user.email, "") + self.assertFalse(user.has_usable_password()) + self.assertFalse(user.is_staff) + self.assertFalse(user.is_superuser) + self.assertFalse(user.is_active) + self.assertEqual(len(mail.outbox), 1) diff --git a/concordia/tests/test_views.py b/concordia/tests/test_views.py index 95353fe5a..d478b5afe 100644 --- a/concordia/tests/test_views.py +++ b/concordia/tests/test_views.py @@ -4,7 +4,7 @@ from django import forms from django.conf import settings from django.contrib.auth.models import User -from django.core.cache import cache +from django.core.cache import caches from django.http import HttpResponse, JsonResponse from django.test import ( Client, @@ -119,10 +119,12 @@ class ConcordiaViewTests(CreateTestUsers, JSONAssertMixin, TestCase): """ def setUp(self): - cache.clear() + for cache in caches.all(): + cache.clear() def tearDown(self): - cache.clear() + for cache in caches.all(): + cache.clear() def test_ratelimit_view(self): c = Client() diff --git a/concordia/views.py b/concordia/views.py index e1eebf0e2..def878a61 100644 --- a/concordia/views.py +++ b/concordia/views.py @@ -605,14 +605,6 @@ def get_queryset(self): def get_context_data(self, *args, **kwargs): ctx = super().get_context_data(*args, **kwargs) - ctx["object_list"] = object_list = [] - campaignSlug = self.request.GET.get("campaign_slug", -1) - - for asset in ctx.pop("object_list"): - if int(campaignSlug) == -1: - object_list.append((asset)) - elif asset.item.project.campaign.id == int(campaignSlug): - object_list.append((asset)) page = self.request.GET.get("page", None) campaign = self.request.GET.get("campaign", None) @@ -623,7 +615,7 @@ def get_context_data(self, *args, **kwargs): order_by = self.request.GET.get("order_by", None) if any([activity, campaign, page, status_list, start, end, order_by]): ctx["active_tab"] = "recent" - if status_list is not None: + if status_list: ctx["status_list"] = status_list ctx["order_by"] = self.request.GET.get("order_by", "date-descending") elif "active_tab" not in ctx: @@ -734,6 +726,10 @@ def send_reconfirmation_email(self, user): "Unable to send email reconfirmation to %s", user.get_email_for_reconfirmation(), ) + messages.error( + self.request, + _("Email confirmation could not be sent."), + ) @method_decorator(never_cache, name="dispatch") @@ -755,7 +751,7 @@ def form_valid(self, form): self.delete_user(form.request.user, form.request) return super().form_valid(form) - def delete_user(self, user, request=None): + def delete_user(self, user, request): logger.info("Deletion request for %s", user) email = user.email if user.transcription_set.exists(): @@ -773,8 +769,7 @@ def delete_user(self, user, request=None): logger.info("Deleting %s", user) user.delete() self.send_deletion_email(email) - if request: - logout(request) + logout(request) def send_deletion_email(self, email): context = {} @@ -802,6 +797,10 @@ def send_deletion_email(self, email): "Unable to send account deletion email to %s", email, ) + messages.error( + self.request, + _("Email confirmation of deletion could not be sent."), + ) @method_decorator(default_cache_control, name="dispatch")