Просмотр исходного кода

Remove override_acl from remaining tests

rafalp 6 лет назад
Родитель
Сommit
4a75270d25

+ 0 - 5
misago/acl/testutils.py

@@ -16,8 +16,3 @@ def fake_post_data(target, data_dict):
             else:
                 data_dict[field.html_name] = field.value()
     return data_dict
-
-
-def override_acl(user, new_acl):
-    """overrides user permissions with specified ones"""
-    raise Exception("override_acl has been removed")

+ 1 - 1
misago/threads/api/pollvotecreateendpoint.py

@@ -10,7 +10,7 @@ from misago.threads.serializers import PollSerializer, NewVoteSerializer
 def poll_vote_create(request, thread, poll):
     poll.make_choices_votes_aware(request.user)
 
-    allow_vote_poll(request.user, poll)
+    allow_vote_poll(request.user_acl, poll)
 
     serializer = NewVoteSerializer(
         data={

+ 3 - 1
misago/threads/api/postendpoints/patch_post.py

@@ -169,7 +169,9 @@ def bulk_patch_endpoint(request, thread):
 
 
 def clean_posts_for_patch(request, thread, posts_ids):
-    posts_queryset = exclude_invisible_posts(request.user, thread.category, thread.post_set)
+    posts_queryset = exclude_invisible_posts(
+        request.user_acl, thread.category, thread.post_set
+    )
     posts_queryset = posts_queryset.filter(
         id__in=posts_ids,
         is_event=False,

+ 5 - 5
misago/threads/api/threadpoll.py

@@ -47,7 +47,7 @@ class ViewSet(viewsets.ViewSet):
     @transaction.atomic
     def create(self, request, thread_pk):
         thread = self.get_thread(request, thread_pk)
-        allow_start_poll(request.user, thread)
+        allow_start_poll(request.user_acl, thread)
 
         try:
             if thread.poll and thread.poll.pk:
@@ -84,7 +84,7 @@ class ViewSet(viewsets.ViewSet):
         thread = self.get_thread(request, thread_pk)
         instance = self.get_poll(thread, pk)
 
-        allow_edit_poll(request.user, instance)
+        allow_edit_poll(request.user_acl, instance)
 
         serializer = EditPollSerializer(instance, data=request.data)
         serializer.is_valid(raise_exception=True)
@@ -103,7 +103,7 @@ class ViewSet(viewsets.ViewSet):
         thread = self.get_thread(request, thread_pk)
         instance = self.get_poll(thread, pk)
 
-        allow_delete_poll(request.user, instance)
+        allow_delete_poll(request.user_acl, instance)
 
         thread.poll.delete()
 
@@ -111,7 +111,7 @@ class ViewSet(viewsets.ViewSet):
         thread.save()
 
         return Response({
-            'can_start_poll': can_start_poll(request.user, thread),
+            'can_start_poll': can_start_poll(request.user_acl, thread),
         })
 
     @detail_route(methods=['get', 'post'])
@@ -138,7 +138,7 @@ class ViewSet(viewsets.ViewSet):
         except Poll.DoesNotExist:
             raise Http404()
 
-        allow_see_poll_votes(request.user, thread.poll)
+        allow_see_poll_votes(request.user_acl, thread.poll)
 
         choices = []
         voters = {}

+ 6 - 13
misago/threads/tests/test_gotoviews.py

@@ -1,10 +1,10 @@
 from django.utils import timezone
 
-from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.conf import settings
 from misago.readtracker.poststracker import save_read
 from misago.threads import testutils
+from misago.threads.test import patch_category_acl
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
@@ -233,24 +233,18 @@ class GotoBestAnswerTests(GotoViewTestCase):
 
 
 class GotoUnapprovedTests(GotoViewTestCase):
-    def grant_permission(self):
-        self.user.acl_cache['categories'][self.category.pk]['can_approve_content'] = 1
-        override_acl(self.user, self.user.acl_cache)
-
     def test_view_validates_permission(self):
         """view validates permission to see unapproved posts"""
         response = self.client.get(self.thread.get_unapproved_post_url())
         self.assertContains(response, "You need permission to approve content", status_code=403)
 
-        self.grant_permission()
-
-        response = self.client.get(self.thread.get_unapproved_post_url())
-        self.assertEqual(response.status_code, 302)
+        with patch_category_acl({"can_approve_content": True}):
+            response = self.client.get(self.thread.get_unapproved_post_url())
+            self.assertEqual(response.status_code, 302)
 
+    @patch_category_acl({"can_approve_content": True})
     def test_view_handles_no_unapproved_posts(self):
         """if thread has no unapproved posts, redirect to last post"""
-        self.grant_permission()
-
         response = self.client.get(self.thread.get_unapproved_post_url())
         self.assertEqual(response.status_code, 302)
         self.assertEqual(
@@ -258,6 +252,7 @@ class GotoUnapprovedTests(GotoViewTestCase):
             GOTO_URL % (self.thread.get_absolute_url(), self.thread.first_post_id)
         )
 
+    @patch_category_acl({"can_approve_content": True})
     def test_view_handles_unapproved_posts(self):
         """if thread has unapproved posts, redirect to first of them"""
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
@@ -267,8 +262,6 @@ class GotoUnapprovedTests(GotoViewTestCase):
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
             testutils.reply_thread(self.thread, posted_on=timezone.now())
 
-        self.grant_permission()
-
         response = self.client.get(self.thread.get_unapproved_post_url())
         self.assertEqual(response.status_code, 302)
         self.assertEqual(

+ 0 - 18
misago/threads/tests/test_post_mentions.py

@@ -2,13 +2,11 @@ from django.contrib.auth import get_user_model
 from django.test.client import BOUNDARY, MULTIPART_CONTENT, encode_multipart
 from django.urls import reverse
 
-from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.markup.mentions import MENTIONS_LIMIT
 from misago.threads import testutils
 from misago.users.testutils import AuthenticatedUserTestCase
 
-
 UserModel = get_user_model()
 
 
@@ -18,7 +16,6 @@ class PostMentionsTests(AuthenticatedUserTestCase):
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)
-        self.override_acl()
 
         self.post_link = reverse(
             'misago:api:thread-post-list', kwargs={
@@ -26,18 +23,6 @@ class PostMentionsTests(AuthenticatedUserTestCase):
             }
         )
 
-    def override_acl(self):
-        new_acl = self.user.acl_cache
-        new_acl['categories'][self.category.pk].update({
-            'can_see': 1,
-            'can_browse': 1,
-            'can_start_threads': 1,
-            'can_reply_threads': 1,
-            'can_edit_posts': 1,
-        })
-
-        override_acl(self.user, new_acl)
-
     def put(self, url, data=None):
         content = encode_multipart(BOUNDARY, data or {})
         return self.client.put(url, content, content_type=MULTIPART_CONTENT)
@@ -129,7 +114,6 @@ class PostMentionsTests(AuthenticatedUserTestCase):
             }
         )
 
-        self.override_acl()
         response = self.put(
             edit_link,
             data={
@@ -142,7 +126,6 @@ class PostMentionsTests(AuthenticatedUserTestCase):
         self.assertEqual(list(post.mentions.order_by('id')), [user_a, user_b])
 
         # remove first mention from post - should preserve mentions
-        self.override_acl()
         response = self.put(
             edit_link, data={
                 'post': "This is test response, @%s!" % user_b,
@@ -154,7 +137,6 @@ class PostMentionsTests(AuthenticatedUserTestCase):
         self.assertEqual(list(post.mentions.order_by('id')), [user_a, user_b])
 
         # remove mentions from post - should preserve mentions
-        self.override_acl()
         response = self.put(
             edit_link, data={
                 'post': "This is test response!",

+ 18 - 49
misago/threads/tests/test_thread_bulkpatch_api.py

@@ -2,10 +2,10 @@ import json
 
 from django.urls import reverse
 
-from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.threads import testutils
 from misago.threads.models import Thread
+from misago.threads.test import patch_category_acl, patch_other_category_acl
 
 from .test_threads_api import ThreadsApiTestCase
 
@@ -183,10 +183,9 @@ class ThreadAddAclApiTests(ThreadsBulkPatchApiTestCase):
 
 
 class BulkThreadChangeTitleApiTests(ThreadsBulkPatchApiTestCase):
+    @patch_category_acl({"can_edit_threads": 2})
     def test_change_thread_title(self):
         """api changes thread title and resyncs the category"""
-        self.override_acl({'can_edit_threads': 2})
-
         response = self.patch(
             self.api_link,
             {
@@ -210,13 +209,12 @@ class BulkThreadChangeTitleApiTests(ThreadsBulkPatchApiTestCase):
         for thread in Thread.objects.filter(id__in=self.ids):
             self.assertEqual(thread.title, 'Changed the title!')
 
-        category = Category.objects.get(pk=self.category.pk)
+        category = Category.objects.get(pk=self.category.id)
         self.assertEqual(category.last_thread_title, 'Changed the title!')
 
+    @patch_category_acl({"can_edit_threads": 0})
     def test_change_thread_title_no_permission(self):
         """api validates permission to change title, returns errors"""
-        self.override_acl({'can_edit_threads': 0})
-
         response = self.patch(
             self.api_link,
             {
@@ -246,46 +244,19 @@ class BulkThreadMoveApiTests(ThreadsBulkPatchApiTestCase):
         super().setUp()
 
         Category(
-            name='Category B',
-            slug='category-b',
+            name='Other Category',
+            slug='other-category',
         ).insert_at(
             self.category,
             position='last-child',
             save=True,
         )
-        self.category_b = Category.objects.get(slug='category-b')
-
-    def override_other_acl(self, acl):
-        other_category_acl = self.user.acl_cache['categories'][self.category.pk].copy()
-        other_category_acl.update({
-            'can_see': 1,
-            'can_browse': 1,
-            'can_see_all_threads': 1,
-            'can_see_own_threads': 0,
-            'can_hide_threads': 0,
-            'can_approve_content': 0,
-        })
-        other_category_acl.update(acl)
-
-        categories_acl = self.user.acl_cache['categories']
-        categories_acl[self.category_b.pk] = other_category_acl
-
-        visible_categories = [self.category.pk]
-        if other_category_acl['can_see']:
-            visible_categories.append(self.category_b.pk)
-
-        override_acl(
-            self.user, {
-                'visible_categories': visible_categories,
-                'categories': categories_acl,
-            }
-        )
+        self.other_category = Category.objects.get(slug='other-category')
 
+    @patch_category_acl({"can_move_threads": True})
+    @patch_other_category_acl({"can_start_threads": 2})
     def test_move_thread(self):
         """api moves threads to other category and syncs both categories"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_start_threads': 2})
-
         response = self.patch(
             self.api_link,
             {
@@ -294,7 +265,7 @@ class BulkThreadMoveApiTests(ThreadsBulkPatchApiTestCase):
                     {
                         'op': 'replace',
                         'path': 'category',
-                        'value': self.category_b.pk,
+                        'value': self.other_category.id,
                     },
                     {
                         'op': 'replace',
@@ -309,23 +280,22 @@ class BulkThreadMoveApiTests(ThreadsBulkPatchApiTestCase):
         response_json = response.json()
         for i, thread in enumerate(self.threads):
             self.assertEqual(response_json[i]['id'], thread.id)
-            self.assertEqual(response_json[i]['category'], self.category_b.pk)
+            self.assertEqual(response_json[i]['category'], self.other_category.id)
 
         for thread in Thread.objects.filter(id__in=self.ids):
-            self.assertEqual(thread.category_id, self.category_b.pk)
+            self.assertEqual(thread.category_id, self.other_category.id)
 
-        category = Category.objects.get(pk=self.category.pk)
+        category = Category.objects.get(pk=self.category.id)
         self.assertEqual(category.threads, self.category.threads - 3)
 
-        new_category = Category.objects.get(pk=self.category_b.pk)
+        new_category = Category.objects.get(pk=self.other_category.id)
         self.assertEqual(new_category.threads, 3)
 
 
 class BulkThreadsHideApiTests(ThreadsBulkPatchApiTestCase):
+    @patch_category_acl({"can_hide_threads": 1})
     def test_hide_thread(self):
         """api makes it possible to hide thread"""
-        self.override_acl({'can_hide_threads': 1})
-
         response = self.patch(
             self.api_link,
             {
@@ -349,11 +319,12 @@ class BulkThreadsHideApiTests(ThreadsBulkPatchApiTestCase):
         for thread in Thread.objects.filter(id__in=self.ids):
             self.assertTrue(thread.is_hidden)
 
-        category = Category.objects.get(pk=self.category.pk)
+        category = Category.objects.get(pk=self.category.id)
         self.assertNotIn(category.last_thread_id, self.ids)
 
 
 class BulkThreadsApproveApiTests(ThreadsBulkPatchApiTestCase):
+    @patch_category_acl({"can_approve_content": True})
     def test_approve_thread(self):
         """api approvse threads and syncs category"""
         for thread in self.threads:
@@ -369,8 +340,6 @@ class BulkThreadsApproveApiTests(ThreadsBulkPatchApiTestCase):
         self.category.synchronize()
         self.category.save()
 
-        self.override_acl({'can_approve_content': 1})
-
         response = self.patch(
             self.api_link,
             {
@@ -396,5 +365,5 @@ class BulkThreadsApproveApiTests(ThreadsBulkPatchApiTestCase):
             self.assertFalse(thread.is_unapproved)
             self.assertFalse(thread.has_unapproved_posts)
 
-        category = Category.objects.get(pk=self.category.pk)
+        category = Category.objects.get(pk=self.category.id)
         self.assertIn(category.last_thread_id, self.ids)

+ 0 - 25
misago/threads/tests/test_thread_poll_api.py

@@ -2,7 +2,6 @@ import json
 
 from django.urls import reverse
 
-from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.threads import testutils
 from misago.users.testutils import AuthenticatedUserTestCase
@@ -14,7 +13,6 @@ class ThreadPollApiTestCase(AuthenticatedUserTestCase):
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(self.category, poster=self.user)
-        self.override_acl()
 
         self.api_link = reverse(
             'misago:api:thread-poll-list', kwargs={
@@ -28,29 +26,6 @@ class ThreadPollApiTestCase(AuthenticatedUserTestCase):
     def put(self, url, data=None):
         return self.client.put(url, json.dumps(data or {}), content_type='application/json')
 
-    def override_acl(self, user=None, category=None):
-        new_acl = self.user.acl_cache
-        new_acl['categories'][self.category.pk].update({
-            'can_see': 1,
-            'can_browse': 1,
-            'can_close_threads': 0,
-        })
-
-        new_acl.update({
-            'can_start_polls': 1,
-            'can_edit_polls': 1,
-            'can_delete_polls': 1,
-            'poll_edit_time': 0,
-            'can_always_see_poll_voters': 0,
-        })
-
-        if user:
-            new_acl.update(user)
-        if category:
-            new_acl['categories'][self.category.pk].update(category)
-
-        override_acl(self.user, new_acl)
-
     def mock_poll(self):
         self.poll = self.thread.poll = testutils.post_poll(self.thread, self.user)
 

+ 28 - 14
misago/threads/tests/test_thread_pollcreate_api.py

@@ -1,7 +1,9 @@
 from django.urls import reverse
 
+from misago.acl.test import patch_user_acl
 from misago.threads.models import Poll, Thread
 from misago.threads.serializers.poll import MAX_POLL_OPTIONS
+from misago.threads.test import patch_category_acl
 
 from .test_thread_poll_api import ThreadPollApiTestCase
 
@@ -36,20 +38,19 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
         response = self.post(api_link)
         self.assertEqual(response.status_code, 404)
 
+    @patch_user_acl({"can_start_polls": 0})
     def test_no_permission(self):
         """api validates that user has permission to start poll in thread"""
-        self.override_acl({'can_start_polls': 0})
-
         response = self.post(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.json(), {
             "detail": "You can't start polls."
         })
 
-    def test_no_permission_closed_thread(self):
+    @patch_user_acl({"can_start_polls": 1})
+    @patch_category_acl({"can_close_threads": False})
+    def test_closed_thread_no_permission(self):
         """api validates that user has permission to start poll in closed thread"""
-        self.override_acl(category={'can_close_threads': 0})
-
         self.thread.is_closed = True
         self.thread.save()
 
@@ -59,15 +60,20 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
             "detail": "This thread is closed. You can't start polls in it."
         })
 
-        self.override_acl(category={'can_close_threads': 1})
+    @patch_user_acl({"can_start_polls": 1})
+    @patch_category_acl({"can_close_threads": True})
+    def test_closed_thread(self):
+        """api validates that user has permission to start poll in closed thread"""
+        self.thread.is_closed = True
+        self.thread.save()
 
         response = self.post(self.api_link)
         self.assertEqual(response.status_code, 400)
 
-    def test_no_permission_closed_category(self):
+    @patch_user_acl({"can_start_polls": 1})
+    @patch_category_acl({"can_close_threads": False})
+    def test_closed_category_no_permission(self):
         """api validates that user has permission to start poll in closed category"""
-        self.override_acl(category={'can_close_threads': 0})
-
         self.category.is_closed = True
         self.category.save()
 
@@ -77,15 +83,19 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
             "detail": "This category is closed. You can't start polls in it."
         })
 
-        self.override_acl(category={'can_close_threads': 1})
+    @patch_user_acl({"can_start_polls": 1})
+    @patch_category_acl({"can_close_threads": True})
+    def test_closed_category(self):
+        """api validates that user has permission to start poll in closed category"""
+        self.category.is_closed = True
+        self.category.save()
 
         response = self.post(self.api_link)
         self.assertEqual(response.status_code, 400)
 
-    def test_no_permission_other_user_thread(self):
+    @patch_user_acl({"can_start_polls": 1})
+    def test_other_user_thread_no_permission(self):
         """api validates that user has permission to start poll in other user's thread"""
-        self.override_acl({'can_start_polls': 1})
-
         self.thread.starter = None
         self.thread.save()
 
@@ -95,7 +105,11 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
             "detail": "You can't start polls in other users threads."
         })
 
-        self.override_acl({'can_start_polls': 2})
+    @patch_user_acl({"can_start_polls": 2})
+    def test_other_user_thread(self):
+        """api validates that user has permission to start poll in other user's thread"""
+        self.thread.starter = None
+        self.thread.save()
 
         response = self.post(self.api_link)
         self.assertEqual(response.status_code, 400)

+ 24 - 16
misago/threads/tests/test_thread_polldelete_api.py

@@ -3,7 +3,9 @@ from datetime import timedelta
 from django.urls import reverse
 from django.utils import timezone
 
+from misago.acl.test import patch_user_acl
 from misago.threads.models import Poll, PollVote, Thread
+from misago.threads.test import patch_category_acl
 
 from .test_thread_poll_api import ThreadPollApiTestCase
 
@@ -73,20 +75,18 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
         response = self.client.delete(api_link)
         self.assertEqual(response.status_code, 404)
 
+    @patch_user_acl({"can_delete_polls": 0})
     def test_no_permission(self):
         """api validates that user has permission to delete poll in thread"""
-        self.override_acl({'can_delete_polls': 0})
-
         response = self.client.delete(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.json(), {
             "detail": "You can't delete polls."
         })
 
+    @patch_user_acl({"can_delete_polls": 1, "poll_edit_time": 5})
     def test_no_permission_timeout(self):
         """api validates that user's window to delete poll in thread has closed"""
-        self.override_acl({'can_delete_polls': 1, 'poll_edit_time': 5})
-
         self.poll.posted_on = timezone.now() - timedelta(minutes=15)
         self.poll.save()
 
@@ -96,10 +96,9 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
             "detail": "You can't delete polls that are older than 5 minutes."
         })
 
+    @patch_user_acl({"can_delete_polls": 1})
     def test_no_permission_poll_closed(self):
         """api validates that user's window to delete poll in thread has closed"""
-        self.override_acl({'can_delete_polls': 1})
-
         self.poll.posted_on = timezone.now() - timedelta(days=15)
         self.poll.length = 5
         self.poll.save()
@@ -110,10 +109,9 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
             "detail": "This poll is over. You can't delete it."
         })
 
+    @patch_user_acl({"can_delete_polls": 1})
     def test_no_permission_other_user_poll(self):
         """api validates that user has permission to delete other user poll in thread"""
-        self.override_acl({'can_delete_polls': 1})
-
         self.poll.poster = None
         self.poll.save()
 
@@ -123,10 +121,10 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
             "detail": "You can't delete other users polls in this category."
         })
 
+    @patch_user_acl({"can_delete_polls": 1})
+    @patch_category_acl({"can_close_threads": False})
     def test_no_permission_closed_thread(self):
         """api validates that user has permission to delete poll in closed thread"""
-        self.override_acl(category={'can_close_threads': 0})
-
         self.thread.is_closed = True
         self.thread.save()
 
@@ -136,15 +134,20 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
             "detail": "This thread is closed. You can't delete polls in it."
         })
 
-        self.override_acl(category={'can_close_threads': 1})
+    @patch_user_acl({"can_delete_polls": 1})
+    @patch_category_acl({"can_close_threads": True})
+    def test_closed_thread(self):
+        """api validates that user has permission to delete poll in closed thread"""
+        self.thread.is_closed = True
+        self.thread.save()
 
         response = self.client.delete(self.api_link)
         self.assertEqual(response.status_code, 200)
 
+    @patch_user_acl({"can_delete_polls": 1})
+    @patch_category_acl({"can_close_threads": False})
     def test_no_permission_closed_category(self):
         """api validates that user has permission to delete poll in closed category"""
-        self.override_acl(category={'can_close_threads': 0})
-
         self.category.is_closed = True
         self.category.save()
 
@@ -154,11 +157,17 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
             "detail": "This category is closed. You can't delete polls in it."
         })
 
