Browse Source

#410: Tests for thread weight moderation

Rafał Pitoń 10 years ago
parent
commit
3057821ab3

+ 2 - 2
misago/threads/moderation/threads.py

@@ -20,7 +20,7 @@ def announce_thread(user, thread):
 
 
 @atomic
 @atomic
 def pin_thread(user, thread):
 def pin_thread(user, thread):
-    if thread.weight < 1:
+    if thread.weight != 1:
         thread.weight = 1
         thread.weight = 1
 
 
         message = _("%(user)s pinned thread.")
         message = _("%(user)s pinned thread.")
@@ -33,7 +33,7 @@ def pin_thread(user, thread):
 
 
 
 
 @atomic
 @atomic
-def default_thread(user, thread):
+def reset_thread(user, thread):
     if thread.weight > 0:
     if thread.weight > 0:
         if thread.is_announcement:
         if thread.is_announcement:
             message = _("%(user)s withhold announcement.")
             message = _("%(user)s withhold announcement.")

+ 163 - 2
misago/threads/tests/test_forumthreads_view.py

@@ -8,8 +8,10 @@ from misago.forums.models import Forum
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 from misago.threads import testutils
 from misago.threads import testutils
-from misago.threads.models import Label
-from misago.threads.views.generic.forum import ForumFiltering, ForumThreads
+from misago.threads.models import Thread, Label
+from misago.threads.moderation import ModerationError
+from misago.threads.views.generic.forum import (ForumActions, ForumFiltering,
+                                                ForumThreads)
 
 
 
 
 class ForumViewHelperTestCase(AuthenticatedUserTestCase):
 class ForumViewHelperTestCase(AuthenticatedUserTestCase):
@@ -26,9 +28,71 @@ class ForumViewHelperTestCase(AuthenticatedUserTestCase):
         forums_acl['visible_forums'].append(self.forum.pk)
         forums_acl['visible_forums'].append(self.forum.pk)
         forums_acl['forums'][self.forum.pk] = new_acl
         forums_acl['forums'][self.forum.pk] = new_acl
         override_acl(self.user, forums_acl)
         override_acl(self.user, forums_acl)
+
+        self.forum.acl = {}
         add_acl(self.user, self.forum)
         add_acl(self.user, self.forum)
 
 
 
 
+class MockRequest(object):
+    def __init__(self, user, method='GET', POST=None):
+        self.POST = POST or {}
+        self.user = user
+        self.session = {}
+        self.path = '/forum/fake-forum-1/'
+
+
+class ActionsTests(ForumViewHelperTestCase):
+    def setUp(self):
+        super(ActionsTests, self).setUp()
+
+        self.user._misago_real_ip = '127.0.0.1'
+
+    def test_weight_actions(self):
+        """ForumActions initializes valid list of available weights"""
+        self.override_acl({
+            'can_change_threads_weight': 0,
+        })
+
+        actions = ForumActions(user=self.user, forum=self.forum)
+        self.assertEqual(actions.available_actions, [])
+
+        self.override_acl({
+            'can_change_threads_weight': 1,
+        })
+
+        actions = ForumActions(user=self.user, forum=self.forum)
+        self.assertEqual(actions.available_actions, [
+            {
+                'action': 'pin',
+                'name': _("Change to pinned")
+            },
+            {
+                'action': 'default',
+                'name': _("Change to default")
+            },
+        ])
+
+        self.override_acl({
+            'can_change_threads_weight': 2,
+        })
+
+        actions = ForumActions(user=self.user, forum=self.forum)
+        self.assertEqual(actions.available_actions, [
+            {
+                'action': 'announce',
+                'name': _("Change to announcements")
+            },
+            {
+                'action': 'pin',
+                'name': _("Change to pinned")
+            },
+            {
+                'action': 'default',
+                'name': _("Change to default")
+            },
+        ])
+
+
 class ForumFilteringTests(ForumViewHelperTestCase):
 class ForumFilteringTests(ForumViewHelperTestCase):
     def test_get_available_filters(self):
     def test_get_available_filters(self):
         """get_available_filters returns filters varying on forum acl"""
         """get_available_filters returns filters varying on forum acl"""
