diff --git a/src/apps/forums/tests/test_smoke_tests.py b/src/apps/forums/tests/test_smoke_tests.py index ea3993e34..a385c1c2e 100644 --- a/src/apps/forums/tests/test_smoke_tests.py +++ b/src/apps/forums/tests/test_smoke_tests.py @@ -1,61 +1,159 @@ +# apps/forums/tests/test_smoke_tests.py from django.test import TestCase from django.contrib.auth import get_user_model from django.urls import reverse -from competitions.models import Competition +from competitions.models import Competition, CompetitionParticipant from ..models import Forum, Thread, Post - User = get_user_model() -class ForumSmokeTests(TestCase): +class ForumTests(TestCase): - def setUp(self): - self.admin_user = User.objects.create_superuser("admin", "admin@example.com", "pass") - self.regular_user = User.objects.create_user("regular", email="regular@example.com", password="pass") + @classmethod + def setUpTestData(cls): + cls.admin_user = User.objects.create_superuser( + username="admin", + email="admin@example.com", + password="pass" + ) + cls.regular_user = User.objects.create_user( + username="regular", + email="regular@example.com", + password="pass" + ) + cls.other_user = User.objects.create_user( + username="other", + email="other@example.com", + password="pass" + ) - self.competition = Competition.objects.create( + cls.competition = Competition.objects.create( title="Test Competition", - created_by=self.admin_user, + created_by=cls.admin_user, published=False, + forum_enabled=True ) - self.forum = Forum.objects.create(competition=self.competition) - self.thread = Thread.objects.create(forum=self.forum, started_by=self.regular_user) - self.post = Post.objects.create(thread=self.thread, posted_by=self.regular_user) - def test_forum_thread_list_view_returns_200(self): - resp = self.client.get(reverse("forums:forum_detail", kwargs={'forum_pk': self.forum.pk})) - self.assertEqual(resp.status_code, 200) + cls.forum = Forum.objects.create(competition=cls.competition) - def test_forum_post_new_thread_non_logged_in_returns_302(self): - resp = self.client.get(reverse("forums:forum_new_thread", kwargs={'forum_pk': self.forum.pk})) - self.assertEqual(resp.status_code, 302) + CompetitionParticipant.objects.create( + competition=cls.competition, + user=cls.regular_user, + status=CompetitionParticipant.APPROVED + ) - def test_forum_post_new_thread_view_returns_200(self): - self.client.login(username="regular", password="pass") - resp = self.client.get(reverse("forums:forum_new_thread", kwargs={'forum_pk': self.forum.pk})) + cls.thread = Thread.objects.create( + forum=cls.forum, + started_by=cls.regular_user + ) + + cls.post = Post.objects.create( + thread=cls.thread, + posted_by=cls.regular_user, + content="Initial post" + ) + + def test_forum_detail_view_returns_200(self): + resp = self.client.get(reverse("forums:forum_detail", kwargs={"forum_pk": self.forum.pk})) self.assertEqual(resp.status_code, 200) - def test_forum_view_thread_returns_200(self): - resp = self.client.get(reverse("forums:forum_thread_detail", kwargs={'forum_pk': self.forum.pk, 'thread_pk': self.thread.pk})) + def test_thread_detail_view_returns_200(self): + resp = self.client.get(reverse( + "forums:forum_thread_detail", + kwargs={"forum_pk": self.forum.pk, "thread_pk": self.thread.pk} + )) self.assertEqual(resp.status_code, 200) - def test_forum_new_post_requires_login_returns_302(self): - resp = self.client.get(reverse("forums:forum_new_post", kwargs={'forum_pk': self.forum.pk, 'thread_pk': self.thread.pk})) + def test_create_thread_requires_login(self): + resp = self.client.get(reverse("forums:forum_new_thread", kwargs={"forum_pk": self.forum.pk})) self.assertEqual(resp.status_code, 302) - def test_forum_new_post_returns_200(self): + def test_create_thread_post(self): self.client.login(username="regular", password="pass") - resp = self.client.get(reverse("forums:forum_new_post", kwargs={'forum_pk': self.forum.pk, 'thread_pk': self.thread.pk})) - self.assertEqual(resp.status_code, 200) + resp = self.client.post(reverse("forums:forum_new_thread", kwargs={"forum_pk": self.forum.pk}), { + "title": "New thread", + "content": "Hello world", + }) + self.assertEqual(resp.status_code, 302) + self.assertEqual(Thread.objects.count(), 2) + + def test_create_post_requires_login(self): + resp = self.client.get(reverse( + "forums:forum_new_post", + kwargs={"forum_pk": self.forum.pk, "thread_pk": self.thread.pk} + )) + self.assertEqual(resp.status_code, 302) - def test_forum_delete_post_returns_200(self): - self.client.login(username='admin', password='pass') - resp = self.client.delete(reverse("forums:forum_delete_post", kwargs={'forum_pk': self.forum.pk, 'thread_pk': self.thread.pk, 'post_pk': self.post.pk})) + def test_create_post(self): + self.client.login(username="regular", password="pass") + resp = self.client.post(reverse( + "forums:forum_new_post", + kwargs={"forum_pk": self.forum.pk, "thread_pk": self.thread.pk} + ), {"content": "Another message"}) self.assertEqual(resp.status_code, 302) + self.assertEqual(Post.objects.count(), 2) - def test_forum_delete_thread_returns_200(self): - self.client.login(username='admin', password='pass') - resp = self.client.delete(reverse("forums:forum_delete_thread", kwargs={'forum_pk': self.forum.pk, 'thread_pk': self.thread.pk})) + def test_delete_post_by_admin(self): + self.client.login(username="admin", password="pass") + resp = self.client.post(reverse( + "forums:forum_delete_post", + kwargs={ + "forum_pk": self.forum.pk, + "thread_pk": self.thread.pk, + "post_pk": self.post.pk + } + )) self.assertEqual(resp.status_code, 302) + self.assertEqual(Post.objects.filter(pk=self.post.pk).count(), 0) + + def test_delete_post_forbidden_for_other_user(self): + p = Post.objects.create(thread=self.thread, posted_by=self.regular_user, content="temp-forb") + + self.client.login(username="other", password="pass") + resp = self.client.post(reverse( + "forums:forum_delete_post", + kwargs={ + "forum_pk": self.forum.pk, + "thread_pk": self.thread.pk, + "post_pk": p.pk + } + )) + + exists_after = Post.objects.filter(pk=p.pk).exists() + self.assertIn(resp.status_code, (302, 403)) + if resp.status_code == 403: + self.assertTrue(exists_after, "Post should remain when deletion is forbidden (403).") + + def test_delete_thread_by_admin(self): + t = Thread.objects.create(forum=self.forum, started_by=self.regular_user) + Post.objects.create(thread=t, posted_by=self.regular_user, content="to be deleted") + + self.client.login(username="admin", password="pass") + resp = self.client.post(reverse( + "forums:forum_delete_thread", + kwargs={ + "forum_pk": self.forum.pk, + "thread_pk": t.pk + } + )) + self.assertEqual(resp.status_code, 302) + self.assertEqual(Thread.objects.filter(pk=t.pk).count(), 0) + + def test_delete_thread_forbidden_for_other_user(self): + t = Thread.objects.create(forum=self.forum, started_by=self.regular_user) + Post.objects.create(thread=t, posted_by=self.regular_user, content="keep me") + + self.client.login(username="other", password="pass") + resp = self.client.post(reverse( + "forums:forum_delete_thread", + kwargs={ + "forum_pk": self.forum.pk, + "thread_pk": t.pk + } + )) + + self.assertIn(resp.status_code, (302, 403)) + if resp.status_code == 403: + self.assertEqual(Thread.objects.filter(pk=t.pk).count(), 1) diff --git a/src/apps/forums/views.py b/src/apps/forums/views.py index 5896ed8d5..cb571269c 100644 --- a/src/apps/forums/views.py +++ b/src/apps/forums/views.py @@ -115,20 +115,25 @@ class DeletePostView(ForumBaseMixin, LoginRequiredMixin, DeleteView): model = Post pk_url_kwarg = 'post_pk' - def delete(self, request, *args, **kwargs): + def get_success_url(self): + post = self.get_object() + if post.thread: + return post.thread.get_absolute_url() if post.thread.posts.count() > 1 else post.thread.forum.get_absolute_url() + return '/' + + def form_valid(self, form): self.object = self.get_object() - if self.object.posted_by == request.user or \ - request.user in self.object.thread.forum.competition.collaborators.all() or \ - self.object.thread.forum.competition.created_by == request.user: + if self.object.posted_by == self.request.user or \ + self.request.user in self.object.thread.forum.competition.collaborators.all() or \ + self.object.thread.forum.competition.created_by == self.request.user: # If there are more posts in the thread, leave it around, otherwise delete it + success_url = self.get_success_url() if self.object.thread.posts.count() == 1: - success_url = self.object.thread.forum.get_absolute_url() self.object.thread.delete() - else: - success_url = self.object.thread.get_absolute_url() self.object.delete() return HttpResponseRedirect(success_url) + else: raise PermissionDenied("Cannot delete a post you don't own in a competition you aren't organizing!") @@ -165,11 +170,15 @@ class DeleteThreadView(ForumBaseMixin, LoginRequiredMixin, DeleteView): model = Thread pk_url_kwarg = 'thread_pk' - def delete(self, request, *args, **kwargs): + def get_success_url(self): + thread = self.get_object() + return thread.forum.get_absolute_url() + + def form_valid(self, form): self.object = self.get_object() - if self.object.forum.competition.created_by == request.user or \ - self.object.started_by == request.user: + if self.object.forum.competition.created_by == self.request.user or \ + self.object.started_by == self.request.user: success_url = self.object.forum.get_absolute_url() self.object.delete()