-        self.override_acl(category={'can_close_threads': 1})
+    @patch_user_acl({"can_delete_polls": 1})
+    @patch_category_acl({"can_close_threads": True})
+    def test_closed_category(self):
+        """api validates that user has permission to delete poll in closed category"""
+        self.category.is_closed = True
+        self.category.save()
 
         response = self.client.delete(self.api_link)
         self.assertEqual(response.status_code, 200)
 
+    @patch_user_acl({"can_delete_polls": 1, "poll_edit_time": 5})
     def test_poll_delete(self):
         """api deletes poll and associated votes"""
         response = self.client.delete(self.api_link)
@@ -173,10 +182,9 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
         thread = Thread.objects.get(pk=self.thread.pk)
         self.assertFalse(thread.has_poll)
 
+    @patch_user_acl({"can_delete_polls": 2, "poll_edit_time": 5})
     def test_other_user_poll_delete(self):
         """api deletes other user's poll and associated votes, even if its over"""
-        self.override_acl({'can_delete_polls': 2, 'poll_edit_time': 5})
-
         self.poll.poster = None
         self.poll.posted_on = timezone.now() - timedelta(days=15)
         self.poll.length = 5

+ 23 - 16
misago/threads/tests/test_thread_polledit_api.py

@@ -3,7 +3,9 @@ from datetime import timedelta
 from django.urls import reverse
 from django.utils import timezone
 
