Browse Source

Make threads patch api tests pass

rafalp 6 years ago
parent
commit
95f9ff5046

+ 18 - 18
misago/threads/api/threadendpoints/patch.py

@@ -57,7 +57,7 @@ def patch_title(request, thread, value):
     except ValidationError as e:
     except ValidationError as e:
         raise PermissionDenied(e.args[0])
         raise PermissionDenied(e.args[0])
 
 
-    allow_edit_thread(request.user, thread)
+    allow_edit_thread(request.user_acl, thread)
 
 
     moderation.change_thread_title(request, thread, value_cleaned)
     moderation.change_thread_title(request, thread, value_cleaned)
     return {'title': thread.title}
     return {'title': thread.title}
@@ -67,7 +67,7 @@ thread_patch_dispatcher.replace('title', patch_title)
 
 
 
 
 def patch_weight(request, thread, value):
 def patch_weight(request, thread, value):
-    allow_pin_thread(request.user, thread)
+    allow_pin_thread(request.user_acl, thread)
 
 
     if not thread.acl.get('can_pin_globally') and thread.weight == 2:
     if not thread.acl.get('can_pin_globally') and thread.weight == 2:
         raise PermissionDenied(_("You can't change globally pinned threads weights in this category."))
         raise PermissionDenied(_("You can't change globally pinned threads weights in this category."))
@@ -89,7 +89,7 @@ thread_patch_dispatcher.replace('weight', patch_weight)
 
 
 
 
 def patch_move(request, thread, value):
 def patch_move(request, thread, value):
-    allow_move_thread(request.user, thread)
+    allow_move_thread(request.user_acl, thread)
 
 
     category_pk = get_int_or_404(value)
     category_pk = get_int_or_404(value)
     new_category = get_object_or_404(
     new_category = get_object_or_404(
@@ -97,9 +97,9 @@ def patch_move(request, thread, value):
     )
     )
 
 
     add_acl(request.user_acl, new_category)
     add_acl(request.user_acl, new_category)
-    allow_see_category(request.user, new_category)
-    allow_browse_category(request.user, new_category)
-    allow_start_thread(request.user, new_category)
+    allow_see_category(request.user_acl, new_category)
+    allow_browse_category(request.user_acl, new_category)
+    allow_start_thread(request.user_acl, new_category)
 
 
     if new_category == thread.category:
     if new_category == thread.category:
         raise PermissionDenied(_("You can't move thread to the category it's already in."))
         raise PermissionDenied(_("You can't move thread to the category it's already in."))
@@ -123,7 +123,7 @@ thread_patch_dispatcher.replace('flatten-categories', patch_flatten_categories)
 
 
 
 
 def patch_is_unapproved(request, thread, value):
 def patch_is_unapproved(request, thread, value):
-    allow_approve_thread(request.user, thread)
+    allow_approve_thread(request.user_acl, thread)
 
 
     if value:
     if value:
         raise PermissionDenied(_("Content approval can't be reversed."))
         raise PermissionDenied(_("Content approval can't be reversed."))
@@ -159,10 +159,10 @@ thread_patch_dispatcher.replace('is-closed', patch_is_closed)
 
 
 def patch_is_hidden(request, thread, value):
 def patch_is_hidden(request, thread, value):
     if value:
     if value:
-        allow_hide_thread(request.user, thread)
+        allow_hide_thread(request.user_acl, thread)
         moderation.hide_thread(request, thread)
         moderation.hide_thread(request, thread)
     else:
     else:
-        allow_unhide_thread(request.user, thread)
+        allow_unhide_thread(request.user_acl, thread)
         moderation.unhide_thread(request, thread)
         moderation.unhide_thread(request, thread)
 
 
     return {'is_hidden': thread.is_hidden}
     return {'is_hidden': thread.is_hidden}
@@ -205,20 +205,20 @@ def patch_best_answer(request, thread, value):
     except (TypeError, ValueError):
     except (TypeError, ValueError):
         raise PermissionDenied(_("A valid integer is required."))
         raise PermissionDenied(_("A valid integer is required."))
 
 
-    allow_mark_best_answer(request.user, thread)
+    allow_mark_best_answer(request.user_acl, thread)
 
 
     post = get_object_or_404(thread.post_set, id=post_id)
     post = get_object_or_404(thread.post_set, id=post_id)
     post.category = thread.category
     post.category = thread.category
     post.thread = thread
     post.thread = thread
 
 
-    allow_see_post(request.user, post)
-    allow_mark_as_best_answer(request.user, post)
+    allow_see_post(request.user_acl, post)
+    allow_mark_as_best_answer(request.user_acl, post)
 
 
     if post.is_best_answer:
     if post.is_best_answer:
         raise PermissionDenied(_("This post is already marked as thread's best answer."))
         raise PermissionDenied(_("This post is already marked as thread's best answer."))
 
 
     if thread.has_best_answer:
     if thread.has_best_answer:
-        allow_change_best_answer(request.user, thread)
+        allow_change_best_answer(request.user_acl, thread)
         
         
     thread.set_best_answer(request.user, post)
     thread.set_best_answer(request.user, post)
     thread.save()
     thread.save()
@@ -250,7 +250,7 @@ def patch_unmark_best_answer(request, thread, value):
         raise PermissionDenied(
         raise PermissionDenied(
             _("This post can't be unmarked because it's not currently marked as best answer."))
             _("This post can't be unmarked because it's not currently marked as best answer."))
 
 
-    allow_unmark_best_answer(request.user, thread)
+    allow_unmark_best_answer(request.user_acl, thread)
     thread.clear_best_answer()
     thread.clear_best_answer()
     thread.save()
     thread.save()
 
 
@@ -268,7 +268,7 @@ thread_patch_dispatcher.remove('best-answer', patch_unmark_best_answer)
 
 
 
 
 def patch_add_participant(request, thread, value):
 def patch_add_participant(request, thread, value):
-    allow_add_participants(request.user, thread)
+    allow_add_participants(request.user_acl, thread)
 
 
     try:
     try:
         username = str(value).strip().lower()
         username = str(value).strip().lower()
@@ -281,7 +281,7 @@ def patch_add_participant(request, thread, value):
     if participant in [p.user for p in thread.participants_list]:
     if participant in [p.user for p in thread.participants_list]:
         raise PermissionDenied(_("This user is already thread participant."))
         raise PermissionDenied(_("This user is already thread participant."))
 
 
-    allow_add_participant(request.user, participant)
+    allow_add_participant(request.user_acl, participant)
     add_participant(request, thread, participant)
     add_participant(request, thread, participant)
 
 
     make_participants_aware(request.user, thread)
     make_participants_aware(request.user, thread)
@@ -305,7 +305,7 @@ def patch_remove_participant(request, thread, value):
     else:
     else:
         raise PermissionDenied(_("Participant doesn't exist."))
         raise PermissionDenied(_("Participant doesn't exist."))
 
 
-    allow_remove_participant(request.user, thread, participant.user)
+    allow_remove_participant(request.user_acl, thread, participant.user)
     remove_participant(request, thread, participant.user)
     remove_participant(request, thread, participant.user)
 
 
     if len(thread.participants_list) == 1:
     if len(thread.participants_list) == 1:
@@ -338,7 +338,7 @@ def patch_replace_owner(request, thread, value):
     else:
     else:
         raise PermissionDenied(_("Participant doesn't exist."))
         raise PermissionDenied(_("Participant doesn't exist."))
 
 
