From b9e759e11c53cf5de0e98efe810a9020a8d1d3cc Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 20 Oct 2019 16:29:03 +0100 Subject: Tests for the new signal listener is_staff changes --- pydis_site/apps/home/tests/test_signal_listener.py | 29 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) (limited to 'pydis_site/apps/home/tests') diff --git a/pydis_site/apps/home/tests/test_signal_listener.py b/pydis_site/apps/home/tests/test_signal_listener.py index 27fc7710..163ad6af 100644 --- a/pydis_site/apps/home/tests/test_signal_listener.py +++ b/pydis_site/apps/home/tests/test_signal_listener.py @@ -67,12 +67,14 @@ class SignalListenerTests(TestCase): cls.admin_mapping = RoleMapping.objects.create( role=cls.admin_role, - group=cls.admin_group + group=cls.admin_group, + is_staff=True ) cls.moderator_mapping = RoleMapping.objects.create( role=cls.moderator_role, - group=cls.moderator_group + group=cls.moderator_group, + is_staff=False ) cls.discord_user = DiscordUser.objects.create( @@ -166,7 +168,7 @@ class SignalListenerTests(TestCase): cls.django_moderator = DjangoUser.objects.create( username="moderator", - is_staff=True, + is_staff=False, is_superuser=False ) @@ -373,10 +375,25 @@ class SignalListenerTests(TestCase): self.assertEqual(self.django_moderator.groups.all().count(), 1) self.assertEqual(self.django_admin.groups.all().count(), 1) + # Test is_staff changes + self.admin_mapping.is_staff = False + self.admin_mapping.save() + + self.assertFalse(self.django_moderator.is_staff) + self.assertFalse(self.django_admin.is_staff) + + self.admin_mapping.is_staff = True + self.admin_mapping.save() + + self.django_admin.refresh_from_db(fields=("is_staff", )) + self.assertTrue(self.django_admin.is_staff) + # Test mapping deletion self.admin_mapping.delete() + self.django_admin.refresh_from_db(fields=("is_staff",)) self.assertEqual(self.django_admin.groups.all().count(), 0) + self.assertFalse(self.django_admin.is_staff) # Test mapping update self.moderator_mapping.group = self.admin_group @@ -388,12 +405,16 @@ class SignalListenerTests(TestCase): # Test mapping creation new_mapping = RoleMapping.objects.create( role=self.admin_role, - group=self.moderator_group + group=self.moderator_group, + is_staff=True ) self.assertEqual(self.django_admin.groups.all().count(), 1) self.assertTrue(self.moderator_group in self.django_admin.groups.all()) + self.django_admin.refresh_from_db(fields=("is_staff",)) + self.assertTrue(self.django_admin.is_staff) + # Test that nothing happens when fixtures are loaded pre_save.send(RoleMapping, instance=new_mapping, raw=True) -- cgit v1.2.3 From 701711594686d8bda3e2786649b5f798eccbf935 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 20 Oct 2019 16:51:10 +0100 Subject: Signals: Complete coverage in tests --- pydis_site/apps/home/tests/test_signal_listener.py | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'pydis_site/apps/home/tests') diff --git a/pydis_site/apps/home/tests/test_signal_listener.py b/pydis_site/apps/home/tests/test_signal_listener.py index 163ad6af..66a67252 100644 --- a/pydis_site/apps/home/tests/test_signal_listener.py +++ b/pydis_site/apps/home/tests/test_signal_listener.py @@ -341,6 +341,33 @@ class SignalListenerTests(TestCase): self.discord_admin.roles.add(self.admin_role) self.discord_admin.save() + def test_apply_groups_moderator(self): + """Test application of groups by role, relating to a non-`is_staff` moderator user.""" + handler = AllauthSignalListener() + + self.assertEqual(self.django_user_discordless.groups.all().count(), 0) + + # Apply groups based on moderator role being present on Discord + handler._apply_groups(self.discord_moderator, self.social_moderator) + self.assertTrue(self.moderator_group in self.django_moderator.groups.all()) + + # Remove groups based on the user apparently leaving the server + handler._apply_groups(self.discord_moderator, self.social_moderator, True) + self.assertEqual(self.django_user_discordless.groups.all().count(), 0) + + # Apply the moderator role again + handler._apply_groups(self.discord_moderator, self.social_moderator) + + # Remove all of the roles from the user + self.discord_moderator.roles.clear() + + # Remove groups based on the user no longer having the moderator role on Discord + handler._apply_groups(self.discord_moderator, self.social_moderator) + self.assertEqual(self.django_user_discordless.groups.all().count(), 0) + + self.discord_moderator.roles.add(self.moderator_role) + self.discord_moderator.save() + def test_apply_groups_other(self): """Test application of groups by role, relating to non-standard cases.""" handler = AllauthSignalListener() @@ -415,6 +442,20 @@ class SignalListenerTests(TestCase): self.django_admin.refresh_from_db(fields=("is_staff",)) self.assertTrue(self.django_admin.is_staff) + new_mapping.delete() + + # Test mapping creation (without is_staff) + new_mapping = RoleMapping.objects.create( + role=self.admin_role, + group=self.moderator_group, + ) + + self.assertEqual(self.django_admin.groups.all().count(), 1) + self.assertTrue(self.moderator_group in self.django_admin.groups.all()) + + self.django_admin.refresh_from_db(fields=("is_staff",)) + self.assertFalse(self.django_admin.is_staff) + # Test that nothing happens when fixtures are loaded pre_save.send(RoleMapping, instance=new_mapping, raw=True) -- cgit v1.2.3 From 3892a5fb8e077726ac8fbe59f716b56a501c9ce3 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 22 Oct 2019 17:54:20 +0100 Subject: Full test coverage, better redirect testing --- pydis_site/apps/home/tests/test_views.py | 111 ++++++++++++++++++++++++- pydis_site/apps/home/views/account/settings.py | 3 - 2 files changed, 110 insertions(+), 4 deletions(-) (limited to 'pydis_site/apps/home/tests') diff --git a/pydis_site/apps/home/tests/test_views.py b/pydis_site/apps/home/tests/test_views.py index 7aeaddd2..85423c1c 100644 --- a/pydis_site/apps/home/tests/test_views.py +++ b/pydis_site/apps/home/tests/test_views.py @@ -1,5 +1,112 @@ +from django.contrib.auth.models import User +from django.http import HttpResponseRedirect from django.test import TestCase -from django_hosts.resolvers import reverse +from django_hosts.resolvers import get_host, reverse, reverse_host + + +def check_redirect_url( + response: HttpResponseRedirect, reversed_url: str, strip_params=True +) -> bool: + """ + Check whether a given redirect response matches a specific reversed URL. + + Arguments: + * `response`: The HttpResponseRedirect returned by the test client + * `reversed_url`: The URL returned by `reverse()` + * `strip_params`: Whether to strip URL parameters (following a "?") from the URL given in the + `response` object + """ + host = get_host(None) + hostname = reverse_host(host) + + redirect_url = response.url + + if strip_params and "?" in redirect_url: + redirect_url = redirect_url.split("?", 1)[0] + + result = reversed_url == f"//{hostname}{redirect_url}" + return result + + +class TestAccountDeleteView(TestCase): + def setUp(self) -> None: + """Create an authorized Django user for testing purposes.""" + self.user = User.objects.create( + username="user#0000" + ) + + def test_redirect_when_logged_out(self): + """Test that the user is redirected to the homepage when not logged in.""" + url = reverse("account_delete") + resp = self.client.get(url) + self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, reverse("home"))) + + def test_get_when_logged_in(self): + """Test that the view returns a HTTP 200 when the user is logged in.""" + url = reverse("account_delete") + + self.client.force_login(self.user) + resp = self.client.get(url) + self.client.logout() + + self.assertEqual(resp.status_code, 200) + + def test_post_invalid(self): + """Test that the user is redirected when the form is filled out incorrectly.""" + url = reverse("account_delete") + + self.client.force_login(self.user) + + resp = self.client.post(url, {}) + self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, url)) + + resp = self.client.post(url, {"username": "user"}) + self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, url)) + + self.client.logout() + + def test_post_valid(self): + """Test that the account is deleted when the form is filled out correctly..""" + url = reverse("account_delete") + + self.client.force_login(self.user) + + resp = self.client.post(url, {"username": "user#0000"}) + self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, reverse("home"))) + + with self.assertRaises(User.DoesNotExist): + User.objects.get(username=self.user.username) + + self.client.logout() + + +class TestAccountSettingsView(TestCase): + def setUp(self) -> None: + """Create an authorized Django user for testing purposes.""" + self.user = User.objects.create( + username="user#0000" + ) + + def test_redirect_when_logged_out(self): + """Check that the user is redirected to the homepage when not logged in.""" + url = reverse("account_settings") + resp = self.client.get(url) + self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, reverse("home"))) + + def test_get_when_logged_in(self): + """Test that the view returns a HTTP 200 when the user is logged in.""" + url = reverse("account_settings") + + self.client.force_login(self.user) + resp = self.client.get(url) + self.client.logout() + + self.assertEqual(resp.status_code, 200) class TestIndexReturns200(TestCase): @@ -16,6 +123,7 @@ class TestLoginCancelledReturns302(TestCase): url = reverse('socialaccount_login_cancelled') resp = self.client.get(url) self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, reverse("home"))) class TestLoginErrorReturns302(TestCase): @@ -24,3 +132,4 @@ class TestLoginErrorReturns302(TestCase): url = reverse('socialaccount_login_error') resp = self.client.get(url) self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, reverse("home"))) diff --git a/pydis_site/apps/home/views/account/settings.py b/pydis_site/apps/home/views/account/settings.py index aa272552..ee9011da 100644 --- a/pydis_site/apps/home/views/account/settings.py +++ b/pydis_site/apps/home/views/account/settings.py @@ -15,6 +15,3 @@ class SettingsView(LoginRequiredMixin, View): def get(self, request: HttpRequest) -> HttpResponse: """HTTP GET: Return the view template.""" return render(request, "home/account/settings.html") - - def post(self, request: HttpRequest) -> HttpResponse: - """HTTP POST: Process account changes, as requested by the user.""" -- cgit v1.2.3 From ebc3a8ae61caa8f5d12992295f0bc122282fbcb4 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 23 Oct 2019 01:33:06 +0100 Subject: Full coverage and finish cleanup --- pydis_site/apps/home/tests/test_views.py | 86 ++++++++++++++++++++++++++ pydis_site/apps/home/views/account/settings.py | 2 +- pydis_site/templates/base/navbar.html | 13 ++-- 3 files changed, 96 insertions(+), 5 deletions(-) (limited to 'pydis_site/apps/home/tests') diff --git a/pydis_site/apps/home/tests/test_views.py b/pydis_site/apps/home/tests/test_views.py index 85423c1c..572317a7 100644 --- a/pydis_site/apps/home/tests/test_views.py +++ b/pydis_site/apps/home/tests/test_views.py @@ -1,3 +1,4 @@ +from allauth.socialaccount.models import SocialAccount from django.contrib.auth.models import User from django.http import HttpResponseRedirect from django.test import TestCase @@ -91,6 +92,42 @@ class TestAccountSettingsView(TestCase): username="user#0000" ) + self.user_unlinked = User.objects.create( + username="user#9999" + ) + + self.user_unlinked_discord = User.objects.create( + username="user#1234" + ) + + self.user_unlinked_github = User.objects.create( + username="user#1111" + ) + + self.github_account = SocialAccount.objects.create( + user=self.user, + provider="github", + uid="0" + ) + + self.discord_account = SocialAccount.objects.create( + user=self.user, + provider="discord", + uid="0000" + ) + + self.github_account_secondary = SocialAccount.objects.create( + user=self.user_unlinked_discord, + provider="github", + uid="1" + ) + + self.discord_account_secondary = SocialAccount.objects.create( + user=self.user_unlinked_github, + provider="discord", + uid="1111" + ) + def test_redirect_when_logged_out(self): """Check that the user is redirected to the homepage when not logged in.""" url = reverse("account_settings") @@ -108,6 +145,55 @@ class TestAccountSettingsView(TestCase): self.assertEqual(resp.status_code, 200) + self.client.force_login(self.user_unlinked) + resp = self.client.get(url) + self.client.logout() + + self.assertEqual(resp.status_code, 200) + + self.client.force_login(self.user_unlinked_discord) + resp = self.client.get(url) + self.client.logout() + + self.assertEqual(resp.status_code, 200) + + self.client.force_login(self.user_unlinked_github) + resp = self.client.get(url) + self.client.logout() + + self.assertEqual(resp.status_code, 200) + + def test_post_invalid(self): + """Test the behaviour of invalid POST submissions.""" + url = reverse("account_settings") + + self.client.force_login(self.user_unlinked) + + resp = self.client.post(url, {"provider": "discord"}) + self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, reverse("home"))) + + resp = self.client.post(url, {"provider": "github"}) + self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, reverse("home"))) + + self.client.logout() + + def test_post_valid(self): + """Ensure that GitHub is unlinked with a valid POST submission.""" + url = reverse("account_settings") + + self.client.force_login(self.user) + + resp = self.client.post(url, {"provider": "github"}) + self.assertEqual(resp.status_code, 302) + self.assertTrue(check_redirect_url(resp, reverse("home"))) + + with self.assertRaises(SocialAccount.DoesNotExist): + SocialAccount.objects.get(user=self.user, provider="github") + + self.client.logout() + class TestIndexReturns200(TestCase): def test_index_returns_200(self): diff --git a/pydis_site/apps/home/views/account/settings.py b/pydis_site/apps/home/views/account/settings.py index 84a2dea0..3a817dbc 100644 --- a/pydis_site/apps/home/views/account/settings.py +++ b/pydis_site/apps/home/views/account/settings.py @@ -36,7 +36,7 @@ class SettingsView(LoginRequiredMixin, View): if account.provider == "discord": context["discord"] = account - elif account.provider == "github": + if account.provider == "github": context["github"] = account return render(request, "home/account/settings.html", context=context) diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html index 6943c2b6..2ba5bdd4 100644 --- a/pydis_site/templates/base/navbar.html +++ b/pydis_site/templates/base/navbar.html @@ -127,16 +127,21 @@ {% if user.is_authenticated %} {% endif %} -- cgit v1.2.3