+from misago.acl.test import patch_user_acl
 from misago.threads.serializers.poll import MAX_POLL_OPTIONS
+from misago.threads.test import patch_category_acl
 
 from .test_thread_poll_api import ThreadPollApiTestCase
 
@@ -73,20 +75,18 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         response = self.put(api_link)
         self.assertEqual(response.status_code, 404)
 
+    @patch_user_acl({"can_edit_polls": 0})
     def test_no_permission(self):
         """api validates that user has permission to edit poll in thread"""
-        self.override_acl({'can_edit_polls': 0})
-
         response = self.put(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.json(), {
             "detail": "You can't edit polls.",
         })
 
+    @patch_user_acl({"can_edit_polls": 1, "poll_edit_time": 5})
     def test_no_permission_timeout(self):
         """api validates that user's window to edit poll in thread has closed"""
-        self.override_acl({'can_edit_polls': 1, 'poll_edit_time': 5})
-
         self.poll.posted_on = timezone.now() - timedelta(minutes=15)
         self.poll.save()
 
@@ -96,10 +96,9 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
             "detail": "You can't edit polls that are older than 5 minutes.",
         })
 
+    @patch_user_acl({"can_edit_polls": 1})
     def test_no_permission_poll_closed(self):
         """api validates that user's window to edit poll in thread has closed"""