-    allow_change_owner(request.user, thread)
+    allow_change_owner(request.user_acl, thread)
     change_owner(request, thread, participant.user)
     change_owner(request, thread, participant.user)
 
 
     make_participants_aware(request.user, thread)
     make_participants_aware(request.user, thread)

+ 83 - 0
misago/threads/test.py

@@ -0,0 +1,83 @@
+from misago.acl.test import patch_user_acl
+from misago.categories.models import Category
+
+default_category_acl = {
+    'can_see': 1,
+    'can_browse': 1,
+    'can_see_all_threads': 1,
+    'can_see_own_threads': 0,
+    'can_hide_threads': 0,
+    'can_approve_content': 0,
+    'can_edit_posts': 0,
+    'can_hide_posts': 0,
+    'can_hide_own_posts': 0,
+    'can_merge_threads': 0,
+    'can_close_threads': 0,
+}
+
+
+def patch_category_acl(acl_patch):
+    def patch_acl(_, user_acl):
+        category = Category.objects.get(slug="first-category")
+        category_acl = user_acl['categories'][category.id]
+        category_acl.update(default_category_acl)
+        if acl_patch:
+            category_acl.update(acl_patch)
+        cleanup_patched_acl(user_acl, category_acl, category)
+
+    return patch_user_acl(patch_acl)
+
+
+def patch_categories_acl_for_move(src_acl_patch=None, dst_acl_patch=None):
+    def patch_acl(_, user_acl):
+        src = Category.objects.get(slug="first-category")
+        dst = Category.objects.get(slug="other-category")
+
+        src_acl = user_acl['categories'][src.id]
+        dst_acl = src_acl.copy()
+        user_acl['categories'][dst.id] = dst_acl
+
+        src_acl.update(default_category_acl)
+        dst_acl.update(default_category_acl)
+
+        if src_acl_patch:
+            src_acl.update(src_acl_patch)
+        if dst_acl_patch:
+            dst_acl.update(dst_acl_patch)
+
+        cleanup_patched_acl(user_acl, src_acl, src)
+        cleanup_patched_acl(user_acl, dst_acl, dst)
+
+    return patch_user_acl(patch_acl)
+
+
+def create_category_acl_patch(category_slug, acl_patch):
+    def created_category_acl_patch(_, user_acl):
+        category = Category.objects.get(slug=category_slug)
+        category_acl = user_acl['categories'].get(category.id, {})
+        category_acl.update(default_category_acl)
+        if acl_patch:
+            category_acl.update(acl_patch)
+        cleanup_patched_acl(user_acl, category_acl, category)
+    
+    return created_category_acl_patch
+
+
+def cleanup_patched_acl(user_acl, category_acl, category):
+    visible_categories = user_acl['visible_categories']
+    browseable_categories = user_acl['browseable_categories']
+
+    if not category_acl['can_see'] and category.id in visible_categories:
+        visible_categories.remove(category.id)
+
+    if not category_acl['can_see'] and category.id in browseable_categories:
+        browseable_categories.remove(category.id)
+
+    if not category_acl['can_browse'] and category.id in browseable_categories:
+        browseable_categories.remove(category.id)
+
+    if category_acl['can_see'] and category.id not in visible_categories:
+        visible_categories.append(category.id)
+
+    if category_acl['can_browse'] and category.id not in browseable_categories:
+        browseable_categories.append(category.id)

+ 228 - 368
misago/threads/tests/test_thread_patch_api.py

@@ -3,10 +3,10 @@ from datetime import timedelta
 
 
 from django.utils import timezone
 from django.utils import timezone
 
 
-from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.readtracker import poststracker
 from misago.readtracker import poststracker
 from misago.threads import testutils
 from misago.threads import testutils
+from misago.threads.test import patch_category_acl, patch_categories_acl_for_move
 from misago.threads.models import Thread
 from misago.threads.models import Thread
 
 
 from .test_threads_api import ThreadsApiTestCase
 from .test_threads_api import ThreadsApiTestCase
@@ -48,10 +48,9 @@ class ThreadAddAclApiTests(ThreadPatchApiTestCase):
 
 
 
 
 class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
 class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
+    @patch_category_acl({'can_edit_threads': 2})
     def test_change_thread_title(self):
     def test_change_thread_title(self):
         """api makes it possible to change thread title"""
         """api makes it possible to change thread title"""
-        self.override_acl({'can_edit_threads': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -69,10 +68,9 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['title'], "Lorem ipsum change!")
         self.assertEqual(thread_json['title'], "Lorem ipsum change!")
 
 
+    @patch_category_acl({'can_edit_threads': 0})
     def test_change_thread_title_no_permission(self):
     def test_change_thread_title_no_permission(self):
         """api validates permission to change title"""
         """api validates permission to change title"""
-        self.override_acl({'can_edit_threads': 0})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -87,13 +85,9 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(response_json['detail'][0], "You can't edit threads in this category.")
         self.assertEqual(response_json['detail'][0], "You can't edit threads in this category.")
 
 
+    @patch_category_acl({'can_edit_threads': 2, 'can_close_threads': 0})
     def test_change_thread_title_closed_category_no_permission(self):
     def test_change_thread_title_closed_category_no_permission(self):
         """api test permission to edit thread title in closed category"""
         """api test permission to edit thread title in closed category"""
-        self.override_acl({
-            'can_edit_threads': 2,
-            'can_close_threads': 0
-        })
-
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
 
 
@@ -113,13 +107,9 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "This category is closed. You can't edit threads in it."
             response_json['detail'][0], "This category is closed. You can't edit threads in it."
         )
         )
 
 
+    @patch_category_acl({'can_edit_threads': 2, 'can_close_threads': 0})
     def test_change_thread_title_closed_thread_no_permission(self):
     def test_change_thread_title_closed_thread_no_permission(self):
         """api test permission to edit closed thread title"""
         """api test permission to edit closed thread title"""
-        self.override_acl({
-            'can_edit_threads': 2,
-            'can_close_threads': 0
-        })
-
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
 
 
@@ -139,10 +129,9 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "This thread is closed. You can't edit it."
             response_json['detail'][0], "This thread is closed. You can't edit it."
         )
         )
 
 
+    @patch_category_acl({'can_edit_threads': 1, 'thread_edit_time': 1})
     def test_change_thread_title_after_edit_time(self):
     def test_change_thread_title_after_edit_time(self):
         """api cleans, validates and rejects too short title"""
         """api cleans, validates and rejects too short title"""
-        self.override_acl({'thread_edit_time': 1, 'can_edit_threads': 1})
-
         self.thread.started_on = timezone.now() - timedelta(minutes=10)
         self.thread.started_on = timezone.now() - timedelta(minutes=10)
         self.thread.starter = self.user
         self.thread.starter = self.user
         self.thread.save()
         self.thread.save()
@@ -163,10 +152,9 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "You can't edit threads that are older than 1 minute."
             response_json['detail'][0], "You can't edit threads that are older than 1 minute."
         )
         )
 
 
+    @patch_category_acl({'can_edit_threads': 2})
     def test_change_thread_title_invalid(self):
     def test_change_thread_title_invalid(self):
         """api cleans, validates and rejects too short title"""
         """api cleans, validates and rejects too short title"""