@@ -518,3 +582,100 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         response = self.client.get(self.link)
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertIn(anon_title, response.content)
         self.assertIn(anon_title, response.content)
+
+    def test_moderate_threads_weight(self):
+        """moderation allows for changing threads weight"""
+        test_acl = {
+            'can_see': 1,
+            'can_browse': 1,
+            'can_see_all_threads': 1,
+            'can_change_threads_weight': 2
+        }
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("Change to announcements", response.content)
+
+        announcement = testutils.post_thread(self.forum, weight=2)
+        pinned = testutils.post_thread(self.forum, weight=1)
+        thread = testutils.post_thread(self.forum, weight=0)
+
+        # annouce nothing
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={'action': 'announce'})
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("You have to select at least one thread.",
+                      response.content)
+
+        # make announcement announcement
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'announce', 'thread': [announcement.pk]
+        })
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("No threads were changed to announcements.",
+                      response.content)
+
+        # make non-announcements announcements
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'announce', 'thread': [pinned.pk, thread.pk]
+        })
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("2 threads were changed to announcements.",
+                      response.content)
+
+        pinned = Thread.objects.get(pk=pinned.pk)
+        thread = Thread.objects.get(pk=thread.pk)
+
+        self.assertEqual(pinned.weight, 2)
+        self.assertEqual(thread.weight, 2)
+
+        # make threads pinned
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'pin', 'thread': [pinned.pk, thread.pk]
+        })
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("2 threads were pinned.", response.content)
+
+        announcement = Thread.objects.get(pk=announcement.pk)
+        pinned = Thread.objects.get(pk=pinned.pk)
+        thread = Thread.objects.get(pk=thread.pk)
+
+        self.assertEqual(announcement.weight, 2)
+        self.assertEqual(pinned.weight, 1)
+        self.assertEqual(thread.weight, 1)
+
+        # reset threads pinned
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'reset', 'thread': [thread.pk]
+        })
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("1 thread weight was reset.", response.content)
+
+        announcement = Thread.objects.get(pk=announcement.pk)
+        pinned = Thread.objects.get(pk=pinned.pk)
+        thread = Thread.objects.get(pk=thread.pk)
+
+        self.assertEqual(announcement.weight, 2)
+        self.assertEqual(pinned.weight, 1)
+        self.assertEqual(thread.weight, 0)

+ 19 - 1
misago/threads/tests/test_threads_moderation.py

@@ -1,4 +1,3 @@
-from misago.acl.testutils import override_acl
 from misago.forums.models import Forum
 from misago.forums.models import Forum
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
@@ -30,6 +29,13 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(event.icon, "star")
         self.assertEqual(event.icon, "star")
         self.assertIn("changed thread to announcement.", event.message)
         self.assertIn("changed thread to announcement.", event.message)
 
 
+    def test_announce_invalid_thread(self):
+        """announce_thread returns false for already announced thread"""
+        self.thread.weight = 2
+
+        self.assertFalse(moderation.announce_thread(self.user, self.thread))
+        self.assertEqual(self.thread.weight, 2)
+
     def test_pin_thread(self):
     def test_pin_thread(self):
         """pin_thread makes thread pinned"""
         """pin_thread makes thread pinned"""
         self.assertEqual(self.thread.weight, 0)
         self.assertEqual(self.thread.weight, 0)
@@ -44,6 +50,13 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(event.icon, "bookmark")
         self.assertEqual(event.icon, "bookmark")
         self.assertIn("pinned thread.", event.message)
         self.assertIn("pinned thread.", event.message)
 
 
+    def test_pin_invalid_thread(self):
+        """pin_thread returns false for already pinned thread"""
+        self.thread.weight = 1
+
+        self.assertFalse(moderation.pin_thread(self.user, self.thread))
+        self.assertEqual(self.thread.weight, 1)
+
     def test_default_thread(self):
     def test_default_thread(self):
         """default_thread defaults thread weight"""
         """default_thread defaults thread weight"""
         moderation.pin_thread(self.user, self.thread)
         moderation.pin_thread(self.user, self.thread)