-        self.override_acl({'can_edit_polls': 1})
-
         self.poll.posted_on = timezone.now() - timedelta(days=15)
         self.poll.length = 5
         self.poll.save()
@@ -110,10 +109,9 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
             "detail": "This poll is over. You can't edit it.",
         })
 
+    @patch_user_acl({"can_edit_polls": 1})
     def test_no_permission_other_user_poll(self):
         """api validates that user has permission to edit other user poll in thread"""
-        self.override_acl({'can_edit_polls': 1})
-
         self.poll.poster = None
         self.poll.save()
 
@@ -123,10 +121,10 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
             "detail": "You can't edit other users polls in this category.",
         })
 
+    @patch_user_acl({"can_edit_polls": 1})
+    @patch_category_acl({"can_close_threads": False})
     def test_no_permission_closed_thread(self):
         """api validates that user has permission to edit poll in closed thread"""
-        self.override_acl(category={'can_close_threads': 0})
-
         self.thread.is_closed = True
         self.thread.save()
 
@@ -136,15 +134,20 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
             "detail": "This thread is closed. You can't edit polls in it.",
         })
 
-        self.override_acl(category={'can_close_threads': 1})
+    @patch_user_acl({"can_edit_polls": 1})
+    @patch_category_acl({"can_close_threads": True})
+    def test_closed_thread(self):
+        """api validates that user has permission to edit poll in closed thread"""
+        self.thread.is_closed = True
+        self.thread.save()
 
         response = self.put(self.api_link)
         self.assertEqual(response.status_code, 400)
 
+    @patch_user_acl({"can_edit_polls": 1})
+    @patch_category_acl({"can_close_threads": False})
     def test_no_permission_closed_category(self):
         """api validates that user has permission to edit poll in closed category"""
-        self.override_acl(category={'can_close_threads': 0})
-
         self.category.is_closed = True
         self.category.save()
 
@@ -154,7 +157,12 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
             "detail": "This category is closed. You can't edit polls in it.",
         })
 
-        self.override_acl(category={'can_close_threads': 1})
+    @patch_user_acl({"can_edit_polls": 1})
+    @patch_category_acl({"can_close_threads": True})
+    def test_closed_category(self):
+        """api validates that user has permission to edit poll in closed category"""
+        self.category.is_closed = True
+        self.category.save()
 
         response = self.put(self.api_link)
         self.assertEqual(response.status_code, 400)
@@ -513,10 +521,9 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
 
         self.assertEqual(self.user.audittrail_set.count(), 1)
 
+    @patch_user_acl({"can_edit_polls": 2, "poll_edit_time": 5})
     def test_moderate_user_poll(self):
         """api edits all poll choices out in other users poll, even if its over"""
-        self.override_acl({'can_edit_polls': 2, 'poll_edit_time': 5})
-
         self.poll.poster = None
         self.poll.posted_on = timezone.now() - timedelta(days=15)
         self.poll.length = 5

+ 22 - 18
misago/threads/tests/test_thread_pollvotes_api.py

@@ -4,7 +4,9 @@ from django.contrib.auth import get_user_model
 from django.urls import reverse
 from django.utils import timezone
 
+from misago.acl.test import patch_user_acl
 from misago.threads.models import Poll
+from misago.threads.test import patch_category_acl
 
 from .test_thread_poll_api import ThreadPollApiTestCase
 
@@ -88,10 +90,9 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
         response = self.client.get(api_link)
         self.assertEqual(response.status_code, 404)
 
+    @patch_user_acl({"can_always_see_poll_voters": False})
     def test_no_permission(self):
         """api chcecks permission to see poll voters"""
-        self.override_acl({'can_always_see_poll_voters': False})
-
         self.poll.is_public = False
         self.poll.save()
 
@@ -128,10 +129,9 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
         self.assertEqual([[v['url'] for v in c['voters']] for c in response_json][0][0],
                          user.get_absolute_url())
 
+    @patch_user_acl({"can_always_see_poll_voters": True})
     def test_get_votes_private_poll(self):
         """api returns list of voters on private poll for user with permission"""
-        self.override_acl({'can_always_see_poll_voters': True})
-
         self.poll.is_public = False
         self.poll.save()
 
@@ -271,10 +271,9 @@ class ThreadPostVotesTests(ThreadPollApiTestCase):
             "detail": 'Expected a list of items but got type "dict".',
         })
 
-    def test_vote_in_closed_thread(self):
+    @patch_category_acl({"can_close_threads": False})
+    def test_vote_in_closed_thread_no_permission(self):
         """api validates is user has permission to vote poll in closed thread"""
-        self.override_acl(category={'can_close_threads': 0})
-
         self.thread.is_closed = True
         self.thread.save()
 
@@ -286,18 +285,20 @@ class ThreadPostVotesTests(ThreadPollApiTestCase):
             "detail": "This thread is closed. You can't vote in it.",
         })
 
-        self.override_acl(category={'can_close_threads': 1})
+    @patch_category_acl({"can_close_threads": True})
+    def test_vote_in_closed_thread(self):
+        """api validates is user has permission to vote poll in closed thread"""
+        self.thread.is_closed = True
+        self.thread.save()
+
+        self.delete_user_votes()
 
         response = self.post(self.api_link)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {
-            "detail": 'Expected a list of items but got type "dict".',
-        })
 
-    def test_vote_in_closed_category(self):
+    @patch_category_acl({"can_close_threads": False})
+    def test_vote_in_closed_category_no_permission(self):
         """api validates is user has permission to vote poll in closed category"""
-        self.override_acl(category={'can_close_threads': 0})
-
         self.category.is_closed = True
         self.category.save()
 
@@ -309,13 +310,16 @@ class ThreadPostVotesTests(ThreadPollApiTestCase):
             "detail": "This category is closed. You can't vote in it.",
         })
 
-        self.override_acl(category={'can_close_threads': 1})
+    @patch_category_acl({"can_close_threads": True})
+    def test_vote_in_closed_category(self):
+        """api validates is user has permission to vote poll in closed category"""
+        self.category.is_closed = True
+        self.category.save()
+
+        self.delete_user_votes()
 
         response = self.post(self.api_link)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {
-            "detail": 'Expected a list of items but got type "dict".',
-        })
 
     def test_vote_in_finished_poll(self):
         """api valdiates if poll has finished before letting user to vote in it"""

+ 4 - 25
misago/threads/tests/test_thread_postbulkpatch_api.py

@@ -4,10 +4,10 @@ from datetime import timedelta
 from django.urls import reverse
 from django.utils import timezone
 
-from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.threads import testutils
 from misago.threads.models import Post, Thread
+from misago.threads.test import patch_category_acl
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
@@ -35,21 +35,6 @@ class ThreadPostBulkPatchApiTestCase(AuthenticatedUserTestCase):
     def patch(self, api_link, ops):
         return self.client.patch(api_link, json.dumps(ops), content_type="application/json")
 
-    def override_acl(self, extra_acl=None):
-        new_acl = self.user.acl_cache
-        new_acl['categories'][self.category.pk].update({
-            'can_see': 1,
-            'can_browse': 1,
-            'can_start_threads': 0,
-            'can_reply_threads': 0,
-            'can_edit_posts': 1,
-        })
-
-        if extra_acl:
-            new_acl['categories'][self.category.pk].update(extra_acl)
-
-        override_acl(self.user, new_acl)
-
 
 class BulkPatchSerializerTests(ThreadPostBulkPatchApiTestCase):
     def test_invalid_input_type(self):
@@ -220,13 +205,9 @@ class PostsAddAclApiTests(ThreadPostBulkPatchApiTestCase):
 
 
 class BulkPostProtectApiTests(ThreadPostBulkPatchApiTestCase):
+    @patch_category_acl({"can_protect_posts": True, "can_edit_posts": 2})
     def test_protect_post(self):
         """api makes it possible to protect posts"""
-        self.override_acl({
-            'can_protect_posts': 1,
-            'can_edit_posts': 2,
-        })
-
         response = self.patch(
             self.api_link, {
                 'ids': self.ids,
@@ -249,10 +230,9 @@ class BulkPostProtectApiTests(ThreadPostBulkPatchApiTestCase):
         for post in Post.objects.filter(id__in=self.ids):
             self.assertTrue(post.is_protected)
 
+    @patch_category_acl({"can_protect_posts": False})
     def test_protect_post_no_permission(self):
         """api validates permission to protect posts and returns errors"""
-        self.override_acl({'can_protect_posts': 0})
-
         response = self.patch(
             self.api_link, {
                 'ids': self.ids,
@@ -280,6 +260,7 @@ class BulkPostProtectApiTests(ThreadPostBulkPatchApiTestCase):
 
 
 class BulkPostsApproveApiTests(ThreadPostBulkPatchApiTestCase):
+    @patch_category_acl({"can_approve_content": True})
     def test_approve_post(self):
         """api resyncs thread and categories on posts approval"""
         for post in self.posts:
@@ -291,8 +272,6 @@ class BulkPostsApproveApiTests(ThreadPostBulkPatchApiTestCase):
 
         self.assertNotIn(self.thread.last_post_id, self.ids)
 
-        self.override_acl({'can_approve_content': 1})
-
         response = self.patch(
             self.api_link, {
                 'ids': self.ids,

+ 6 - 6
misago/threads/tests/test_thread_postedits_api.py

@@ -1,6 +1,7 @@
 from django.urls import reverse
 
 from misago.threads import testutils
+from misago.threads.test import patch_category_acl
 
 from .test_threads_api import ThreadsApiTestCase
 
@@ -19,8 +20,6 @@ class ThreadPostEditsApiTestCase(ThreadsApiTestCase):
             }
         )
 
-        self.override_acl()
-
     def mock_edit_record(self):
         edits_record = [
             self.post.edits_record.create(
@@ -135,18 +134,19 @@ class ThreadPostPostEditTests(ThreadPostEditsApiTestCase):
         super().setUp()
         self.edits = self.mock_edit_record()
 
-        self.override_acl({'can_edit_posts': 2})
-
+    @patch_category_acl({"can_edit_posts": 2})
     def test_empty_edit_id(self):
         """api handles empty edit in querystring"""
         response = self.client.post('%s?edit=' % self.api_link)
         self.assertEqual(response.status_code, 404)
 
+    @patch_category_acl({"can_edit_posts": 2})
     def test_invalid_edit_id(self):
         """api handles invalid edit in querystring"""
         response = self.client.post('%s?edit=dsa67d8sa68' % self.api_link)
         self.assertEqual(response.status_code, 404)
 
+    @patch_category_acl({"can_edit_posts": 2})
     def test_nonexistant_edit_id(self):
         """api handles nonexistant edit in querystring"""
         response = self.client.post('%s?edit=1321' % self.api_link)
@@ -159,13 +159,13 @@ class ThreadPostPostEditTests(ThreadPostEditsApiTestCase):
         response = self.client.post('%s?edit=%s' % (self.api_link, self.edits[0].id))
         self.assertEqual(response.status_code, 403)
 
+    @patch_category_acl({"can_edit_posts": 0})
     def test_no_permission(self):
         """api validates permission to revert post"""
-        self.override_acl({'can_edit_posts': 0})
-
         response = self.client.post('%s?edit=1321' % self.api_link)
         self.assertEqual(response.status_code, 403)
 
+    @patch_category_acl({"can_edit_posts": 2})
     def test_revert_post(self):
         """api reverts post to version from before specified edit"""
         response = self.client.post('%s?edit=%s' % (self.api_link, self.edits[0].id))

+ 5 - 4
misago/threads/tests/test_thread_postlikes_api.py

@@ -1,6 +1,7 @@
 from django.urls import reverse
 
 from misago.threads import testutils
+from misago.threads.test import patch_category_acl
 from misago.threads.serializers import PostLikeSerializer
 
 from .test_threads_api import ThreadsApiTestCase
@@ -20,32 +21,32 @@ class ThreadPostLikesApiTestCase(ThreadsApiTestCase):
             }
         )
 
+    @patch_category_acl({"can_see_posts_likes": 0})
     def test_no_permission(self):
         """api errors if user has no permission to see likes"""
-        self.override_acl({'can_see_posts_likes': 0})
-
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEquals(response.json(), {
             "detail": "You can't see who liked this post."
         })
 
+    @patch_category_acl({"can_see_posts_likes": 1})
     def test_no_permission_to_list(self):
         """api errors if user has no permission to see likes, but can see likes count"""
-        self.override_acl({'can_see_posts_likes': 1})
-
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEquals(response.json(), {
             "detail": "You can't see who liked this post."
         })
 
+    @patch_category_acl({"can_see_posts_likes": 2})
     def test_no_likes(self):
         """api returns empty list if post has no likes"""
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.json(), [])
 
+    @patch_category_acl({"can_see_posts_likes": 2})
     def test_likes(self):
         """api returns list of likes"""
         like = testutils.like_post(self.post, self.user)

+ 6 - 3
misago/threads/views/goto.py

@@ -19,9 +19,13 @@ class GotoView(View):
         thread = self.get_thread(request, pk, slug).unwrap()
         self.test_permissions(request, thread)
 
-        posts_queryset = exclude_invisible_posts(request.user, thread.category, thread.post_set)
+        posts_queryset = exclude_invisible_posts(
+            request.user_acl, thread.category, thread.post_set
+        )
 
-        target_post = self.get_target_post(request.user, thread, posts_queryset.order_by('id'), **kwargs)
+        target_post = self.get_target_post(
+            request.user, thread, posts_queryset.order_by('id'), **kwargs
+        )
         target_page = self.compute_post_page(target_post, posts_queryset)
 
         return self.get_redirect(thread, target_post, target_page)
@@ -38,7 +42,6 @@ class GotoView(View):
     def compute_post_page(self, target_post, posts_queryset):
         # filter out events, order queryset
         posts_queryset = posts_queryset.filter(is_event=False).order_by('id')
-
         thread_length = posts_queryset.count()
 
         # is target an event?