-        self.override_acl({'can_edit_threads': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -186,10 +174,9 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
 
 
 
 
 class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
 class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
+    @patch_category_acl({'can_pin_threads': 2})
     def test_pin_thread(self):
     def test_pin_thread(self):
         """api makes it possible to pin globally thread"""
         """api makes it possible to pin globally thread"""
-        self.override_acl({'can_pin_threads': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -207,13 +194,9 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 2)
         self.assertEqual(thread_json['weight'], 2)
 
 
+    @patch_category_acl({'can_pin_threads': 2, 'can_close_threads': 0})
     def test_pin_thread_closed_category_no_permission(self):
     def test_pin_thread_closed_category_no_permission(self):
         """api checks if category is closed"""
         """api checks if category is closed"""
-        self.override_acl({
-            'can_pin_threads': 2,
-            'can_close_threads': 0,
-        })
-
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
 
 
@@ -233,13 +216,9 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "This category is closed. You can't change threads weights in it."
             response_json['detail'][0], "This category is closed. You can't change threads weights in it."
         )
         )
 
 
+    @patch_category_acl({'can_pin_threads': 2, 'can_close_threads': 0})
     def test_pin_thread_closed_no_permission(self):
     def test_pin_thread_closed_no_permission(self):
         """api checks if thread is closed"""
         """api checks if thread is closed"""
-        self.override_acl({
-            'can_pin_threads': 2,
-            'can_close_threads': 0,
-        })
-
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
 
 
@@ -259,6 +238,7 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "This thread is closed. You can't change its weight."
             response_json['detail'][0], "This thread is closed. You can't change its weight."
         )
         )
 
 
+    @patch_category_acl({'can_pin_threads': 2})
     def test_unpin_thread(self):
     def test_unpin_thread(self):
         """api makes it possible to unpin thread"""
         """api makes it possible to unpin thread"""
         self.thread.weight = 2
         self.thread.weight = 2
@@ -267,8 +247,6 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 2)
         self.assertEqual(thread_json['weight'], 2)
 
 
-        self.override_acl({'can_pin_threads': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -286,10 +264,9 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 0)
         self.assertEqual(thread_json['weight'], 0)
 
 
+    @patch_category_acl({'can_pin_threads': 1})
     def test_pin_thread_no_permission(self):
     def test_pin_thread_no_permission(self):
         """api pin thread globally with no permission fails"""
         """api pin thread globally with no permission fails"""
-        self.override_acl({'can_pin_threads': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -309,6 +286,7 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 0)
         self.assertEqual(thread_json['weight'], 0)
 
 
+    @patch_category_acl({'can_pin_threads': 1})
     def test_unpin_thread_no_permission(self):
     def test_unpin_thread_no_permission(self):
         """api unpin thread with no permission fails"""
         """api unpin thread with no permission fails"""
         self.thread.weight = 2
         self.thread.weight = 2
@@ -317,8 +295,6 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 2)
         self.assertEqual(thread_json['weight'], 2)
 
 
-        self.override_acl({'can_pin_threads': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -340,10 +316,9 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
 
 
 
 
 class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
 class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
+    @patch_category_acl({'can_pin_threads': 1})
     def test_pin_thread(self):
     def test_pin_thread(self):
         """api makes it possible to pin locally thread"""
         """api makes it possible to pin locally thread"""
-        self.override_acl({'can_pin_threads': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -361,6 +336,7 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 1)
         self.assertEqual(thread_json['weight'], 1)
 
 
+    @patch_category_acl({'can_pin_threads': 1})
     def test_unpin_thread(self):
     def test_unpin_thread(self):
         """api makes it possible to unpin thread"""
         """api makes it possible to unpin thread"""
         self.thread.weight = 1
         self.thread.weight = 1
@@ -369,8 +345,6 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 1)
         self.assertEqual(thread_json['weight'], 1)
 
 
-        self.override_acl({'can_pin_threads': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -388,10 +362,9 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 0)
         self.assertEqual(thread_json['weight'], 0)
 
 
+    @patch_category_acl({'can_pin_threads': 0})
     def test_pin_thread_no_permission(self):
     def test_pin_thread_no_permission(self):
         """api pin thread locally with no permission fails"""
         """api pin thread locally with no permission fails"""
-        self.override_acl({'can_pin_threads': 0})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -411,6 +384,7 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 0)
         self.assertEqual(thread_json['weight'], 0)
 
 
+    @patch_category_acl({'can_pin_threads': 0})
     def test_unpin_thread_no_permission(self):
     def test_unpin_thread_no_permission(self):
         """api unpin thread with no permission fails"""
         """api unpin thread with no permission fails"""
         self.thread.weight = 1
         self.thread.weight = 1
@@ -419,8 +393,6 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['weight'], 1)
         self.assertEqual(thread_json['weight'], 1)
 
 
-        self.override_acl({'can_pin_threads': 0})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -446,57 +418,31 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         super().setUp()
         super().setUp()
 
 
         Category(
         Category(
-            name='Category B',
-            slug='category-b',
+            name='Other category',
+            slug='other-category',
         ).insert_at(
         ).insert_at(
             self.category,
             self.category,
             position='last-child',
             position='last-child',
             save=True,
             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.dst_category = Category.objects.get(slug='other-category')
 
 
+    @patch_categories_acl_for_move(
+        {'can_move_threads': True}, {'can_start_threads': 2}
+    )
     def test_move_thread_no_top(self):
     def test_move_thread_no_top(self):
         """api moves thread to other category, sets no top category"""
         """api moves thread to other category, sets no top category"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_start_threads': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
                 {
                 {
                     'op': 'add',
                     'op': 'add',
                     'path': 'top-category',
                     'path': 'top-category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
@@ -508,24 +454,22 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         reponse_json = response.json()
         reponse_json = response.json()
-        self.assertEqual(reponse_json['category'], self.category_b.pk)
-
-        self.override_other_acl({})
+        self.assertEqual(reponse_json['category'], self.dst_category.pk)
 
 
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
-        self.assertEqual(thread_json['category']['id'], self.category_b.pk)
+        self.assertEqual(thread_json['category']['id'], self.dst_category.pk)
 
 
+    @patch_categories_acl_for_move(
+        {'can_move_threads': True}, {'can_start_threads': 2}
+    )
     def test_move_thread_with_top(self):
     def test_move_thread_with_top(self):
         """api moves thread to other category, sets top"""
         """api moves thread to other category, sets top"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_start_threads': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
                 {
                 {
                     'op': 'add',
                     'op': 'add',
@@ -542,18 +486,16 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         reponse_json = response.json()
         reponse_json = response.json()
-        self.assertEqual(reponse_json['category'], self.category_b.pk)
-
-        self.override_other_acl({})
+        self.assertEqual(reponse_json['category'], self.dst_category.pk)
 
 
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
-        self.assertEqual(thread_json['category']['id'], self.category_b.pk)
+        self.assertEqual(thread_json['category']['id'], self.dst_category.pk)
 
 
+    @patch_categories_acl_for_move(
+        {'can_move_threads': True}, {'can_start_threads': 2}
+    )
     def test_move_thread_reads(self):
     def test_move_thread_reads(self):
         """api moves thread reads together with thread"""
         """api moves thread reads together with thread"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_start_threads': 2})
-
         poststracker.save_read(self.user, self.thread.first_post)
         poststracker.save_read(self.user, self.thread.first_post)
 
 
         self.assertEqual(self.user.postread_set.count(), 1)
         self.assertEqual(self.user.postread_set.count(), 1)
@@ -564,12 +506,12 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
                 {
                 {
                     'op': 'add',
                     'op': 'add',
                     'path': 'top-category',
                     'path': 'top-category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
@@ -584,13 +526,13 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         postreads = self.user.postread_set.filter(post__is_event=False).order_by('id')
         postreads = self.user.postread_set.filter(post__is_event=False).order_by('id')
 
 
         self.assertEqual(postreads.count(), 1)
         self.assertEqual(postreads.count(), 1)
-        postreads.get(category=self.category_b)
+        postreads.get(category=self.dst_category)
 
 
+    @patch_categories_acl_for_move(
+        {'can_move_threads': True}, {'can_start_threads': 2}
+    )
     def test_move_thread_subscriptions(self):
     def test_move_thread_subscriptions(self):
         """api moves thread subscriptions together with thread"""
         """api moves thread subscriptions together with thread"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_start_threads': 2})
-
         self.user.subscription_set.create(
         self.user.subscription_set.create(
             thread=self.thread,
             thread=self.thread,
             category=self.thread.category,
             category=self.thread.category,
@@ -606,12 +548,12 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
                 {
                 {
                     'op': 'add',
                     'op': 'add',
                     'path': 'top-category',
                     'path': 'top-category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
@@ -624,19 +566,17 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
 
 
         # thread read was moved to new category
         # thread read was moved to new category
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.assertEqual(self.user.subscription_set.count(), 1)
-        self.user.subscription_set.get(category=self.category_b)
+        self.user.subscription_set.get(category=self.dst_category)
 
 
+    @patch_categories_acl_for_move({'can_move_threads': False})
     def test_move_thread_no_permission(self):
     def test_move_thread_no_permission(self):
         """api move thread to other category with no permission fails"""
         """api move thread to other category with no permission fails"""
-        self.override_acl({'can_move_threads': False})
-        self.override_other_acl({})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
             ]
             ]
         )
         )
@@ -647,19 +587,12 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "You can't move threads in this category."
             response_json['detail'][0], "You can't move threads in this category."
         )
         )
 
 
-        self.override_other_acl({})
-
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['category']['id'], self.category.pk)
         self.assertEqual(thread_json['category']['id'], self.category.pk)
 
 
+    @patch_category_acl({'can_move_threads': True, 'can_close_threads': False})
     def test_move_thread_closed_category_no_permission(self):
     def test_move_thread_closed_category_no_permission(self):
         """api move thread from closed category with no permission fails"""
         """api move thread from closed category with no permission fails"""
-        self.override_acl({
-            'can_move_threads': True,
-            'can_close_threads': False,
-        })
-        self.override_other_acl({})
-
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
 
 
@@ -668,7 +601,7 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
             ]
             ]
         )
         )
@@ -679,14 +612,9 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "This category is closed. You can't move it's threads."
             response_json['detail'][0], "This category is closed. You can't move it's threads."
         )
         )
 
 
+    @patch_category_acl({'can_move_threads': True, 'can_close_threads': False})
     def test_move_closed_thread_no_permission(self):
     def test_move_closed_thread_no_permission(self):
         """api move closed thread with no permission fails"""
         """api move closed thread with no permission fails"""
-        self.override_acl({
-            'can_move_threads': True,
-            'can_close_threads': False,
-        })
-        self.override_other_acl({})
-
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
 
 
@@ -695,7 +623,7 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
             ]
             ]
         )
         )
@@ -706,17 +634,15 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "This thread is closed. You can't move it."
             response_json['detail'][0], "This thread is closed. You can't move it."
         )
         )
 
 
+    @patch_categories_acl_for_move({'can_move_threads': True}, {'can_see': False})
     def test_move_thread_no_category_access(self):
     def test_move_thread_no_category_access(self):
         """api move thread to category with no access fails"""
         """api move thread to category with no access fails"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_see': False})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
             ]
             ]
         )
         )
@@ -725,22 +651,18 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(response_json['detail'][0], 'NOT FOUND')
         self.assertEqual(response_json['detail'][0], 'NOT FOUND')
 
 
-        self.override_other_acl({})
-
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['category']['id'], self.category.pk)
         self.assertEqual(thread_json['category']['id'], self.category.pk)
 
 
+    @patch_categories_acl_for_move({'can_move_threads': True}, {'can_browse': False})
     def test_move_thread_no_category_browse(self):
     def test_move_thread_no_category_browse(self):
         """api move thread to category with no browsing access fails"""
         """api move thread to category with no browsing access fails"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_browse': False})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
             ]
             ]
         )
         )
@@ -749,25 +671,23 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
             response_json['detail'][0],
             response_json['detail'][0],
-            'You don\'t have permission to browse "Category B" contents.'
+            'You don\'t have permission to browse "Other category" contents.'
         )
         )
 
 
-        self.override_other_acl({})
-
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['category']['id'], self.category.pk)
         self.assertEqual(thread_json['category']['id'], self.category.pk)
 
 
+    @patch_categories_acl_for_move(
+        {'can_move_threads': True}, {'can_start_threads': False}
+    )
     def test_move_thread_no_category_start_threads(self):
     def test_move_thread_no_category_start_threads(self):
         """api move thread to category with no posting access fails"""
         """api move thread to category with no posting access fails"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_start_threads': False})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk,
+                    'value': self.dst_category.pk,
                 },
                 },
             ]
             ]
         )
         )
@@ -779,16 +699,14 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
             "You don't have permission to start new threads in this category."
             "You don't have permission to start new threads in this category."
         )
         )
 
 
-        self.override_other_acl({})
-
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['category']['id'], self.category.pk)
         self.assertEqual(thread_json['category']['id'], self.category.pk)
 
 
+    @patch_categories_acl_for_move(
+        {'can_move_threads': True}, {'can_start_threads': 2}
+    )
     def test_move_thread_same_category(self):
     def test_move_thread_same_category(self):
         """api move thread to category it's already in fails"""
         """api move thread to category it's already in fails"""
-        self.override_acl({'can_move_threads': True})
-        self.override_other_acl({'can_start_threads': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -805,8 +723,6 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "You can't move thread to the category it's already in."
             response_json['detail'][0], "You can't move thread to the category it's already in."
         )
         )
 
 
-        self.override_other_acl({})
-
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['category']['id'], self.category.pk)
         self.assertEqual(thread_json['category']['id'], self.category.pk)
 
 
@@ -828,10 +744,9 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
 
 
 
 
 class ThreadCloseApiTests(ThreadPatchApiTestCase):
 class ThreadCloseApiTests(ThreadPatchApiTestCase):
+    @patch_category_acl({'can_close_threads': True})
     def test_close_thread(self):
     def test_close_thread(self):
         """api makes it possible to close thread"""
         """api makes it possible to close thread"""
-        self.override_acl({'can_close_threads': True})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -849,6 +764,7 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertTrue(thread_json['is_closed'])
         self.assertTrue(thread_json['is_closed'])
 
 
+    @patch_category_acl({'can_close_threads': True})
     def test_open_thread(self):
     def test_open_thread(self):
         """api makes it possible to open thread"""
         """api makes it possible to open thread"""
         self.thread.is_closed = True
         self.thread.is_closed = True
@@ -857,8 +773,6 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertTrue(thread_json['is_closed'])
         self.assertTrue(thread_json['is_closed'])
 
 
-        self.override_acl({'can_close_threads': True})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -876,10 +790,9 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertFalse(thread_json['is_closed'])
         self.assertFalse(thread_json['is_closed'])
 
 
+    @patch_category_acl({'can_close_threads': False})
     def test_close_thread_no_permission(self):
     def test_close_thread_no_permission(self):
         """api close thread with no permission fails"""
         """api close thread with no permission fails"""
-        self.override_acl({'can_close_threads': False})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -899,6 +812,7 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertFalse(thread_json['is_closed'])
         self.assertFalse(thread_json['is_closed'])
 
 
+    @patch_category_acl({'can_close_threads': False})
     def test_open_thread_no_permission(self):
     def test_open_thread_no_permission(self):
         """api open thread with no permission fails"""
         """api open thread with no permission fails"""
         self.thread.is_closed = True
         self.thread.is_closed = True
@@ -907,8 +821,6 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertTrue(thread_json['is_closed'])
         self.assertTrue(thread_json['is_closed'])
 
 
-        self.override_acl({'can_close_threads': False})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -930,6 +842,7 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
 
 
 
 
 class ThreadApproveApiTests(ThreadPatchApiTestCase):
 class ThreadApproveApiTests(ThreadPatchApiTestCase):
+    @patch_category_acl({'can_approve_content': True})
     def test_approve_thread(self):
     def test_approve_thread(self):
         """api makes it possible to approve thread"""
         """api makes it possible to approve thread"""
         self.thread.first_post.is_unapproved = True
         self.thread.first_post.is_unapproved = True
@@ -941,8 +854,6 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
         self.assertTrue(self.thread.is_unapproved)
         self.assertTrue(self.thread.is_unapproved)
         self.assertTrue(self.thread.has_unapproved_posts)
         self.assertTrue(self.thread.has_unapproved_posts)
 
 
-        self.override_acl({'can_approve_content': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -966,6 +877,7 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
         self.assertFalse(thread.is_unapproved)
         self.assertFalse(thread.is_unapproved)
         self.assertFalse(thread.has_unapproved_posts)
         self.assertFalse(thread.has_unapproved_posts)
 
 
+    @patch_category_acl({'can_approve_content': True, 'can_close_threads': False})
     def test_approve_thread_category_closed_no_permission(self):
     def test_approve_thread_category_closed_no_permission(self):
         """api checks permission for approving threads in closed categories"""
         """api checks permission for approving threads in closed categories"""
         self.thread.first_post.is_unapproved = True
         self.thread.first_post.is_unapproved = True
@@ -980,11 +892,6 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
 
 
-        self.override_acl({
-            'can_approve_content': 1,
-            'can_close_threads': 0,
-        })
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -999,6 +906,7 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(response_json['detail'][0], "This category is closed. You can't approve threads in it.")
         self.assertEqual(response_json['detail'][0], "This category is closed. You can't approve threads in it.")
 
 
+    @patch_category_acl({'can_approve_content': True, 'can_close_threads': False})
     def test_approve_thread_closed_no_permission(self):
     def test_approve_thread_closed_no_permission(self):
         """api checks permission for approving posts in closed categories"""
         """api checks permission for approving posts in closed categories"""
         self.thread.first_post.is_unapproved = True
         self.thread.first_post.is_unapproved = True
@@ -1013,11 +921,6 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
 
 
-        self.override_acl({
-            'can_approve_content': 1,
-            'can_close_threads': 0,
-        })
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1032,10 +935,9 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(response_json['detail'][0], "This thread is closed. You can't approve it.")
         self.assertEqual(response_json['detail'][0], "This thread is closed. You can't approve it.")
 
 
+    @patch_category_acl({'can_approve_content': True})
     def test_unapprove_thread(self):
     def test_unapprove_thread(self):
         """api returns permission error on approval removal"""
         """api returns permission error on approval removal"""
-        self.override_acl({'can_approve_content': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1052,10 +954,9 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
 
 
 
 
 class ThreadHideApiTests(ThreadPatchApiTestCase):
 class ThreadHideApiTests(ThreadPatchApiTestCase):
+    @patch_category_acl({'can_hide_threads': True})
     def test_hide_thread(self):
     def test_hide_thread(self):
         """api makes it possible to hide thread"""
         """api makes it possible to hide thread"""
-        self.override_acl({'can_hide_threads': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1070,15 +971,12 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
         reponse_json = response.json()
         reponse_json = response.json()
         self.assertTrue(reponse_json['is_hidden'])
         self.assertTrue(reponse_json['is_hidden'])
 
 
-        self.override_acl({'can_hide_threads': 1})
-
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertTrue(thread_json['is_hidden'])
         self.assertTrue(thread_json['is_hidden'])
 
 
+    @patch_category_acl({'can_hide_threads': False})
     def test_hide_thread_no_permission(self):
     def test_hide_thread_no_permission(self):
         """api hide thread with no permission fails"""
         """api hide thread with no permission fails"""
-        self.override_acl({'can_hide_threads': 0})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1098,13 +996,9 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertFalse(thread_json['is_hidden'])
         self.assertFalse(thread_json['is_hidden'])
 
 
+    @patch_category_acl({'can_hide_threads': False, 'can_hide_own_threads': True})
     def test_hide_non_owned_thread(self):
     def test_hide_non_owned_thread(self):
         """api forbids non-moderator from hiding other users threads"""
         """api forbids non-moderator from hiding other users threads"""
-        self.override_acl({
-            'can_hide_own_threads': 1,
-            'can_hide_threads': 0
-        })
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1121,14 +1015,13 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "You can't hide other users theads in this category."
             response_json['detail'][0], "You can't hide other users theads in this category."
         )
         )
 
 
+    @patch_category_acl({
+        'can_hide_threads': False,
+        'can_hide_own_threads': True,
+        'thread_edit_time': 1,
+    })
     def test_hide_owned_thread_no_time(self):
     def test_hide_owned_thread_no_time(self):
         """api forbids non-moderator from hiding other users threads"""
         """api forbids non-moderator from hiding other users threads"""
-        self.override_acl({
-            'can_hide_own_threads': 1,
-            'can_hide_threads': 0,
-            'thread_edit_time': 1,
-        })
-
         self.thread.started_on = timezone.now() - timedelta(minutes=5)
         self.thread.started_on = timezone.now() - timedelta(minutes=5)
         self.thread.starter = self.user
         self.thread.starter = self.user
         self.thread.save()
         self.thread.save()
@@ -1149,13 +1042,9 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "You can't hide threads that are older than 1 minute."
             response_json['detail'][0], "You can't hide threads that are older than 1 minute."
         )
         )
 
 
+    @patch_category_acl({'can_hide_threads': True, 'can_close_threads': False})
     def test_hide_closed_category_no_permission(self):
     def test_hide_closed_category_no_permission(self):
         """api test permission to hide thread in closed category"""
         """api test permission to hide thread in closed category"""
-        self.override_acl({
-            'can_hide_threads': 1,
-            'can_close_threads': 0
-        })
-
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
 
 
@@ -1175,13 +1064,9 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "This category is closed. You can't hide threads in it."
             response_json['detail'][0], "This category is closed. You can't hide threads in it."
         )
         )
 
 
+    @patch_category_acl({'can_hide_threads': True, 'can_close_threads': False})
     def test_hide_closed_thread_no_permission(self):
     def test_hide_closed_thread_no_permission(self):
         """api test permission to hide closed thread"""
         """api test permission to hide closed thread"""
-        self.override_acl({
-            'can_hide_threads': 1,
-            'can_close_threads': 0
-        })
-
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
 
 
@@ -1209,10 +1094,9 @@ class ThreadUnhideApiTests(ThreadPatchApiTestCase):
         self.thread.is_hidden = True
         self.thread.is_hidden = True
         self.thread.save()
         self.thread.save()
 
 
+    @patch_category_acl({'can_hide_threads': True})
     def test_unhide_thread(self):
     def test_unhide_thread(self):
         """api makes it possible to unhide thread"""
         """api makes it possible to unhide thread"""
-        self.override_acl({'can_hide_threads': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1227,15 +1111,12 @@ class ThreadUnhideApiTests(ThreadPatchApiTestCase):
         reponse_json = response.json()
         reponse_json = response.json()
         self.assertFalse(reponse_json['is_hidden'])
         self.assertFalse(reponse_json['is_hidden'])
 
 
-        self.override_acl({'can_hide_threads': 1})
-
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertFalse(thread_json['is_hidden'])
         self.assertFalse(thread_json['is_hidden'])
 
 
+    @patch_category_acl({'can_hide_threads': False})
     def test_unhide_thread_no_permission(self):
     def test_unhide_thread_no_permission(self):
         """api unhide thread with no permission fails as thread is invisible"""
         """api unhide thread with no permission fails as thread is invisible"""
-        self.override_acl({'can_hide_threads': 0})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1247,13 +1128,9 @@ class ThreadUnhideApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
 
 
+    @patch_category_acl({'can_hide_threads': True, 'can_close_threads': False})
     def test_unhide_closed_category_no_permission(self):
     def test_unhide_closed_category_no_permission(self):
         """api test permission to unhide thread in closed category"""
         """api test permission to unhide thread in closed category"""
-        self.override_acl({
-            'can_hide_threads': 1,
-            'can_close_threads': 0
-        })
-
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
 
 
@@ -1273,13 +1150,9 @@ class ThreadUnhideApiTests(ThreadPatchApiTestCase):
             response_json['detail'][0], "This category is closed. You can't reveal threads in it."
             response_json['detail'][0], "This category is closed. You can't reveal threads in it."
         )
         )
 
 
+    @patch_category_acl({'can_hide_threads': True, 'can_close_threads': False})
     def test_unhide_closed_thread_no_permission(self):
     def test_unhide_closed_thread_no_permission(self):
         """api test permission to unhide closed thread"""
         """api test permission to unhide closed thread"""
-        self.override_acl({
-            'can_hide_threads': 1,
-            'can_close_threads': 0
-        })
-
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
 
 
@@ -1405,10 +1278,9 @@ class ThreadSubscribeApiTests(ThreadPatchApiTestCase):
 
 
 
 
 class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
 class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer(self):
     def test_mark_best_answer(self):
         """api makes it possible to mark best answer"""
         """api makes it possible to mark best answer"""
-        self.override_acl({'can_mark_best_answers': 2})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -1442,12 +1314,11 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         self.assertEqual(thread_json['best_answer_marked_by_name'], self.user.username)
         self.assertEqual(thread_json['best_answer_marked_by_name'], self.user.username)
         self.assertEqual(thread_json['best_answer_marked_by_slug'], self.user.slug)
         self.assertEqual(thread_json['best_answer_marked_by_slug'], self.user.slug)
 
 
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_anonymous(self):
     def test_mark_best_answer_anonymous(self):
         """api validates that user is authenticated before marking best answer"""
         """api validates that user is authenticated before marking best answer"""
         self.logout_user()
         self.logout_user()
 
 
-        self.override_acl({'can_mark_best_answers': 2})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -1467,10 +1338,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 0})
     def test_mark_best_answer_no_permission(self):
     def test_mark_best_answer_no_permission(self):
         """api validates permission to mark best answers"""
         """api validates permission to mark best answers"""
-        self.override_acl({'can_mark_best_answers': 0})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -1493,10 +1363,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 1})
     def test_mark_best_answer_not_thread_starter(self):
     def test_mark_best_answer_not_thread_starter(self):
         """api validates permission to mark best answers in owned thread"""
         """api validates permission to mark best answers in owned thread"""
-        self.override_acl({'can_mark_best_answers': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -1524,8 +1393,6 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         self.thread.starter = self.user
         self.thread.starter = self.user
         self.thread.save()
         self.thread.save()
 
 
-        self.override_acl({'can_mark_best_answers': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1537,10 +1404,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_mark_best_answer_category_closed(self):
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_close_threads': False})
+    def test_mark_best_answer_category_closed_no_permission(self):
         """api validates permission to mark best answers in closed category"""
         """api validates permission to mark best answers in closed category"""
-        self.override_acl({'can_mark_best_answers': 2, 'can_close_threads': 0})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         self.category.is_closed = True
         self.category.is_closed = True
@@ -1567,8 +1433,13 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
-        # passing scenario is possible
-        self.override_acl({'can_mark_best_answers': 2, 'can_close_threads': 1})
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_close_threads': True})
+    def test_mark_best_answer_category_closed(self):
+        """api validates permission to mark best answers in closed category"""
+        best_answer = testutils.reply_thread(self.thread)
+
+        self.category.is_closed = True
+        self.category.save()
 
 
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
@@ -1581,10 +1452,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_mark_best_answer_thread_closed(self):
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_close_threads': False})
+    def test_mark_best_answer_thread_closed_no_permission(self):
         """api validates permission to mark best answers in closed thread"""
         """api validates permission to mark best answers in closed thread"""
-        self.override_acl({'can_mark_best_answers': 2, 'can_close_threads': 0})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         self.thread.is_closed = True
         self.thread.is_closed = True
@@ -1611,8 +1481,13 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
-        # passing scenario is possible
-        self.override_acl({'can_mark_best_answers': 2, 'can_close_threads': 1})
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_close_threads': True})
+    def test_mark_best_answer_thread_closed(self):
+        """api validates permission to mark best answers in closed thread"""
+        best_answer = testutils.reply_thread(self.thread)
+
+        self.thread.is_closed = True
+        self.thread.save()
 
 
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
@@ -1624,11 +1499,10 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
             ]
             ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
-
+    
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_invalid_post_id(self):
     def test_mark_best_answer_invalid_post_id(self):
         """api validates that post id is int"""
         """api validates that post id is int"""
-        self.override_acl({'can_mark_best_answers': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1647,10 +1521,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_post_not_found(self):
     def test_mark_best_answer_post_not_found(self):
         """api validates that post exists"""
         """api validates that post exists"""
-        self.override_acl({'can_mark_best_answers': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1668,11 +1541,10 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
 
 
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
-        
+
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_post_invisible(self):
     def test_mark_best_answer_post_invisible(self):
         """api validates post visibility to action author"""
         """api validates post visibility to action author"""
-        self.override_acl({'can_mark_best_answers': 2})
-
         unapproved_post = testutils.reply_thread(self.thread, is_unapproved=True)
         unapproved_post = testutils.reply_thread(self.thread, is_unapproved=True)
 
 
         response = self.patch(
         response = self.patch(
@@ -1693,10 +1565,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_post_other_thread(self):
     def test_mark_best_answer_post_other_thread(self):
         """api validates post belongs to same thread"""
         """api validates post belongs to same thread"""
-        self.override_acl({'can_mark_best_answers': 2})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
         response = self.patch(
         response = self.patch(
@@ -1717,10 +1588,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_event_id(self):
     def test_mark_best_answer_event_id(self):
         """api validates that post is not an event"""
         """api validates that post is not an event"""
-        self.override_acl({'can_mark_best_answers': 2})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         best_answer.is_event = True
         best_answer.is_event = True
         best_answer.save()
         best_answer.save()
@@ -1743,10 +1613,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_first_post(self):
     def test_mark_best_answer_first_post(self):
         """api validates that post is not a first post in thread"""
         """api validates that post is not a first post in thread"""
-        self.override_acl({'can_mark_best_answers': 2})
-        
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1765,10 +1634,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_hidden_post(self):
     def test_mark_best_answer_hidden_post(self):
         """api validates that post is not hidden"""
         """api validates that post is not hidden"""
-        self.override_acl({'can_mark_best_answers': 2})
-        
         best_answer = testutils.reply_thread(self.thread, is_hidden=True)
         best_answer = testutils.reply_thread(self.thread, is_hidden=True)
 
 
         response = self.patch(
         response = self.patch(
@@ -1789,10 +1657,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 2})
     def test_mark_best_answer_unapproved_post(self):
     def test_mark_best_answer_unapproved_post(self):
         """api validates that post is not unapproved"""
         """api validates that post is not unapproved"""
-        self.override_acl({'can_mark_best_answers': 2})
-        
         best_answer = testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True)
         best_answer = testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True)
 
 
         response = self.patch(
         response = self.patch(
@@ -1813,10 +1680,9 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
-    def test_mark_best_answer_protected_post(self):
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_protect_posts': False})
+    def test_mark_best_answer_protected_post_no_permission(self):
         """api respects post protection"""
         """api respects post protection"""
-        self.override_acl({'can_mark_best_answers': 2, 'can_protect_posts': 0})
-        
         best_answer = testutils.reply_thread(self.thread, is_protected=True)
         best_answer = testutils.reply_thread(self.thread, is_protected=True)
 
 
         response = self.patch(
         response = self.patch(
@@ -1840,8 +1706,10 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertIsNone(thread_json['best_answer'])
         self.assertIsNone(thread_json['best_answer'])
 
 
-        # passing scenario is possible
-        self.override_acl({'can_mark_best_answers': 2, 'can_protect_posts': 1})
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_protect_posts': True})
+    def test_mark_best_answer_protected_post(self):
+        """api respects post protection"""
+        best_answer = testutils.reply_thread(self.thread, is_protected=True)
 
 
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
@@ -1863,10 +1731,9 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         self.thread.set_best_answer(self.user, self.best_answer)
         self.thread.set_best_answer(self.user, self.best_answer)
         self.thread.save()
         self.thread.save()
 
 
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 2})
     def test_change_best_answer(self):
     def test_change_best_answer(self):
         """api makes it possible to change best answer"""
         """api makes it possible to change best answer"""
-        self.override_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 2})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -1900,10 +1767,9 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         self.assertEqual(thread_json['best_answer_marked_by_name'], self.user.username)
         self.assertEqual(thread_json['best_answer_marked_by_name'], self.user.username)
         self.assertEqual(thread_json['best_answer_marked_by_slug'], self.user.slug)
         self.assertEqual(thread_json['best_answer_marked_by_slug'], self.user.slug)
 
 
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 2})
     def test_change_best_answer_same_post(self):
     def test_change_best_answer_same_post(self):
         """api validates if new best answer is same as current one"""
         """api validates if new best answer is same as current one"""
-        self.override_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -1922,10 +1788,9 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
+    @patch_category_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
     def test_change_best_answer_no_permission_to_mark(self):
     def test_change_best_answer_no_permission_to_mark(self):
         """api validates permission to mark best answers before allowing answer change"""
         """api validates permission to mark best answers before allowing answer change"""
-        self.override_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -1948,10 +1813,9 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 0})
     def test_change_best_answer_no_permission(self):
     def test_change_best_answer_no_permission(self):
         """api validates permission to change best answers"""
         """api validates permission to change best answers"""
-        self.override_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 0})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -1975,10 +1839,9 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 1})
     def test_change_best_answer_not_starter(self):
     def test_change_best_answer_not_starter(self):
         """api validates permission to change best answers"""
         """api validates permission to change best answers"""
-        self.override_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -2003,8 +1866,6 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
         # passing scenario is possible
         # passing scenario is possible
-        self.override_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 1})
-        
         self.thread.starter = self.user
         self.thread.starter = self.user
         self.thread.save()
         self.thread.save()
 
 
@@ -2019,14 +1880,13 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_change_best_answer_timelimit(self):
+    @patch_category_acl({
+        'can_mark_best_answers': 2,
+        'can_change_marked_answers': 1,
+        'best_answer_change_time': 5,
+    })
+    def test_change_best_answer_timelimit_out_of_time(self):
         """api validates permission for starter to change best answers within timelimit"""
         """api validates permission for starter to change best answers within timelimit"""
-        self.override_acl({
-            'can_mark_best_answers': 2,
-            'can_change_marked_answers': 1,
-            'best_answer_change_time': 5,
-        })
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         self.thread.best_answer_marked_on = timezone.now() - timedelta(minutes=6)
         self.thread.best_answer_marked_on = timezone.now() - timedelta(minutes=6)
@@ -2054,13 +1914,19 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
-        # passing scenario is possible
-        self.override_acl({
-            'can_mark_best_answers': 2,
-            'can_change_marked_answers': 1,
-            'best_answer_change_time': 10,
-        })
-        
+    @patch_category_acl({
+        'can_mark_best_answers': 2,
+        'can_change_marked_answers': 1,
+        'best_answer_change_time': 5,
+    })
+    def test_change_best_answer_timelimit(self):
+        """api validates permission for starter to change best answers within timelimit"""
+        best_answer = testutils.reply_thread(self.thread)
+
+        self.thread.best_answer_marked_on = timezone.now() - timedelta(minutes=1)
+        self.thread.starter = self.user
+        self.thread.save()
+
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2072,14 +1938,13 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_change_best_answer_protected(self):
+    @patch_category_acl({
+        'can_mark_best_answers': 2,
+        'can_change_marked_answers': 2,
+        'can_protect_posts': False,
+    })
+    def test_change_best_answer_protected_no_permission(self):
         """api validates permission to change protected best answers"""
         """api validates permission to change protected best answers"""
-        self.override_acl({
-            'can_mark_best_answers': 2,
-            'can_change_marked_answers': 2,
-            'can_protect_posts': 0,
-        })
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         self.thread.best_answer_is_protected = True
         self.thread.best_answer_is_protected = True
@@ -2106,13 +1971,18 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
-        # passing scenario is possible
-        self.override_acl({
-            'can_mark_best_answers': 2,
-            'can_change_marked_answers': 2,
-            'can_protect_posts': 1,
-        })
-        
+    @patch_category_acl({
+        'can_mark_best_answers': 2,
+        'can_change_marked_answers': 2,
+        'can_protect_posts': True,
+    })
+    def test_change_best_answer_protected(self):
+        """api validates permission to change protected best answers"""
+        best_answer = testutils.reply_thread(self.thread)
+
+        self.thread.best_answer_is_protected = True
+        self.thread.save()
+
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2124,13 +1994,9 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
+    @patch_category_acl({'can_mark_best_answers': 2, 'can_change_marked_answers': 2})
     def test_change_best_answer_post_validation(self):
     def test_change_best_answer_post_validation(self):
         """api validates new post'"""
         """api validates new post'"""
-        self.override_acl({
-            'can_mark_best_answers': 2,
-            'can_change_marked_answers': 2,
-        })
-
         best_answer = testutils.reply_thread(self.thread, is_hidden=True)
         best_answer = testutils.reply_thread(self.thread, is_hidden=True)
 
 
         response = self.patch(
         response = self.patch(
@@ -2156,10 +2022,9 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         self.thread.set_best_answer(self.user, self.best_answer)
         self.thread.set_best_answer(self.user, self.best_answer)
         self.thread.save()
         self.thread.save()
 
 
+    @patch_category_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
     def test_unmark_best_answer(self):
     def test_unmark_best_answer(self):
         """api makes it possible to unmark best answer"""
         """api makes it possible to unmark best answer"""
-        self.override_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2190,10 +2055,9 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         self.assertIsNone(thread_json['best_answer_marked_by_name'])
         self.assertIsNone(thread_json['best_answer_marked_by_name'])
         self.assertIsNone(thread_json['best_answer_marked_by_slug'])
         self.assertIsNone(thread_json['best_answer_marked_by_slug'])
 
 
+    @patch_category_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
     def test_unmark_best_answer_invalid_post_id(self):
     def test_unmark_best_answer_invalid_post_id(self):
         """api validates that post id is int"""
         """api validates that post id is int"""
-        self.override_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2212,10 +2076,9 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
+    @patch_category_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
     def test_unmark_best_answer_post_not_found(self):
     def test_unmark_best_answer_post_not_found(self):
         """api validates that post to unmark exists"""
         """api validates that post to unmark exists"""
-        self.override_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2233,11 +2096,10 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
 
 
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
-        
+
+    @patch_category_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
     def test_unmark_best_answer_wrong_post(self):
     def test_unmark_best_answer_wrong_post(self):
         """api validates if post given to unmark is best answer"""
         """api validates if post given to unmark is best answer"""
-        self.override_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 2})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
 
 
         response = self.patch(
         response = self.patch(
@@ -2260,10 +2122,9 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
+    @patch_category_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 0})
     def test_unmark_best_answer_no_permission(self):
     def test_unmark_best_answer_no_permission(self):
         """api validates if user has permission to unmark best answers"""
         """api validates if user has permission to unmark best answers"""
-        self.override_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 0})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2285,10 +2146,9 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
+    @patch_category_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 1})
     def test_unmark_best_answer_not_starter(self):
     def test_unmark_best_answer_not_starter(self):
         """api validates if starter has permission to unmark best answers"""
         """api validates if starter has permission to unmark best answers"""
-        self.override_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 1})
-
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2311,8 +2171,6 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
         # passing scenario is possible
         # passing scenario is possible
-        self.override_acl({'can_mark_best_answers': 0, 'can_change_marked_answers': 1})
-
         self.thread.starter = self.user
         self.thread.starter = self.user
         self.thread.save()
         self.thread.save()
 
 
@@ -2327,14 +2185,13 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
+    @patch_category_acl({
+        'can_mark_best_answers': 0,
+        'can_change_marked_answers': 1,
+        'best_answer_change_time': 5,
+    })
     def test_unmark_best_answer_timelimit(self):
     def test_unmark_best_answer_timelimit(self):
         """api validates if starter has permission to unmark best answer within time limit"""
         """api validates if starter has permission to unmark best answer within time limit"""
-        self.override_acl({
-            'can_mark_best_answers': 0,
-            'can_change_marked_answers': 1,
-            'best_answer_change_time': 5,
-        })
-
         self.thread.best_answer_marked_on = timezone.now() - timedelta(minutes=6)
         self.thread.best_answer_marked_on = timezone.now() - timedelta(minutes=6)
         self.thread.starter = self.user
         self.thread.starter = self.user
         self.thread.save()
         self.thread.save()
@@ -2361,11 +2218,8 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
         # passing scenario is possible
         # passing scenario is possible
-        self.override_acl({
-            'can_mark_best_answers': 0,
-            'can_change_marked_answers': 1,
-            'best_answer_change_time': 10,
-        })
+        self.thread.best_answer_marked_on = timezone.now() - timedelta(minutes=2)
+        self.thread.save()
         
         
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
@@ -2378,14 +2232,13 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_unmark_best_answer_closed_category(self):
+    @patch_category_acl({
+        'can_mark_best_answers': 0,
+        'can_change_marked_answers': 2,
+        'can_close_threads': False,
+    })
+    def test_unmark_best_answer_closed_category_no_permission(self):
         """api validates if user has permission to unmark best answer in closed category"""
         """api validates if user has permission to unmark best answer in closed category"""
-        self.override_acl({
-            'can_mark_best_answers': 0,
-            'can_change_marked_answers': 2,
-            'can_close_threads': 0,
-        })
-
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
 
 
@@ -2410,13 +2263,16 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
-        # passing scenario is possible
-        self.override_acl({
-            'can_mark_best_answers': 0,
-            'can_change_marked_answers': 2,
-            'can_close_threads': 1,
-        })
-        
+    @patch_category_acl({
+        'can_mark_best_answers': 0,
+        'can_change_marked_answers': 2,
+        'can_close_threads': True,
+    })
+    def test_unmark_best_answer_closed_category(self):
+        """api validates if user has permission to unmark best answer in closed category"""
+        self.category.is_closed = True
+        self.category.save()
+
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2428,14 +2284,13 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_unmark_best_answer_closed_thread(self):
+    @patch_category_acl({
+        'can_mark_best_answers': 0,
+        'can_change_marked_answers': 2,
+        'can_close_threads': False,
+    })
+    def test_unmark_best_answer_closed_thread_no_permission(self):
         """api validates if user has permission to unmark best answer in closed thread"""
         """api validates if user has permission to unmark best answer in closed thread"""
-        self.override_acl({
-            'can_mark_best_answers': 0,
-            'can_change_marked_answers': 2,
-            'can_close_threads': 0,
-        })
-
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
 
 
@@ -2460,13 +2315,16 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
-        # passing scenario is possible
-        self.override_acl({
-            'can_mark_best_answers': 0,
-            'can_change_marked_answers': 2,
-            'can_close_threads': 1,
-        })
-        
+    @patch_category_acl({
+        'can_mark_best_answers': 0,
+        'can_change_marked_answers': 2,
+        'can_close_threads': True,
+    })
+    def test_unmark_best_answer_closed_thread(self):
+        """api validates if user has permission to unmark best answer in closed thread"""
+        self.thread.is_closed = True
+        self.thread.save()
+
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {
@@ -2478,14 +2336,13 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_unmark_best_answer_protected(self):
+    @patch_category_acl({
+        'can_mark_best_answers': 0,
+        'can_change_marked_answers': 2,
+        'can_protect_posts': 0,
+    })
+    def test_unmark_best_answer_protected_no_permission(self):
         """api validates permission to unmark protected best answers"""
         """api validates permission to unmark protected best answers"""
-        self.override_acl({
-            'can_mark_best_answers': 0,
-            'can_change_marked_answers': 2,
-            'can_protect_posts': 0,
-        })
-
         self.thread.best_answer_is_protected = True
         self.thread.best_answer_is_protected = True
         self.thread.save()
         self.thread.save()
 
 
@@ -2510,13 +2367,16 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
         self.assertEqual(thread_json['best_answer'], self.best_answer.id)
 
 
-        # passing scenario is possible
-        self.override_acl({
-            'can_mark_best_answers': 0,
-            'can_change_marked_answers': 2,
-            'can_protect_posts': 1,
-        })
-        
+    @patch_category_acl({
+        'can_mark_best_answers': 0,
+        'can_change_marked_answers': 2,
+        'can_protect_posts': 1,
+    })
+    def test_unmark_best_answer_protected(self):
+        """api validates permission to unmark protected best answers"""
+        self.thread.best_answer_is_protected = True
+        self.thread.save()
+
         response = self.patch(
         response = self.patch(
             self.api_link, [
             self.api_link, [
                 {
                 {