@@ -59,3 +72,8 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
 
 
         self.assertIn("unpinned thread.", event.message)
         self.assertIn("unpinned thread.", event.message)
         self.assertEqual(event.icon, "circle")
         self.assertEqual(event.icon, "circle")
+
+    def test_default_invalid_thread(self):
+        """default_thread returns false for already default thread"""
+        self.assertFalse(moderation.default_thread(self.user, self.thread))
+        self.assertEqual(self.thread.weight, 0)

+ 116 - 1
misago/threads/tests/test_threadslist_view.py

@@ -1,7 +1,122 @@
 from django.test import TestCase
 from django.test import TestCase
 
 
+from misago.threads.moderation import ModerationError
+from misago.threads.views.generic.threads import Actions, Sorting
 
 
-from misago.threads.views.generic.threads import Sorting
+from misago.users.testutils import AuthenticatedUserTestCase
+
+
+class MockRequest(object):
+    def __init__(self, user, method='GET', POST=None):
+        self.POST = POST or {}
+        self.user = user
+        self.session = {}
+        self.path = '/cool-threads/'
+
+
+class MockActions(Actions):
+    def get_available_actions(self, kwargs):
+        return []
+
+    def action_test(self):
+        pass
+
+
+class ActionsTests(AuthenticatedUserTestCase):
+    def test_resolve_valid_action(self):
+        """resolve_action returns valid action"""
+        actions = MockActions(user=self.user)
+
+        actions.available_actions = [{
+            'action': 'test',
+            'name': "Test action"
+        }]
+
+        resolution = actions.resolve_action(MockRequest(
+            user=self.user,
+            POST={'action': 'test'},
+        ))
+
+        self.assertEqual(resolution[0], actions.action_test)
+        self.assertIsNone(resolution[1])
+
+    def test_resolve_arg_action(self):
+        """resolve_action returns valid action and argument"""
+        actions = MockActions(user=self.user)
+
+        actions.available_actions = [{
+            'action': 'test:',
+            'name': "Test action"
+        }]
+
+        resolution = actions.resolve_action(MockRequest(
+            user=self.user,
+            POST={'action': 'test:1234'},
+        ))
+
+        self.assertEqual(resolution[0], actions.action_test)
+        self.assertEqual(resolution[1], '1234')
+
+    def test_resolve_invalid_action(self):
+        """resolve_action handles invalid actions gracefully"""
+        actions = MockActions(user=self.user)
+
+        actions.available_actions = [{
+            'action': 'test',
+            'name': "Test action"
+        }]
+
+        with self.assertRaises(ModerationError):
+            resolution = actions.resolve_action(MockRequest(
+                user=self.user,
+                POST={'action': 'test:1234'},
+            ))
+
+        with self.assertRaises(ModerationError):
+            resolution = actions.resolve_action(MockRequest(
+                user=self.user,
+                POST={'action': 'test:1234'},
+            ))
+
+        actions.available_actions = [{
+            'action': 'test:',
+            'name': "Test action"
+        }]
+
+        with self.assertRaises(ModerationError):
+            resolution = actions.resolve_action(MockRequest(
+                user=self.user,
+                POST={'action': 'test'},
+            ))
+
+    def test_clean_selection(self):
+        """clean_selection clears valid input"""
+        actions = MockActions(user=self.user)
+        self.assertEqual(actions.clean_selection(['1', '-', '9']), [1, 9])
+
+    def test_clean_invalid_selection(self):
+        """clean_selection raises exception for invalid/empty input"""
+        actions = MockActions(user=self.user)
+        with self.assertRaises(ModerationError):
+            actions.clean_selection([])
+
+        with self.assertRaises(ModerationError):
+            actions.clean_selection(['abc'])
+
+    def get_list(self):
+        """get_list returns list of available actions"""
+        actions = MockActions(user=self.user)
+        actions.available_actions = [{
+            'action': 'test:',
+            'name': "Test action"
+        }]
+        self.assertEqual(actions.get_list(), actions.available_actions)
+
+    def get_selected_ids(self):
+        """get_selected_ids returns list of selected items"""
+        actions = MockActions(user=self.user)
+        actions.selected_ids = [1, 2, 4, 5, 6]
+        self.assertEqual(actions.get_selected_ids(), actions.selected_ids)
 
 
 
 
 class SortingTests(TestCase):
 class SortingTests(TestCase):

+ 1 - 0
misago/threads/testutils.py

@@ -79,4 +79,5 @@ def reply_thread(thread, poster="Tester", message='I am test message',
     thread.save()
     thread.save()
     thread.forum.synchronize()
     thread.forum.synchronize()
     thread.forum.save()
     thread.forum.save()
+
     return post
     return post

+ 7 - 8
misago/threads/views/generic/forum.py

@@ -33,10 +33,9 @@ class ForumActions(Actions):
                 'name': _("Change to pinned")
                 'name': _("Change to pinned")
             })
             })
             actions.append({
             actions.append({
-                'action': 'default',
-                'name': _("Change to default")
+                'action': 'reset',
+                'name': _("Reset threads weight")
             })
             })
-
         return actions
         return actions
 
 
     def action_announce(self, request, threads):
     def action_announce(self, request, threads):
@@ -71,20 +70,20 @@ class ForumActions(Actions):
             message = ("No threads were pinned.")
             message = ("No threads were pinned.")
             messages.info(request, message)
             messages.info(request, message)
 
 
-    def action_default(self, request, threads):
+    def action_reset(self, request, threads):
         changed_threads = 0
         changed_threads = 0
         for thread in threads:
         for thread in threads:
-            if moderation.default_thread(request.user, thread):
+            if moderation.reset_thread(request.user, thread):
                 changed_threads += 1
                 changed_threads += 1
 
 
         if changed_threads:
         if changed_threads:
             message = ungettext(
             message = ungettext(
-                '%(changed)d thread weight was changed to default.',
-                '%(changed)d threads weight was changed to default.',
+                '%(changed)d thread weight was reset.',
+                '%(changed)d threads weight was reset.',
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads weight was changed to default.")
+            message = ("No threads weight was reset.")
             messages.info(request, message)
             messages.info(request, message)
 
 
 
 

+ 4 - 2
misago/threads/views/generic/threads.py

@@ -16,7 +16,7 @@ __all__ = ['Actions', 'Sorting', 'Threads', 'ThreadsView']
 
 
 class Actions(object):
 class Actions(object):
     select_threads_message = ugettext_lazy(
     select_threads_message = ugettext_lazy(
-        "You have to select at least one thread")
+        "You have to select at least one thread.")
 
 
     def __init__(self, **kwargs):
     def __init__(self, **kwargs):
         if kwargs.get('user').is_authenticated():
         if kwargs.get('user').is_authenticated():
@@ -33,13 +33,15 @@ class Actions(object):
         action_name = request.POST.get('action')
         action_name = request.POST.get('action')
         if ':' in action_name:
         if ':' in action_name:
             action_bits = action_name.split(':')
             action_bits = action_name.split(':')
-            action_name = action_bits[0]
+            action_name = '%s:' % action_bits[0]
             action_arg = action_bits[1]
             action_arg = action_bits[1]
         else:
         else:
             action_arg = None
             action_arg = None
 
 
         for action in self.available_actions:
         for action in self.available_actions:
             if action['action'] == action_name:
             if action['action'] == action_name:
+                if action_name[-1] == ':':
+                    action_name = action_name[:-1]
                 action_callable = 'action_%s' % action_name
                 action_callable = 'action_%s' % action_name
                 return getattr(self, action_callable), action_arg
                 return getattr(self, action_callable), action_arg
         else:
         else: