Browse Source

Rename testutils modules to test

rafalp 6 years ago
parent
commit
e1eea12159
146 changed files with 931 additions and 931 deletions
  1. 1 1
      misago/acl/tests/test_roleadmin_views.py
  2. 1 1
      misago/admin/test.py
  3. 1 1
      misago/admin/tests/test_admin_index.py
  4. 2 2
      misago/admin/tests/test_admin_views.py
  5. 5 5
      misago/categories/tests/test_categories_admin_views.py
  6. 2 2
      misago/categories/tests/test_category_model.py
  7. 1 1
      misago/categories/tests/test_permissions_admin_views.py
  8. 13 13
      misago/categories/tests/test_prunecategories.py
  9. 3 3
      misago/categories/tests/test_synchronizecategories.py
  10. 1 1
      misago/categories/tests/test_utils.py
  11. 1 1
      misago/categories/tests/test_views.py
  12. 1 1
      misago/conf/tests/test_admin_views.py
  13. 1 1
      misago/conftest.py
  14. 1 1
      misago/core/tests/test_common_middleware_redirect.py
  15. 1 1
      misago/core/tests/test_mail.py
  16. 4 4
      misago/core/tests/test_serializers.py
  17. 1 1
      misago/legal/tests/test_admin_views.py
  18. 1 1
      misago/legal/tests/test_api.py
  19. 1 1
      misago/legal/tests/test_context_processors.py
  20. 1 1
      misago/legal/tests/test_required_agreement.py
  21. 1 1
      misago/legal/tests/test_utils.py
  22. 1 1
      misago/markup/tests/test_api.py
  23. 1 1
      misago/markup/tests/test_mentions.py
  24. 1 1
      misago/markup/tests/test_parser.py
  25. 26 26
      misago/readtracker/tests/test_categoriestracker.py
  26. 3 3
      misago/readtracker/tests/test_clearreadtracker.py
  27. 9 9
      misago/readtracker/tests/test_poststracker.py
  28. 21 21
      misago/readtracker/tests/test_threadstracker.py
  29. 1 1
      misago/search/tests/test_api.py
  30. 1 1
      misago/search/tests/test_views.py
  31. 218 0
      misago/threads/test.py
  32. 8 8
      misago/threads/tests/test_anonymize_data.py
  33. 3 3
      misago/threads/tests/test_attachmentadmin_views.py
  34. 1 1
      misago/threads/tests/test_attachments_api.py
  35. 4 4
      misago/threads/tests/test_attachments_middleware.py
  36. 1 1
      misago/threads/tests/test_attachmenttypeadmin_views.py
  37. 3 3
      misago/threads/tests/test_attachmentview.py
  38. 2 2
      misago/threads/tests/test_clearattachments.py
  39. 4 4
      misago/threads/tests/test_delete_user_likes.py
  40. 4 4
      misago/threads/tests/test_emailnotification_middleware.py
  41. 1 1
      misago/threads/tests/test_events.py
  42. 3 3
      misago/threads/tests/test_floodprotection.py
  43. 1 1
      misago/threads/tests/test_floodprotection_middleware.py
  44. 27 27
      misago/threads/tests/test_gotoviews.py
  45. 7 7
      misago/threads/tests/test_mergeconflict.py
  46. 1 1
      misago/threads/tests/test_participants.py
  47. 3 3
      misago/threads/tests/test_post_mentions.py
  48. 1 1
      misago/threads/tests/test_post_model.py
  49. 4 4
      misago/threads/tests/test_posts_moderation.py
  50. 3 3
      misago/threads/tests/test_privatethread_patch_api.py
  51. 3 3
      misago/threads/tests/test_privatethread_reply_api.py
  52. 1 1
      misago/threads/tests/test_privatethread_start_api.py
  53. 2 2
      misago/threads/tests/test_privatethread_view.py
  54. 1 1
      misago/threads/tests/test_privatethreads.py
  55. 6 6
      misago/threads/tests/test_privatethreads_api.py
  56. 4 4
      misago/threads/tests/test_privatethreads_lists.py
  57. 18 18
      misago/threads/tests/test_search.py
  58. 4 4
      misago/threads/tests/test_subscription_middleware.py
  59. 3 3
      misago/threads/tests/test_subscriptions.py
  60. 4 4
      misago/threads/tests/test_sync_unread_private_threads.py
  61. 3 3
      misago/threads/tests/test_synchronizethreads.py
  62. 6 6
      misago/threads/tests/test_thread_bulkpatch_api.py
  63. 4 4
      misago/threads/tests/test_thread_editreply_api.py
  64. 52 52
      misago/threads/tests/test_thread_merge_api.py
  65. 5 5
      misago/threads/tests/test_thread_model.py
  66. 28 28
      misago/threads/tests/test_thread_patch_api.py
  67. 4 4
      misago/threads/tests/test_thread_poll_api.py
  68. 6 6
      misago/threads/tests/test_thread_postbulkdelete_api.py
  69. 8 8
      misago/threads/tests/test_thread_postbulkpatch_api.py
  70. 3 3
      misago/threads/tests/test_thread_postdelete_api.py
  71. 2 2
      misago/threads/tests/test_thread_postedits_api.py
  72. 4 4
      misago/threads/tests/test_thread_postlikes_api.py
  73. 42 42
      misago/threads/tests/test_thread_postmerge_api.py
  74. 36 36
      misago/threads/tests/test_thread_postmove_api.py
  75. 11 11
      misago/threads/tests/test_thread_postpatch_api.py
  76. 2 2
      misago/threads/tests/test_thread_postread_api.py
  77. 13 13
      misago/threads/tests/test_thread_postsplit_api.py
  78. 3 3
      misago/threads/tests/test_thread_reply_api.py
  79. 1 1
      misago/threads/tests/test_thread_start_api.py
  80. 1 1
      misago/threads/tests/test_threadparticipant_model.py
  81. 6 6
      misago/threads/tests/test_threads_api.py
  82. 4 4
      misago/threads/tests/test_threads_bulkdelete_api.py
  83. 11 11
      misago/threads/tests/test_threads_editor_api.py
  84. 57 57
      misago/threads/tests/test_threads_merge_api.py
  85. 4 4
      misago/threads/tests/test_threads_moderation.py
  86. 46 46
      misago/threads/tests/test_threadslists.py
  87. 18 18
      misago/threads/tests/test_threadview.py
  88. 3 3
      misago/threads/tests/test_updatepostschecksums.py
  89. 9 9
      misago/threads/tests/test_utils.py
  90. 1 1
      misago/threads/tests/test_validate_post.py
  91. 0 218
      misago/threads/testutils.py
  92. 0 0
      misago/users/test.py
  93. 1 1
      misago/users/tests/test_activation_views.py
  94. 2 2
      misago/users/tests/test_activepostersranking.py
  95. 1 1
      misago/users/tests/test_audittrail.py
  96. 1 1
      misago/users/tests/test_auth_api.py
  97. 1 1
      misago/users/tests/test_auth_backend.py
  98. 1 1
      misago/users/tests/test_avatars.py
  99. 1 1
      misago/users/tests/test_avatarserver_views.py
  100. 1 1
      misago/users/tests/test_banadmin_views.py
  101. 1 1
      misago/users/tests/test_bans.py
  102. 1 1
      misago/users/tests/test_bio_profilefield.py
  103. 1 1
      misago/users/tests/test_credentialchange.py
  104. 2 2
      misago/users/tests/test_datadownloads.py
  105. 1 1
      misago/users/tests/test_datadownloads_dataarchive.py
  106. 2 2
      misago/users/tests/test_datadownloadsadmin_views.py
  107. 1 1
      misago/users/tests/test_decorators.py
  108. 1 1
      misago/users/tests/test_deleteinactiveusers.py
  109. 1 1
      misago/users/tests/test_deletemarkedusers.py
  110. 1 1
      misago/users/tests/test_deleteprofilefield.py
  111. 1 1
      misago/users/tests/test_djangoadmin_auth.py
  112. 2 2
      misago/users/tests/test_djangoadmin_user.py
  113. 1 1
      misago/users/tests/test_expireuserdatadownloads.py
  114. 1 1
      misago/users/tests/test_forgottenpassword_views.py
  115. 1 1
      misago/users/tests/test_gender_profilefield.py
  116. 1 1
      misago/users/tests/test_getting_user_status.py
  117. 1 1
      misago/users/tests/test_invalidatebans.py
  118. 1 1
      misago/users/tests/test_joinip_profilefield.py
  119. 2 2
      misago/users/tests/test_lists_views.py
  120. 1 1
      misago/users/tests/test_listusedprofilefields.py
  121. 1 1
      misago/users/tests/test_mention_api.py
  122. 1 1
      misago/users/tests/test_options_views.py
  123. 1 1
      misago/users/tests/test_prepareuserdatadownloads.py
  124. 8 8
      misago/users/tests/test_profile_views.py
  125. 1 1
      misago/users/tests/test_rankadmin_views.py
  126. 1 1
      misago/users/tests/test_rest_permissions.py
  127. 1 1
      misago/users/tests/test_search.py
  128. 1 1
      misago/users/tests/test_social_pipeline.py
  129. 1 1
      misago/users/tests/test_testutils.py
  130. 1 1
      misago/users/tests/test_twitter_profilefield.py
  131. 1 1
      misago/users/tests/test_user_avatar_api.py
  132. 1 1
      misago/users/tests/test_user_changeemail_api.py
  133. 1 1
      misago/users/tests/test_user_changepassword_api.py
  134. 1 1
      misago/users/tests/test_user_create_api.py
  135. 1 1
      misago/users/tests/test_user_datadownloads_api.py
  136. 1 1
      misago/users/tests/test_user_details_api.py
  137. 1 1
      misago/users/tests/test_user_editdetails_api.py
  138. 17 17
      misago/users/tests/test_user_feeds_api.py
  139. 1 1
      misago/users/tests/test_user_middleware.py
  140. 1 1
      misago/users/tests/test_user_requestdatadownload_api.py
  141. 1 1
      misago/users/tests/test_user_signature_api.py
  142. 1 1
      misago/users/tests/test_user_username_api.py
  143. 3 3
      misago/users/tests/test_useradmin_views.py
  144. 1 1
      misago/users/tests/test_usernamechanges_api.py
  145. 2 2
      misago/users/tests/test_users_api.py
  146. 1 1
      misago/users/tests/test_validators.py

+ 1 - 1
misago/acl/tests/test_roleadmin_views.py

@@ -4,7 +4,7 @@ from misago.acl import ACL_CACHE
 from misago.acl.models import Role
 from misago.acl.test import mock_role_form_data
 from misago.cache.test import assert_invalidates_cache
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 
 
 def create_data(data_dict):

+ 1 - 1
misago/admin/testutils.py → misago/admin/test.py

@@ -1,6 +1,6 @@
 from django.urls import reverse
 
-from misago.users.testutils import SuperUserTestCase
+from misago.users.test import SuperUserTestCase
 
 
 class AdminTestCase(SuperUserTestCase):

+ 1 - 1
misago/admin/tests/test_admin_index.py

@@ -1,7 +1,7 @@
 from django.test import TestCase, override_settings
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.admin.views.index import check_misago_address
 
 

+ 2 - 2
misago/admin/tests/test_admin_views.py

@@ -1,9 +1,9 @@
 from django.test import TestCase
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.admin.views import get_protected_namespace
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class MockRequest(object):

+ 5 - 5
misago/categories/tests/test_categories_admin_views.py

@@ -1,10 +1,10 @@
 from django.urls import reverse
 
 from misago.acl import ACL_CACHE
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.cache.test import assert_invalidates_cache
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread
 
 
@@ -472,7 +472,7 @@ class CategoryAdminDeleteViewTests(CategoryAdminTestCase):
     def test_delete_category_move_contents(self):
         """category was deleted and its contents were moved"""
         for _ in range(10):
-            testutils.post_thread(self.category_b)
+            test.post_thread(self.category_b)
         self.assertEqual(Thread.objects.count(), 10)
 
         response = self.client.get(
@@ -514,7 +514,7 @@ class CategoryAdminDeleteViewTests(CategoryAdminTestCase):
     def test_delete_category_and_contents(self):
         """category and its contents were deleted"""
         for _ in range(10):
-            testutils.post_thread(self.category_b)
+            test.post_thread(self.category_b)
 
         response = self.client.get(
             reverse(
@@ -549,7 +549,7 @@ class CategoryAdminDeleteViewTests(CategoryAdminTestCase):
     def test_delete_leaf_category_and_contents(self):
         """leaf category was deleted with contents"""
         for _ in range(10):
-            testutils.post_thread(self.category_d)
+            test.post_thread(self.category_d)
         self.assertEqual(Thread.objects.count(), 10)
 
         response = self.client.get(

+ 2 - 2
misago/categories/tests/test_category_model.py

@@ -2,7 +2,7 @@ from django.test import TestCase
 
 from misago.categories import THREADS_ROOT_NAME
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.threadtypes import trees_map
 
 
@@ -53,7 +53,7 @@ class CategoryModelTests(TestCase):
         self.category = Category.objects.all_categories()[:1][0]
 
     def create_thread(self):
-        return testutils.post_thread(self.category)
+        return test.post_thread(self.category)
 
     def assertCategoryIsEmpty(self):
         self.assertIsNone(self.category.last_post_on)

+ 1 - 1
misago/categories/tests/test_permissions_admin_views.py

@@ -3,7 +3,7 @@ from django.urls import reverse
 from misago.acl import ACL_CACHE
 from misago.acl.models import Role
 from misago.acl.test import mock_role_form_data
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.cache.test import assert_invalidates_cache
 from misago.categories.models import Category, CategoryRole
 

+ 13 - 13
misago/categories/tests/test_prunecategories.py

@@ -7,7 +7,7 @@ from django.utils import timezone
 
 from misago.categories.management.commands import prunecategories
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 
 
 class PruneCategoriesTests(TestCase):
@@ -22,11 +22,11 @@ class PruneCategoriesTests(TestCase):
         started_on = timezone.now() - timedelta(days=30)
         posted_on = timezone.now()
         for _ in range(10):
-            thread = testutils.post_thread(category, started_on=started_on)
-            testutils.reply_thread(thread, posted_on=posted_on)
+            thread = test.post_thread(category, started_on=started_on)
+            test.reply_thread(thread, posted_on=posted_on)
 
         # post recent threads that will be preserved
-        threads = [testutils.post_thread(category) for _ in range(10)]
+        threads = [test.post_thread(category) for _ in range(10)]
 
         category.synchronize()
         self.assertEqual(category.threads, 20)
@@ -58,11 +58,11 @@ class PruneCategoriesTests(TestCase):
         # post old threads with recent replies
         started_on = timezone.now() - timedelta(days=30)
         for _ in range(10):
-            thread = testutils.post_thread(category, started_on=started_on)
-            testutils.reply_thread(thread)
+            thread = test.post_thread(category, started_on=started_on)
+            test.reply_thread(thread)
 
         # post recent threads that will be preserved
-        threads = [testutils.post_thread(category) for _ in range(10)]
+        threads = [test.post_thread(category) for _ in range(10)]
 
         category.synchronize()
         self.assertEqual(category.threads, 20)
@@ -99,11 +99,11 @@ class PruneCategoriesTests(TestCase):
         started_on = timezone.now() - timedelta(days=30)
         posted_on = timezone.now()
         for _ in range(10):
-            thread = testutils.post_thread(category, started_on=started_on)
-            testutils.reply_thread(thread, posted_on=posted_on)
+            thread = test.post_thread(category, started_on=started_on)
+            test.reply_thread(thread, posted_on=posted_on)
 
         # post recent threads that will be preserved
-        threads = [testutils.post_thread(category) for _ in range(10)]
+        threads = [test.post_thread(category) for _ in range(10)]
 
         category.synchronize()
         self.assertEqual(category.threads, 20)
@@ -143,11 +143,11 @@ class PruneCategoriesTests(TestCase):
         # post old threads with recent replies
         started_on = timezone.now() - timedelta(days=30)
         for _ in range(10):
-            thread = testutils.post_thread(category, started_on=started_on)
-            testutils.reply_thread(thread)
+            thread = test.post_thread(category, started_on=started_on)
+            test.reply_thread(thread)
 
         # post recent threads that will be preserved
-        threads = [testutils.post_thread(category) for _ in range(10)]
+        threads = [test.post_thread(category) for _ in range(10)]
 
         category.synchronize()
         self.assertEqual(category.threads, 20)

+ 3 - 3
misago/categories/tests/test_synchronizecategories.py

@@ -5,7 +5,7 @@ from django.test import TestCase
 
 from misago.categories.management.commands import synchronizecategories
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 
 
 class SynchronizeCategoriesTests(TestCase):
@@ -13,9 +13,9 @@ class SynchronizeCategoriesTests(TestCase):
         """command synchronizes categories"""
         category = Category.objects.all_categories()[:1][0]
 
-        threads = [testutils.post_thread(category) for _ in range(10)]
+        threads = [test.post_thread(category) for _ in range(10)]
         for thread in threads:
-            [testutils.reply_thread(thread) for _ in range(5)]
+            [test.reply_thread(thread) for _ in range(5)]
 
         category.threads = 0
         category.posts = 0

+ 1 - 1
misago/categories/tests/test_utils.py

@@ -2,7 +2,7 @@ from misago.acl.useracl import get_user_acl
 from misago.categories.models import Category
 from misago.categories.utils import get_categories_tree, get_category_path
 from misago.conftest import get_cache_versions
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 cache_versions = get_cache_versions()
 

+ 1 - 1
misago/categories/tests/test_views.py

@@ -4,7 +4,7 @@ from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class CategoryViewsTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/conf/tests/test_admin_views.py

@@ -1,6 +1,6 @@
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.conf.models import SettingsGroup
 
 

+ 1 - 1
misago/conftest.py

@@ -6,7 +6,7 @@ from misago.conf.dynamicsettings import DynamicSettings
 from misago.conf.staticsettings import StaticSettings
 from misago.users.constants import BANS_CACHE
 from misago.users.models import AnonymousUser
-from misago.users.testutils import create_test_superuser, create_test_user
+from misago.users.test import create_test_superuser, create_test_user
 
 
 def get_cache_versions():

+ 1 - 1
misago/core/tests/test_common_middleware_redirect.py

@@ -1,4 +1,4 @@
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class CommonMiddlewareRedirectTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/core/tests/test_mail.py

@@ -5,7 +5,7 @@ from django.urls import reverse
 from misago.cache.versions import get_cache_versions
 from misago.conf.dynamicsettings import DynamicSettings
 from misago.core.mail import build_mail, mail_user, mail_users
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class MailTests(TestCase):

+ 4 - 4
misago/core/tests/test_serializers.py

@@ -4,7 +4,7 @@ from django.test import TestCase
 
 from misago.categories.models import Category
 from misago.core.serializers import MutableFields
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread
 
 
@@ -34,7 +34,7 @@ class MutableFieldsSerializerTests(TestCase):
     def test_subset_fields(self):
         """classmethod subset_fields creates new serializer"""
         category = Category.objects.get(slug="first-category")
-        thread = testutils.post_thread(category=category)
+        thread = test.post_thread(category=category)
 
         fields = ["id", "title", "replies", "last_poster_name"]
 
@@ -60,7 +60,7 @@ class MutableFieldsSerializerTests(TestCase):
     def test_exclude_fields(self):
         """classmethod exclude_fields creates new serializer"""
         category = Category.objects.get(slug="first-category")
-        thread = testutils.post_thread(category=category)
+        thread = test.post_thread(category=category)
 
         kept_fields = ["id", "title", "weight"]
         removed_fields = list(set(Serializer.Meta.fields) - set(kept_fields))
@@ -80,7 +80,7 @@ class MutableFieldsSerializerTests(TestCase):
     def test_extend_fields(self):
         """classmethod extend_fields creates new serializer"""
         category = Category.objects.get(slug="first-category")
-        thread = testutils.post_thread(category=category)
+        thread = test.post_thread(category=category)
 
         serializer = Serializer.extend_fields("category")
 

+ 1 - 1
misago/legal/tests/test_admin_views.py

@@ -1,6 +1,6 @@
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.legal.models import Agreement
 
 

+ 1 - 1
misago/legal/tests/test_api.py

@@ -3,7 +3,7 @@ import json
 from django.urls import reverse
 
 from misago.legal.models import Agreement
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class SubmitAgreementTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/legal/tests/test_context_processors.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 
 from misago.legal.context_processors import legal_links
 from misago.legal.models import Agreement
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class MockRequest(object):

+ 1 - 1
misago/legal/tests/test_required_agreement.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.legal.models import Agreement
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class RequiredAgreementTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/legal/tests/test_utils.py

@@ -7,7 +7,7 @@ from misago.legal.utils import (
     save_user_agreement_acceptance,
     set_agreement_as_active,
 )
-from misago.users.testutils import UserTestCase
+from misago.users.test import UserTestCase
 
 
 class MockRequest(object):

+ 1 - 1
misago/markup/tests/test_api.py

@@ -1,6 +1,6 @@
 from django.urls import reverse
 
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ParseMarkupApiTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/markup/tests/test_mentions.py

@@ -1,5 +1,5 @@
 from misago.markup.mentions import add_mentions
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class MockRequest(object):

+ 1 - 1
misago/markup/tests/test_parser.py

@@ -1,7 +1,7 @@
 from django.test import TestCase
 
 from misago.markup.parser import parse
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class MockRequest(object):

+ 26 - 26
misago/readtracker/tests/test_categoriestracker.py

@@ -9,8 +9,8 @@ from misago.conf import settings
 from misago.conftest import get_cache_versions
 from misago.readtracker import poststracker, categoriestracker
 from misago.readtracker.models import PostRead
-from misago.threads import testutils
-from misago.users.testutils import create_test_user
+from misago.threads import test
+from misago.users.test import create_test_user
 
 cache_versions = get_cache_versions()
 
@@ -35,7 +35,7 @@ class CategoriesTrackerTests(TestCase):
     def test_anon_thread_before_cutoff(self):
         """non-tracked thread is marked as read for anonymous users"""
         started_on = timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF)
-        testutils.post_thread(self.category, started_on=started_on)
+        test.post_thread(self.category, started_on=started_on)
 
         categoriestracker.make_read_aware(AnonymousUser(), None, self.category)
         self.assertTrue(self.category.is_read)
@@ -43,7 +43,7 @@ class CategoriesTrackerTests(TestCase):
 
     def test_anon_thread_after_cutoff(self):
         """tracked thread is marked as read for anonymous users"""
-        testutils.post_thread(self.category, started_on=timezone.now())
+        test.post_thread(self.category, started_on=timezone.now())
 
         categoriestracker.make_read_aware(AnonymousUser(), None, self.category)
         self.assertTrue(self.category.is_read)
@@ -52,7 +52,7 @@ class CategoriesTrackerTests(TestCase):
     def test_user_thread_before_cutoff(self):
         """non-tracked thread is marked as read for authenticated users"""
         started_on = timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF)
-        testutils.post_thread(self.category, started_on=started_on)
+        test.post_thread(self.category, started_on=started_on)
 
         categoriestracker.make_read_aware(self.user, self.user_acl, self.category)
         self.assertTrue(self.category.is_read)
@@ -60,7 +60,7 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_unread_thread(self):
         """tracked thread is marked as unread for authenticated users"""
-        testutils.post_thread(self.category, started_on=timezone.now())
+        test.post_thread(self.category, started_on=timezone.now())
 
         categoriestracker.make_read_aware(self.user, self.user_acl, self.category)
         self.assertFalse(self.category.is_read)
@@ -69,7 +69,7 @@ class CategoriesTrackerTests(TestCase):
     def test_user_created_after_thread(self):
         """tracked thread older than user is marked as read"""
         started_on = timezone.now() - timedelta(days=1)
-        testutils.post_thread(self.category, started_on=started_on)
+        test.post_thread(self.category, started_on=started_on)
 
         categoriestracker.make_read_aware(self.user, self.user_acl, self.category)
         self.assertTrue(self.category.is_read)
@@ -77,7 +77,7 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_read_post(self):
         """tracked thread with read post marked as read"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
 
         poststracker.save_read(self.user, thread.first_post)
 
@@ -87,9 +87,9 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_first_unread_last_read_post(self):
         """tracked thread with unread first and last read post marked as unread"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
 
-        post = testutils.reply_thread(thread, posted_on=timezone.now())
+        post = test.reply_thread(thread, posted_on=timezone.now())
         poststracker.save_read(self.user, post)
 
         categoriestracker.make_read_aware(self.user, self.user_acl, self.category)
@@ -98,10 +98,10 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_first_read_post_unread_event(self):
         """tracked thread with read first post and unread event"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(thread, posted_on=timezone.now(), is_event=True)
+        test.reply_thread(thread, posted_on=timezone.now(), is_event=True)
 
         categoriestracker.make_read_aware(self.user, self.user_acl, self.category)
         self.assertFalse(self.category.is_read)
@@ -109,9 +109,9 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_hidden_event(self):
         """tracked thread with unread first post and hidden event"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
 
-        testutils.reply_thread(
+        test.reply_thread(
             thread, posted_on=timezone.now(), is_event=True, is_hidden=True
         )
 
@@ -121,10 +121,10 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_first_read_post_hidden_event(self):
         """tracked thread with read first post and hidden event"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(
+        test.reply_thread(
             thread, posted_on=timezone.now(), is_event=True, is_hidden=True
         )
 
@@ -135,7 +135,7 @@ class CategoriesTrackerTests(TestCase):
     def test_user_thread_before_cutoff_unread_post(self):
         """non-tracked thread is marked as unread for anonymous users"""
         started_on = timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF)
-        testutils.post_thread(self.category, started_on=started_on)
+        test.post_thread(self.category, started_on=started_on)
 
         categoriestracker.make_read_aware(self.user, self.user_acl, self.category)
         self.assertTrue(self.category.is_read)
@@ -143,10 +143,10 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_first_read_post_unapproved_post(self):
         """tracked thread with read first post and unapproved post"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(thread, posted_on=timezone.now(), is_unapproved=True)
+        test.reply_thread(thread, posted_on=timezone.now(), is_unapproved=True)
 
         categoriestracker.make_read_aware(self.user, self.user_acl, self.category)
         self.assertTrue(self.category.is_read)
@@ -154,10 +154,10 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_first_read_post_unapproved_own_post(self):
         """tracked thread with read first post and unapproved own post"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(
+        test.reply_thread(
             thread, posted_on=timezone.now(), poster=self.user, is_unapproved=True
         )
 
@@ -167,10 +167,10 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_first_read_post_unapproved_own_post(self):
         """tracked thread with read first post and unapproved own post"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(
+        test.reply_thread(
             thread, posted_on=timezone.now(), poster=self.user, is_unapproved=True
         )
 
@@ -180,7 +180,7 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_unapproved_thread_unread_post(self):
         """tracked unapproved thread"""
-        thread = testutils.post_thread(
+        thread = test.post_thread(
             self.category, started_on=timezone.now(), is_unapproved=True
         )
 
@@ -190,7 +190,7 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_unapproved_own_thread_unread_post(self):
         """tracked unapproved but visible thread"""
-        thread = testutils.post_thread(
+        thread = test.post_thread(
             self.category,
             poster=self.user,
             started_on=timezone.now(),
@@ -203,7 +203,7 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_hidden_thread_unread_post(self):
         """tracked hidden thread"""
-        thread = testutils.post_thread(
+        thread = test.post_thread(
             self.category, started_on=timezone.now(), is_hidden=True
         )
 

+ 3 - 3
misago/readtracker/tests/test_clearreadtracker.py

@@ -9,8 +9,8 @@ from misago.categories.models import Category
 from misago.conf import settings
 from misago.readtracker.management.commands import clearreadtracker
 from misago.readtracker.models import PostRead
-from misago.threads import testutils
-from misago.users.testutils import create_test_user
+from misago.threads import test
+from misago.users.test import create_test_user
 
 
 class ClearReadTrackerTests(TestCase):
@@ -32,7 +32,7 @@ class ClearReadTrackerTests(TestCase):
 
     def test_delete_expired_entries(self):
         """test deletes one expired tracker entry, but spares the other"""
-        thread = testutils.post_thread(self.category)
+        thread = test.post_thread(self.category)
 
         existing = PostRead.objects.create(
             user=self.user,

+ 9 - 9
misago/readtracker/tests/test_poststracker.py

@@ -7,8 +7,8 @@ from misago.categories.models import Category
 from misago.conf import settings
 from misago.readtracker import poststracker
 from misago.readtracker.models import PostRead
-from misago.threads import testutils
-from misago.users.testutils import create_test_user
+from misago.threads import test
+from misago.users.test import create_test_user
 
 
 class AnonymousUser(object):
@@ -20,7 +20,7 @@ class PostsTrackerTests(TestCase):
     def setUp(self):
         self.user = create_test_user("User", "user@example.com")
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(self.category)
+        self.thread = test.post_thread(self.category)
 
     def test_falsy_value(self):
         """passing falsy value to readtracker causes no errors"""
@@ -31,7 +31,7 @@ class PostsTrackerTests(TestCase):
     def test_anon_post_before_cutoff(self):
         """non-tracked post is marked as read for anonymous users"""
         posted_on = timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF)
-        post = testutils.reply_thread(self.thread, posted_on=posted_on)
+        post = test.reply_thread(self.thread, posted_on=posted_on)
 
         poststracker.make_read_aware(AnonymousUser(), post)
         self.assertTrue(post.is_read)
@@ -39,7 +39,7 @@ class PostsTrackerTests(TestCase):
 
     def test_anon_post_after_cutoff(self):
         """tracked post is marked as read for anonymous users"""
-        post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+        post = test.reply_thread(self.thread, posted_on=timezone.now())
 
         poststracker.make_read_aware(AnonymousUser(), post)
         self.assertTrue(post.is_read)
@@ -48,7 +48,7 @@ class PostsTrackerTests(TestCase):
     def test_user_post_before_cutoff(self):
         """untracked post is marked as read for authenticated users"""
         posted_on = timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF)
-        post = testutils.reply_thread(self.thread, posted_on=posted_on)
+        post = test.reply_thread(self.thread, posted_on=posted_on)
 
         poststracker.make_read_aware(self.user, post)
         self.assertTrue(post.is_read)
@@ -56,7 +56,7 @@ class PostsTrackerTests(TestCase):
 
     def test_user_unread_post(self):
         """tracked post is marked as unread for authenticated users"""
-        post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+        post = test.reply_thread(self.thread, posted_on=timezone.now())
 
         poststracker.make_read_aware(self.user, post)
         self.assertFalse(post.is_read)
@@ -65,7 +65,7 @@ class PostsTrackerTests(TestCase):
     def test_user_created_after_post(self):
         """tracked post older than user is marked as read"""
         posted_on = timezone.now() - timedelta(days=1)
-        post = testutils.reply_thread(self.thread, posted_on=posted_on)
+        post = test.reply_thread(self.thread, posted_on=posted_on)
 
         poststracker.make_read_aware(self.user, post)
         self.assertTrue(post.is_read)
@@ -73,7 +73,7 @@ class PostsTrackerTests(TestCase):
 
     def test_user_read_post(self):
         """tracked post is marked as read for authenticated users with read entry"""
-        post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+        post = test.reply_thread(self.thread, posted_on=timezone.now())
 
         poststracker.save_read(self.user, post)
         poststracker.make_read_aware(self.user, post)

+ 21 - 21
misago/readtracker/tests/test_threadstracker.py

@@ -10,8 +10,8 @@ from misago.conf import settings
 from misago.conftest import get_cache_versions
 from misago.readtracker import poststracker, threadstracker
 from misago.readtracker.models import PostRead
-from misago.threads import testutils
-from misago.users.testutils import create_test_user
+from misago.threads import test
+from misago.users.test import create_test_user
 
 cache_versions = get_cache_versions()
 
@@ -38,7 +38,7 @@ class ThreadsTrackerTests(TestCase):
     def test_anon_thread_before_cutoff(self):
         """non-tracked thread is marked as read for anonymous users"""
         started_on = timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF)
-        thread = testutils.post_thread(self.category, started_on=started_on)
+        thread = test.post_thread(self.category, started_on=started_on)
 
         threadstracker.make_read_aware(AnonymousUser(), None, thread)
         self.assertTrue(thread.is_read)
@@ -46,7 +46,7 @@ class ThreadsTrackerTests(TestCase):
 
     def test_anon_thread_after_cutoff(self):
         """tracked thread is marked as read for anonymous users"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
 
         threadstracker.make_read_aware(AnonymousUser(), None, thread)
         self.assertTrue(thread.is_read)
@@ -55,7 +55,7 @@ class ThreadsTrackerTests(TestCase):
     def test_user_thread_before_cutoff(self):
         """non-tracked thread is marked as read for authenticated users"""
         started_on = timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF)
-        thread = testutils.post_thread(self.category, started_on=started_on)
+        thread = test.post_thread(self.category, started_on=started_on)
 
         threadstracker.make_read_aware(self.user, self.user_acl, thread)
         self.assertTrue(thread.is_read)
@@ -63,7 +63,7 @@ class ThreadsTrackerTests(TestCase):
 
     def test_user_unread_thread(self):
         """tracked thread is marked as unread for authenticated users"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
 
         threadstracker.make_read_aware(self.user, self.user_acl, thread)
         self.assertFalse(thread.is_read)
@@ -72,7 +72,7 @@ class ThreadsTrackerTests(TestCase):
     def test_user_created_after_thread(self):
         """tracked thread older than user is marked as read"""
         started_on = timezone.now() - timedelta(days=1)
-        thread = testutils.post_thread(self.category, started_on=started_on)
+        thread = test.post_thread(self.category, started_on=started_on)
 
         threadstracker.make_read_aware(self.user, self.user_acl, thread)
         self.assertTrue(thread.is_read)
@@ -80,7 +80,7 @@ class ThreadsTrackerTests(TestCase):
 
     def test_user_read_post(self):
         """tracked thread with read post marked as read"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
 
         poststracker.save_read(self.user, thread.first_post)
 
@@ -90,9 +90,9 @@ class ThreadsTrackerTests(TestCase):
 
     def test_user_first_unread_last_read_post(self):
         """tracked thread with unread first and last read post marked as unread"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
 
-        post = testutils.reply_thread(thread, posted_on=timezone.now())
+        post = test.reply_thread(thread, posted_on=timezone.now())
         poststracker.save_read(self.user, post)
 
         threadstracker.make_read_aware(self.user, self.user_acl, thread)
@@ -101,10 +101,10 @@ class ThreadsTrackerTests(TestCase):
 
     def test_user_first_read_post_unread_event(self):
         """tracked thread with read first post and unread event"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(thread, posted_on=timezone.now(), is_event=True)
+        test.reply_thread(thread, posted_on=timezone.now(), is_event=True)
 
         threadstracker.make_read_aware(self.user, self.user_acl, thread)
         self.assertFalse(thread.is_read)
@@ -112,9 +112,9 @@ class ThreadsTrackerTests(TestCase):
 
     def test_user_hidden_event(self):
         """tracked thread with unread first post and hidden event"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
 
-        testutils.reply_thread(
+        test.reply_thread(
             thread, posted_on=timezone.now(), is_event=True, is_hidden=True
         )
 
@@ -124,10 +124,10 @@ class ThreadsTrackerTests(TestCase):
 
     def test_user_first_read_post_hidden_event(self):
         """tracked thread with read first post and hidden event"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(
+        test.reply_thread(
             thread, posted_on=timezone.now(), is_event=True, is_hidden=True
         )
 
@@ -138,7 +138,7 @@ class ThreadsTrackerTests(TestCase):
     def test_user_thread_before_cutoff_unread_post(self):
         """non-tracked thread is marked as unread for anonymous users"""
         started_on = timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF)
-        thread = testutils.post_thread(self.category, started_on=started_on)
+        thread = test.post_thread(self.category, started_on=started_on)
 
         threadstracker.make_read_aware(self.user, self.user_acl, thread)
         self.assertTrue(thread.is_read)
@@ -146,10 +146,10 @@ class ThreadsTrackerTests(TestCase):
 
     def test_user_first_read_post_unapproved_post(self):
         """tracked thread with read first post and unapproved post"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(thread, posted_on=timezone.now(), is_unapproved=True)
+        test.reply_thread(thread, posted_on=timezone.now(), is_unapproved=True)
 
         threadstracker.make_read_aware(self.user, self.user_acl, thread)
         self.assertTrue(thread.is_read)
@@ -157,10 +157,10 @@ class ThreadsTrackerTests(TestCase):
 
     def test_user_first_read_post_unapproved_own_post(self):
         """tracked thread with read first post and unapproved own post"""
-        thread = testutils.post_thread(self.category, started_on=timezone.now())
+        thread = test.post_thread(self.category, started_on=timezone.now())
         poststracker.save_read(self.user, thread.first_post)
 
-        testutils.reply_thread(
+        test.reply_thread(
             thread, posted_on=timezone.now(), poster=self.user, is_unapproved=True
         )
 

+ 1 - 1
misago/search/tests/test_api.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
 from misago.search.searchproviders import searchproviders
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class SearchApiTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/search/tests/test_views.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
 from misago.threads.search import SearchThreads
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class LandingTests(AuthenticatedUserTestCase):

+ 218 - 0
misago/threads/test.py

@@ -1,5 +1,15 @@
+from datetime import timedelta
+
+from django.contrib.auth import get_user_model
+from django.utils import timezone
+
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
+from misago.core.utils import slugify
+from misago.users.test import create_test_user
+
+from .checksums import update_post_checksum
+from .models import Poll, Post, Thread
 
 default_category_acl = {
     "can_see": 1,
@@ -107,3 +117,211 @@ def cleanup_patched_acl(user_acl, category_acl, category):
 
     if category_acl["can_browse"] and category.id not in browseable_categories:
         browseable_categories.append(category.id)
+
+
+User = get_user_model()
+
+
+def post_thread(
+    category,
+    title="Test thread",
+    poster="Tester",
+    is_global=False,
+    is_pinned=False,
+    is_unapproved=False,
+    is_hidden=False,
+    is_closed=False,
+    started_on=None,
+):
+    started_on = started_on or timezone.now()
+
+    kwargs = {
+        "category": category,
+        "title": title,
+        "slug": slugify(title),
+        "started_on": started_on,
+        "last_post_on": started_on,
+        "is_unapproved": is_unapproved,
+        "is_hidden": is_hidden,
+        "is_closed": is_closed,
+    }
+
+    if is_global:
+        kwargs["weight"] = 2
+    elif is_pinned:
+        kwargs["weight"] = 1
+
+    try:
+        kwargs.update(
+            {
+                "starter": poster,
+                "starter_name": poster.username,
+                "starter_slug": poster.slug,
+                "last_poster": poster,
+                "last_poster_name": poster.username,
+                "last_poster_slug": poster.slug,
+            }
+        )
+    except AttributeError:
+        kwargs.update(
+            {
+                "starter_name": poster,
+                "starter_slug": slugify(poster),
+                "last_poster_name": poster,
+                "last_poster_slug": slugify(poster),
+            }
+        )
+
+    thread = Thread.objects.create(**kwargs)
+    reply_thread(
+        thread,
+        poster=poster,
+        posted_on=started_on,
+        is_hidden=is_hidden,
+        is_unapproved=is_unapproved,
+    )
+
+    return thread
+
+
+def reply_thread(
+    thread,
+    poster="Tester",
+    message="I am test message",
+    is_unapproved=False,
+    is_hidden=False,
+    is_event=False,
+    is_protected=False,
+    has_reports=False,
+    has_open_reports=False,
+    posted_on=None,
+):
+    posted_on = posted_on or thread.last_post_on + timedelta(minutes=5)
+
+    kwargs = {
+        "category": thread.category,
+        "thread": thread,
+        "original": message,
+        "parsed": message,
+        "checksum": "nope",
+        "posted_on": posted_on,
+        "updated_on": posted_on,
+        "is_event": is_event,
+        "is_unapproved": is_unapproved,
+        "is_hidden": is_hidden,
+        "is_protected": is_protected,
+        "has_reports": has_reports,
+        "has_open_reports": has_open_reports,
+    }
+
+    try:
+        kwargs.update({"poster": poster, "poster_name": poster.username})
+    except AttributeError:
+        kwargs.update({"poster_name": poster})
+
+    post = Post.objects.create(**kwargs)
+
+    update_post_checksum(post)
+    post.save()
+
+    thread.synchronize()
+    thread.save()
+    thread.category.synchronize()
+    thread.category.save()
+
+    return post
+
+
+def post_poll(thread, poster):
+    poll = Poll.objects.create(
+        category=thread.category,
+        thread=thread,
+        poster=poster,
+        poster_name=poster.username,
+        poster_slug=poster.slug,
+        question="Lorem ipsum dolor met?",
+        choices=[
+            {"hash": "aaaaaaaaaaaa", "label": "Alpha", "votes": 1},
+            {"hash": "bbbbbbbbbbbb", "label": "Beta", "votes": 0},
+            {"hash": "gggggggggggg", "label": "Gamma", "votes": 2},
+            {"hash": "dddddddddddd", "label": "Delta", "votes": 1},
+        ],
+        allowed_choices=2,
+        votes=4,
+    )
+
+    # one user voted for Alpha choice
+    try:
+        user = User.objects.get(slug="user")
+    except User.DoesNotExist:
+        user = create_test_user("User", "user@example.com")
+
+    poll.pollvote_set.create(
+        category=thread.category,
+        thread=thread,
+        voter=user,
+        voter_name=user.username,
+        voter_slug=user.slug,
+        choice_hash="aaaaaaaaaaaa",
+    )
+
+    # test user voted on third and last choices
+    poll.pollvote_set.create(
+        category=thread.category,
+        thread=thread,
+        voter=poster,
+        voter_name=poster.username,
+        voter_slug=poster.slug,
+        choice_hash="gggggggggggg",
+    )
+    poll.pollvote_set.create(
+        category=thread.category,
+        thread=thread,
+        voter=poster,
+        voter_name=poster.username,
+        voter_slug=poster.slug,
+        choice_hash="dddddddddddd",
+    )
+
+    # somebody else voted on third option before being deleted
+    poll.pollvote_set.create(
+        category=thread.category,
+        thread=thread,
+        voter_name="deleted",
+        voter_slug="deleted",
+        choice_hash="gggggggggggg",
+    )
+
+    return poll
+
+
+def like_post(post, liker=None, username=None):
+    if not post.last_likes:
+        post.last_likes = []
+
+    if liker:
+        like = post.postlike_set.create(
+            category=post.category,
+            thread=post.thread,
+            liker=liker,
+            liker_name=liker.username,
+            liker_slug=liker.slug,
+        )
+
+        post.last_likes = [
+            {"id": liker.id, "username": liker.username}
+        ] + post.last_likes
+    else:
+        like = post.postlike_set.create(
+            category=post.category,
+            thread=post.thread,
+            liker_name=username,
+            liker_slug=slugify(username),
+        )
+
+        post.last_likes = [{"id": None, "username": username}] + post.last_likes
+
+    post.likes += 1
+    post.save()
+
+    return like

+ 8 - 8
misago/threads/tests/test_anonymize_data.py

@@ -4,9 +4,9 @@ from django.urls import reverse
 from misago.cache.versions import get_cache_versions
 from misago.categories.models import Category
 from misago.conf.dynamicsettings import DynamicSettings
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.api.postendpoints.patch_post import patch_is_liked
 from misago.threads.models import Post
 from misago.threads.participants import (
@@ -16,7 +16,7 @@ from misago.threads.participants import (
     remove_participant,
     set_owner,
 )
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class AnonymizeEventsTests(AuthenticatedUserTestCase):
@@ -25,7 +25,7 @@ class AnonymizeEventsTests(AuthenticatedUserTestCase):
         self.factory = RequestFactory()
 
         category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category)
+        self.thread = test.post_thread(category)
 
     def get_request(self, user=None):
         request = self.factory.get("/customer/details")
@@ -204,8 +204,8 @@ class AnonymizeLikesTests(AuthenticatedUserTestCase):
     def test_anonymize_user_likes(self):
         """post's last like is anonymized by user.anonymize_data"""
         category = Category.objects.get(slug="first-category")
-        thread = testutils.post_thread(category)
-        post = testutils.reply_thread(thread)
+        thread = test.post_thread(category)
+        post = test.reply_thread(thread)
         post.acl = {"can_like": True}
 
         user = create_test_user("OtherUser", "otheruser@example.com")
@@ -240,10 +240,10 @@ class AnonymizePostsTests(AuthenticatedUserTestCase):
     def test_anonymize_user_posts(self):
         """post is anonymized by user.anonymize_data"""
         category = Category.objects.get(slug="first-category")
-        thread = testutils.post_thread(category)
+        thread = test.post_thread(category)
 
         user = create_test_user("OtherUser", "otheruser@example.com")
-        post = testutils.reply_thread(thread, poster=user)
+        post = test.reply_thread(thread, poster=user)
         user.anonymize_data()
 
         anonymized_post = Post.objects.get(pk=post.pk)

+ 3 - 3
misago/threads/tests/test_attachmentadmin_views.py

@@ -1,8 +1,8 @@
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Attachment, AttachmentType
 
 
@@ -11,7 +11,7 @@ class AttachmentAdminViewsTests(AdminTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.post = testutils.post_thread(category=self.category).first_post
+        self.post = test.post_thread(category=self.category).first_post
 
         self.filetype = AttachmentType.objects.order_by("id").first()
 

+ 1 - 1
misago/threads/tests/test_attachments_api.py

@@ -8,7 +8,7 @@ from misago.acl.models import Role
 from misago.acl.test import patch_user_acl
 from misago.conf import settings
 from misago.threads.models import Attachment, AttachmentType
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 TESTFILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "testfiles")
 TEST_DOCUMENT_PATH = os.path.join(TESTFILES_DIR, "document.pdf")

+ 4 - 4
misago/threads/tests/test_attachments_middleware.py

@@ -7,14 +7,14 @@ from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.conf import settings
 from misago.conftest import get_cache_versions
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.api.postingendpoint import PostingEndpoint
 from misago.threads.api.postingendpoint.attachments import (
     AttachmentsMiddleware,
     validate_attachments_count,
 )
 from misago.threads.models import Attachment, AttachmentType
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 cache_versions = get_cache_versions()
 
@@ -30,7 +30,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
         self.post = self.thread.first_post
 
         self.post.update_fields = []
@@ -237,7 +237,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
     @patch_attachments_acl()
     def test_steal_attachments(self):
         """middleware validates if attachments are already assigned to other posts"""
-        other_post = testutils.reply_thread(self.thread)
+        other_post = test.reply_thread(self.thread)
 
         attachments = [self.mock_attachment(post=other_post), self.mock_attachment()]
 

+ 1 - 1
misago/threads/tests/test_attachmenttypeadmin_views.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.acl.models import Role
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.threads.models import AttachmentType
 
 

+ 3 - 3
misago/threads/tests/test_attachmentview.py

@@ -6,9 +6,9 @@ from misago.acl.models import Role
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.conf import settings
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Attachment, AttachmentType
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 TESTFILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "testfiles")
 TEST_DOCUMENT_PATH = os.path.join(TESTFILES_DIR, "document.pdf")
@@ -29,7 +29,7 @@ class AttachmentViewTestCase(AuthenticatedUserTestCase):
         AttachmentType.objects.all().delete()
 
         self.category = Category.objects.get(slug="first-category")
-        self.post = testutils.post_thread(category=self.category).first_post
+        self.post = test.post_thread(category=self.category).first_post
 
         self.api_link = reverse("misago:api:attachment-list")
 

+ 2 - 2
misago/threads/tests/test_clearattachments.py

@@ -7,7 +7,7 @@ from django.utils import timezone
 
 from misago.categories.models import Category
 from misago.conf import settings
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.management.commands import clearattachments
 from misago.threads.models import Attachment, AttachmentType
 
@@ -46,7 +46,7 @@ class ClearAttachmentsTests(TestCase):
 
         # create 5 expired non-orphaned attachments
         category = Category.objects.get(slug="first-category")
-        post = testutils.post_thread(category).first_post
+        post = test.post_thread(category).first_post
 
         for _ in range(5):
             Attachment.objects.create(

+ 4 - 4
misago/threads/tests/test_delete_user_likes.py

@@ -1,9 +1,9 @@
 from django.test import RequestFactory
 
 from misago.categories.models import Category
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.api.postendpoints.patch_post import patch_is_liked
 from misago.threads.models import Post
 
@@ -23,8 +23,8 @@ class DeleteUserLikesTests(AuthenticatedUserTestCase):
     def test_anonymize_user_likes(self):
         """post's last like is anonymized by user.anonymize_data"""
         category = Category.objects.get(slug="first-category")
-        thread = testutils.post_thread(category)
-        post = testutils.reply_thread(thread)
+        thread = test.post_thread(category)
+        post = test.reply_thread(thread)
         post.acl = {"can_like": True}
 
         user = create_test_user("OtherUser", "otheruser@example.com")

+ 4 - 4
misago/threads/tests/test_emailnotification_middleware.py

@@ -7,9 +7,9 @@ from django.utils import timezone
 from django.utils.encoding import smart_str
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.test import patch_category_acl, patch_other_user_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class EmailNotificationTests(AuthenticatedUserTestCase):
@@ -17,7 +17,7 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(
+        self.thread = test.post_thread(
             category=self.category, started_on=timezone.now() - timedelta(seconds=5)
         )
 
@@ -119,7 +119,7 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             send_email=True,
         )
 
-        testutils.reply_thread(self.thread, posted_on=timezone.now())
+        test.reply_thread(self.thread, posted_on=timezone.now())
 
         response = self.client.post(
             self.api_link, data={"post": "This is test response!"}

+ 1 - 1
misago/threads/tests/test_events.py

@@ -9,7 +9,7 @@ from misago.categories.models import Category
 from misago.conftest import get_cache_versions
 from misago.threads.events import record_event
 from misago.threads.models import Thread
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 cache_versions = get_cache_versions()
 

+ 3 - 3
misago/threads/tests/test_floodprotection.py

@@ -2,8 +2,8 @@ from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
-from misago.threads import testutils
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.threads import test
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class FloodProtectionTests(AuthenticatedUserTestCase):
@@ -11,7 +11,7 @@ class FloodProtectionTests(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
 
         self.post_link = reverse(
             "misago:api:thread-post-list", kwargs={"thread_pk": self.thread.pk}

+ 1 - 1
misago/threads/tests/test_floodprotection_middleware.py

@@ -4,7 +4,7 @@ from django.utils import timezone
 
 from misago.threads.api.postingendpoint import PostingInterrupt
 from misago.threads.api.postingendpoint.floodprotection import FloodProtectionMiddleware
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 user_acl = {"can_omit_flood_protection": False}
 

+ 27 - 27
misago/threads/tests/test_gotoviews.py

@@ -3,9 +3,9 @@ from django.utils import timezone
 from misago.categories.models import Category
 from misago.conf import settings
 from misago.readtracker.poststracker import save_read
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 GOTO_URL = "%s#post-%s"
@@ -17,7 +17,7 @@ class GotoViewTestCase(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
 
 
 class GotoPostTests(GotoViewTestCase):
@@ -36,7 +36,7 @@ class GotoPostTests(GotoViewTestCase):
     def test_goto_last_post_on_page(self):
         """last post on page redirect url is valid"""
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
-            post = testutils.reply_thread(self.thread)
+            post = test.reply_thread(self.thread)
 
         response = self.client.get(post.get_absolute_url())
         self.assertEqual(response.status_code, 302)
@@ -50,7 +50,7 @@ class GotoPostTests(GotoViewTestCase):
     def test_goto_first_post_on_next_page(self):
         """first post on next page redirect url is valid"""
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
-            post = testutils.reply_thread(self.thread)
+            post = test.reply_thread(self.thread)
 
         response = self.client.get(post.get_absolute_url())
         self.assertEqual(response.status_code, 302)
@@ -66,7 +66,7 @@ class GotoPostTests(GotoViewTestCase):
         """first post on next page redirect url is valid"""
         posts = []
         for _ in range(settings.MISAGO_POSTS_PER_PAGE * 4 - 1):
-            post = testutils.reply_thread(self.thread)
+            post = test.reply_thread(self.thread)
             posts.append(post)
 
         post = posts[settings.MISAGO_POSTS_PER_PAGE * 2 - 3]
@@ -85,7 +85,7 @@ class GotoPostTests(GotoViewTestCase):
         """event redirect url is valid"""
         posts = []
         for _ in range(settings.MISAGO_POSTS_PER_PAGE * 4 - 1):
-            post = testutils.reply_thread(self.thread)
+            post = test.reply_thread(self.thread)
             posts.append(post)
 
         post = posts[settings.MISAGO_POSTS_PER_PAGE * 2 - 2]
@@ -123,7 +123,7 @@ class GotoLastTests(GotoViewTestCase):
     def test_goto_last_post_on_page(self):
         """last post on page redirect url is valid"""
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
-            post = testutils.reply_thread(self.thread)
+            post = test.reply_thread(self.thread)
 
         response = self.client.get(self.thread.get_last_post_url())
         self.assertEqual(response.status_code, 302)
@@ -149,9 +149,9 @@ class GotoNewTests(GotoViewTestCase):
         """first unread post redirect url in already read thread is valid"""
         save_read(self.user, self.thread.first_post)
 
-        post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+        post = test.reply_thread(self.thread, posted_on=timezone.now())
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
         response = self.client.get(self.thread.get_new_post_url())
         self.assertEqual(response.status_code, 302)
@@ -164,12 +164,12 @@ class GotoNewTests(GotoViewTestCase):
         save_read(self.user, self.thread.first_post)
 
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
-            last_post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+            last_post = test.reply_thread(self.thread, posted_on=timezone.now())
             save_read(self.user, last_post)
 
-        post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+        post = test.reply_thread(self.thread, posted_on=timezone.now())
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
         response = self.client.get(self.thread.get_new_post_url())
         self.assertEqual(response.status_code, 302)
@@ -183,7 +183,7 @@ class GotoNewTests(GotoViewTestCase):
         save_read(self.user, self.thread.first_post)
 
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
-            post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+            post = test.reply_thread(self.thread, posted_on=timezone.now())
             save_read(self.user, post)
 
         response = self.client.get(self.thread.get_new_post_url())
@@ -196,7 +196,7 @@ class GotoNewTests(GotoViewTestCase):
     def test_guest_goto_first_new_post_in_thread(self):
         """guest goto new in read thread points to last post"""
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
-            post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+            post = test.reply_thread(self.thread, posted_on=timezone.now())
 
         self.logout_user()
 
@@ -221,14 +221,14 @@ class GotoBestAnswerTests(GotoViewTestCase):
     def test_view_handles_best_answer(self):
         """if thread has best answer, redirect to it"""
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
-        best_answer = testutils.reply_thread(self.thread, posted_on=timezone.now())
+        best_answer = test.reply_thread(self.thread, posted_on=timezone.now())
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
         response = self.client.get(self.thread.get_best_answer_url())
         self.assertEqual(response.status_code, 302)
@@ -264,13 +264,13 @@ class GotoUnapprovedTests(GotoViewTestCase):
     def test_view_handles_unapproved_posts(self):
         """if thread has unapproved posts, redirect to first of them"""
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
-        post = testutils.reply_thread(
+        post = test.reply_thread(
             self.thread, is_unapproved=True, posted_on=timezone.now()
         )
         for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
         response = self.client.get(self.thread.get_unapproved_post_url())
         self.assertEqual(response.status_code, 302)
@@ -286,7 +286,7 @@ class ThreadGotoPostTests(GotoViewTestCase):
     def test_thread_growing_post_goto(self):
         """growing thread goto views don't fail"""
         for _ in range(60):
-            post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+            post = test.reply_thread(self.thread, posted_on=timezone.now())
 
             # go to post link is valid
             post_url = self.client.get(post.get_absolute_url())["location"]
@@ -301,9 +301,9 @@ class ThreadGotoPostTests(GotoViewTestCase):
     def test_thread_growing_event_goto(self):
         """growing thread goto views don't fail for events"""
         for i in range(60):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
-            post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+            post = test.reply_thread(self.thread, posted_on=timezone.now())
             post.is_event = True
             post.save()
 
@@ -325,7 +325,7 @@ class ThreadGotoPostTests(GotoViewTestCase):
     def test_thread_post_goto(self):
         """thread goto views don't fail"""
         for _ in range(60):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
         for post in self.thread.post_set.order_by("id").iterator():
             # go to post link is valid
@@ -341,9 +341,9 @@ class ThreadGotoPostTests(GotoViewTestCase):
     def test_thread_event_goto(self):
         """thread goto views don't fail for events"""
         for _ in range(60):
-            testutils.reply_thread(self.thread, posted_on=timezone.now())
+            test.reply_thread(self.thread, posted_on=timezone.now())
 
-            post = testutils.reply_thread(self.thread, posted_on=timezone.now())
+            post = test.reply_thread(self.thread, posted_on=timezone.now())
             post.is_event = True
             post.save()
 

+ 7 - 7
misago/threads/tests/test_mergeconflict.py

@@ -4,9 +4,9 @@ from django.test import TestCase
 
 from misago.categories.models import Category
 
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.mergeconflict import MergeConflict
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class MergeConflictTests(TestCase):
@@ -15,16 +15,16 @@ class MergeConflictTests(TestCase):
         self.user = create_test_user("User", "user@example.com")
 
     def create_plain_thread(self):
-        return testutils.post_thread(self.category)
+        return test.post_thread(self.category)
 
     def create_poll_thread(self):
-        thread = testutils.post_thread(self.category)
-        testutils.post_poll(thread, self.user)
+        thread = test.post_thread(self.category)
+        test.post_poll(thread, self.user)
         return thread
 
     def create_best_answer_thread(self):
-        thread = testutils.post_thread(self.category)
-        best_answer = testutils.reply_thread(thread)
+        thread = test.post_thread(self.category)
+        best_answer = test.reply_thread(thread)
         thread.set_best_answer(self.user, best_answer)
         thread.synchronize()
         thread.save()

+ 1 - 1
misago/threads/tests/test_participants.py

@@ -9,7 +9,7 @@ from misago.threads.participants import (
     set_owner,
     set_users_unread_private_threads_sync,
 )
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class ParticipantsTests(TestCase):

+ 3 - 3
misago/threads/tests/test_post_mentions.py

@@ -3,8 +3,8 @@ from django.urls import reverse
 
 from misago.categories.models import Category
 from misago.markup.mentions import MENTIONS_LIMIT
-from misago.threads import testutils
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.threads import test
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class PostMentionsTests(AuthenticatedUserTestCase):
@@ -12,7 +12,7 @@ class PostMentionsTests(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
 
         self.post_link = reverse(
             "misago:api:thread-post-list", kwargs={"thread_pk": self.thread.pk}

+ 1 - 1
misago/threads/tests/test_post_model.py

@@ -6,7 +6,7 @@ from django.utils import timezone
 from misago.categories.models import Category
 from misago.threads.checksums import update_post_checksum
 from misago.threads.models import Post, Thread
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class PostModelTests(TestCase):

+ 4 - 4
misago/threads/tests/test_posts_moderation.py

@@ -1,7 +1,7 @@
 from misago.categories.models import Category
-from misago.threads import moderation, testutils
+from misago.threads import moderation, test
 from misago.threads.models import Post, Thread
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class PostsModerationTests(AuthenticatedUserTestCase):
@@ -9,8 +9,8 @@ class PostsModerationTests(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.all_categories()[:1][0]
-        self.thread = testutils.post_thread(self.category)
-        self.post = testutils.reply_thread(self.thread)
+        self.thread = test.post_thread(self.category)
+        self.post = test.reply_thread(self.thread)
 
     def reload_thread(self):
         self.thread = Thread.objects.get(pk=self.thread.pk)

+ 3 - 3
misago/threads/tests/test_privatethread_patch_api.py

@@ -3,10 +3,10 @@ import json
 from django.core import mail
 
 from misago.acl.test import patch_user_acl
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.test import other_user_cant_use_private_threads
 from misago.threads.models import Thread, ThreadParticipant
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 from .test_privatethreads import PrivateThreadsTestCase
 
@@ -15,7 +15,7 @@ class PrivateThreadPatchApiTestCase(PrivateThreadsTestCase):
     def setUp(self):
         super().setUp()
 
-        self.thread = testutils.post_thread(self.category, poster=self.user)
+        self.thread = test.post_thread(self.category, poster=self.user)
         self.api_link = self.thread.get_api_url()
 
         self.other_user = create_test_user("OtherUser", "otheruser@example.com")

+ 3 - 3
misago/threads/tests/test_privatethread_reply_api.py

@@ -1,6 +1,6 @@
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import ThreadParticipant
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 from .test_privatethreads import PrivateThreadsTestCase
 
@@ -9,7 +9,7 @@ class PrivateThreadReplyApiTestCase(PrivateThreadsTestCase):
     def setUp(self):
         super().setUp()
 
-        self.thread = testutils.post_thread(self.category, poster=self.user)
+        self.thread = test.post_thread(self.category, poster=self.user)
         self.api_link = self.thread.get_posts_api_url()
 
         self.other_user = create_test_user("OtherUser", "otheruser@example.com")

+ 1 - 1
misago/threads/tests/test_privatethread_start_api.py

@@ -7,7 +7,7 @@ from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.threads.models import ThreadParticipant
 from misago.threads.test import other_user_cant_use_private_threads
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 User = get_user_model()
 

+ 2 - 2
misago/threads/tests/test_privatethread_view.py

@@ -1,5 +1,5 @@
 from misago.acl.test import patch_user_acl
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import ThreadParticipant
 
 from .test_privatethreads import PrivateThreadsTestCase
@@ -9,7 +9,7 @@ class PrivateThreadViewTests(PrivateThreadsTestCase):
     def setUp(self):
         super().setUp()
 
-        self.thread = testutils.post_thread(self.category, poster=self.user)
+        self.thread = test.post_thread(self.category, poster=self.user)
         self.test_link = self.thread.get_absolute_url()
 
     def test_anonymous(self):

+ 1 - 1
misago/threads/tests/test_privatethreads.py

@@ -1,5 +1,5 @@
 from misago.categories.models import Category
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class PrivateThreadsTestCase(AuthenticatedUserTestCase):

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

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread, ThreadParticipant
 from misago.threads.test import patch_private_threads_acl
 
@@ -43,11 +43,11 @@ class PrivateThreadsListApiTests(PrivateThreadsTestCase):
     @patch_user_acl({"can_use_private_threads": True})
     def test_thread_visibility(self):
         """only participated threads are returned by private threads api"""
-        visible = testutils.post_thread(category=self.category, poster=self.user)
-        reported = testutils.post_thread(category=self.category, poster=self.user)
+        visible = test.post_thread(category=self.category, poster=self.user)
+        reported = test.post_thread(category=self.category, poster=self.user)
 
         # hidden thread
-        testutils.post_thread(category=self.category, poster=self.user)
+        test.post_thread(category=self.category, poster=self.user)
 
         ThreadParticipant.objects.add_participants(visible, [self.user])
 
@@ -76,7 +76,7 @@ class PrivateThreadRetrieveApiTests(PrivateThreadsTestCase):
     def setUp(self):
         super().setUp()
 
-        self.thread = testutils.post_thread(self.category, poster=self.user)
+        self.thread = test.post_thread(self.category, poster=self.user)
         self.api_link = self.thread.get_api_url()
 
     def test_anonymous(self):
@@ -187,7 +187,7 @@ class PrivateThreadDeleteApiTests(PrivateThreadsTestCase):
     def setUp(self):
         super().setUp()
 
-        self.thread = testutils.post_thread(self.category, poster=self.user)
+        self.thread = test.post_thread(self.category, poster=self.user)
         self.api_link = self.thread.get_api_url()
 
         ThreadParticipant.objects.add_participants(self.thread, [self.user])

+ 4 - 4
misago/threads/tests/test_privatethreads_lists.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import ThreadParticipant
 
 from .test_privatethreads import PrivateThreadsTestCase
@@ -34,11 +34,11 @@ class PrivateThreadsListTests(PrivateThreadsTestCase):
 
     def test_thread_visibility(self):
         """only participated threads are returned by private threads view"""
-        visible = testutils.post_thread(category=self.category, poster=self.user)
-        reported = testutils.post_thread(category=self.category, poster=self.user)
+        visible = test.post_thread(category=self.category, poster=self.user)
+        reported = test.post_thread(category=self.category, poster=self.user)
 
         # post hidden thread
-        testutils.post_thread(category=self.category, poster=self.user)
+        test.post_thread(category=self.category, poster=self.user)
 
         ThreadParticipant.objects.add_participants(visible, [self.user])
 

+ 18 - 18
misago/threads/tests/test_search.py

@@ -1,8 +1,8 @@
 from django.urls import reverse
 
 from misago.categories.models import Category
-from misago.threads import testutils
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.threads import test
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class SearchApiTests(AuthenticatedUserTestCase):
@@ -49,8 +49,8 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
     def test_short_query(self):
         """api handles short search query"""
-        thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(thread, message="Lorem ipsum dolor.")
+        thread = test.post_thread(self.category)
+        post = test.reply_thread(thread, message="Lorem ipsum dolor.")
         self.index_post(post)
 
         response = self.client.get("%s?q=ip" % self.api_link)
@@ -65,8 +65,8 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
     def test_wrong_query(self):
         """api handles query miss"""
-        thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(thread, message="Lorem ipsum dolor.")
+        thread = test.post_thread(self.category)
+        post = test.reply_thread(thread, message="Lorem ipsum dolor.")
         self.index_post(post)
 
         response = self.client.get("%s?q=elit" % self.api_link)
@@ -81,8 +81,8 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
     def test_hidden_post(self):
         """hidden posts are extempt from search"""
-        thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(
+        thread = test.post_thread(self.category)
+        post = test.reply_thread(
             thread, message="Lorem ipsum dolor.", is_hidden=True
         )
         self.index_post(post)
@@ -99,8 +99,8 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
     def test_unapproved_post(self):
         """unapproves posts are extempt from search"""
-        thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(
+        thread = test.post_thread(self.category)
+        post = test.reply_thread(
             thread, message="Lorem ipsum dolor.", is_unapproved=True
         )
         self.index_post(post)
@@ -117,8 +117,8 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
     def test_query(self):
         """api handles search query"""
-        thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(thread, message="Lorem ipsum dolor.")
+        thread = test.post_thread(self.category)
+        post = test.reply_thread(thread, message="Lorem ipsum dolor.")
         self.index_post(post)
 
         response = self.client.get("%s?q=ipsum" % self.api_link)
@@ -135,10 +135,10 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
     def test_thread_title_search(self):
         """api searches threads by title"""
-        thread = testutils.post_thread(self.category, title="Atmosphere of mars")
+        thread = test.post_thread(self.category, title="Atmosphere of mars")
         self.index_post(thread.first_post)
 
-        post = testutils.reply_thread(thread, message="Lorem ipsum dolor.")
+        post = test.reply_thread(thread, message="Lorem ipsum dolor.")
         self.index_post(post)
 
         response = self.client.get("%s?q=mars atmosphere" % self.api_link)
@@ -155,8 +155,8 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
     def test_complex_query(self):
         """api handles complex query that uses fulltext search facilities"""
-        thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(thread, message="Atmosphere of Mars")
+        thread = test.post_thread(self.category)
+        post = test.reply_thread(thread, message="Atmosphere of Mars")
         self.index_post(post)
 
         response = self.client.get("%s?q=Mars atmosphere" % self.api_link)
@@ -173,8 +173,8 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
     def test_filtered_query(self):
         """search filters are used by search system"""
-        thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(
+        thread = test.post_thread(self.category)
+        post = test.reply_thread(
             thread, message="You just do MMM in 4th minute and its pwnt"
         )
 

+ 4 - 4
misago/threads/tests/test_subscription_middleware.py

@@ -3,9 +3,9 @@ from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 User = get_user_model()
@@ -92,7 +92,7 @@ class SubscribeStartedThreadTests(SubscriptionMiddlewareTestCase):
 class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
     def setUp(self):
         super().setUp()
-        self.thread = testutils.post_thread(self.category)
+        self.thread = test.post_thread(self.category)
         self.api_link = reverse(
             "misago:api:thread-post-list", kwargs={"thread_pk": self.thread.pk}
         )
@@ -153,7 +153,7 @@ class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
         self.user.save()
 
         # set event in thread
-        testutils.reply_thread(self.thread, self.user, is_event=True)
+        test.reply_thread(self.thread, self.user, is_event=True)
 
         # reply thread
         response = self.client.post(

+ 3 - 3
misago/threads/tests/test_subscriptions.py

@@ -4,10 +4,10 @@ from django.test import TestCase
 from django.utils import timezone
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.subscriptions import make_subscription_aware
 from misago.users.models import AnonymousUser
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class SubscriptionsTests(TestCase):
@@ -19,7 +19,7 @@ class SubscriptionsTests(TestCase):
         self.user = create_test_user("User", "user@example.com")
 
     def post_thread(self, datetime):
-        return testutils.post_thread(category=self.category, started_on=datetime)
+        return test.post_thread(category=self.category, started_on=datetime)
 
     def test_anon_subscription(self):
         """make single thread sub aware for anon"""

+ 4 - 4
misago/threads/tests/test_sync_unread_private_threads.py

@@ -1,6 +1,6 @@
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import ThreadParticipant
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 from .test_privatethreads import PrivateThreadsTestCase
 
@@ -10,7 +10,7 @@ class SyncUnreadPrivateThreadsTestCase(PrivateThreadsTestCase):
         super().setUp()
 
         self.other_user = create_test_user("OtherUser", "user@example.com")
-        self.thread = testutils.post_thread(self.category, poster=self.user)
+        self.thread = test.post_thread(self.category, poster=self.user)
 
         ThreadParticipant.objects.set_owner(self.thread, self.other_user)
         ThreadParticipant.objects.add_participants(self.thread, [self.user])
@@ -42,7 +42,7 @@ class SyncUnreadPrivateThreadsTestCase(PrivateThreadsTestCase):
         self.assertEqual(self.user.unread_private_threads, 0)
 
         # reply to thread
-        testutils.reply_thread(self.thread)
+        test.reply_thread(self.thread)
 
         self.user.sync_unread_private_threads = True
         self.user.save()

+ 3 - 3
misago/threads/tests/test_synchronizethreads.py

@@ -4,7 +4,7 @@ from django.core.management import call_command
 from django.test import TestCase
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.management.commands import synchronizethreads
 
 
@@ -23,9 +23,9 @@ class SynchronizeThreadsTests(TestCase):
         """command synchronizes threads"""
         category = Category.objects.all_categories()[:1][0]
 
-        threads = [testutils.post_thread(category) for _ in range(10)]
+        threads = [test.post_thread(category) for _ in range(10)]
         for i, thread in enumerate(threads):
-            [testutils.reply_thread(thread) for _ in range(i)]
+            [test.reply_thread(thread) for _ in range(i)]
             thread.replies = 0
             thread.save()
 

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

@@ -3,7 +3,7 @@ import json
 from django.urls import reverse
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread
 from misago.threads.test import patch_category_acl, patch_other_category_acl
 
@@ -17,9 +17,9 @@ class ThreadsBulkPatchApiTestCase(ThreadsApiTestCase):
         self.threads = list(
             reversed(
                 [
-                    testutils.post_thread(category=self.category),
-                    testutils.post_thread(category=self.category),
-                    testutils.post_thread(category=self.category),
+                    test.post_thread(category=self.category),
+                    test.post_thread(category=self.category),
+                    test.post_thread(category=self.category),
                 ]
             )
         )
@@ -114,8 +114,8 @@ class BulkPatchSerializerTests(ThreadsBulkPatchApiTestCase):
     def test_threads_not_found(self):
         """api fails to find threads"""
         threads = [
-            testutils.post_thread(category=self.category, is_hidden=True),
-            testutils.post_thread(category=self.category, is_unapproved=True),
+            test.post_thread(category=self.category, is_hidden=True),
+            test.post_thread(category=self.category, is_unapproved=True),
         ]
 
         response = self.patch(

+ 4 - 4
misago/threads/tests/test_thread_editreply_api.py

@@ -6,10 +6,10 @@ from django.utils import timezone
 
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Post, Thread
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class EditReplyTests(AuthenticatedUserTestCase):
@@ -17,8 +17,8 @@ class EditReplyTests(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
-        self.post = testutils.reply_thread(self.thread, poster=self.user)
+        self.thread = test.post_thread(category=self.category)
+        self.post = test.reply_thread(self.thread, poster=self.user)
 
         self.api_link = reverse(
             "misago:api:thread-post-detail",

+ 52 - 52
misago/threads/tests/test_thread_merge_api.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 
 from misago.categories.models import Category
 from misago.readtracker import poststracker
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Poll, PollVote, Thread
 from misago.threads.test import patch_category_acl, patch_other_category_acl
 
@@ -64,7 +64,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_exists(self):
         """api validates if other thread exists"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
         other_other_thread = other_thread.get_absolute_url()
         other_thread.delete()
 
@@ -84,7 +84,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_is_invisible(self):
         """api validates if other thread is visible"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         response = self.client.post(
             self.api_link, {"other_thread": other_thread.get_absolute_url()}
@@ -104,7 +104,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_isnt_mergeable(self):
         """api validates if other thread can be merged"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         response = self.client.post(
             self.api_link, {"other_thread": other_thread.get_absolute_url()}
@@ -118,7 +118,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_thread_category_is_closed(self):
         """api validates if thread's category is open"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         self.category.is_closed = True
         self.category.save()
@@ -136,7 +136,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_thread_is_closed(self):
         """api validates if thread is open"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         self.thread.is_closed = True
         self.thread.save()
@@ -154,7 +154,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_category_is_closed(self):
         """api validates if other thread's category is open"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         self.other_category.is_closed = True
         self.other_category.save()
@@ -172,7 +172,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_is_closed(self):
         """api validates if other thread is open"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         other_thread.is_closed = True
         other_thread.save()
@@ -190,7 +190,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_isnt_replyable(self):
         """api validates if other thread can be replied, which is condition for merge"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         response = self.client.post(
             self.api_link, {"other_thread": other_thread.get_absolute_url()}
@@ -205,7 +205,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads(self):
         """api merges two threads successfully"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         response = self.client.post(
             self.api_link, {"other_thread": other_thread.get_absolute_url()}
@@ -231,7 +231,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_reads(self):
         """api keeps both threads readtrackers after merge"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         poststracker.save_read(self.user, self.thread.first_post)
         poststracker.save_read(self.user, other_thread.first_post)
@@ -263,7 +263,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_subs(self):
         """api keeps other thread's subscription after merge"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         self.user.subscription_set.create(
             thread=self.thread,
@@ -298,7 +298,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_moved_subs(self):
         """api keeps other thread's subscription after merge"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         self.user.subscription_set.create(
             thread=other_thread,
@@ -340,7 +340,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             send_email=False,
         )
 
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         self.user.subscription_set.create(
             thread=other_thread,
@@ -377,8 +377,8 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_best_answer(self):
         """api merges two threads successfully, keeping best answer from old thread"""
-        other_thread = testutils.post_thread(self.other_category)
-        best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.other_category)
+        best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, best_answer)
         other_thread.save()
 
@@ -410,9 +410,9 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_moved_best_answer(self):
         """api merges two threads successfully, moving best answer to old thread"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
@@ -444,12 +444,12 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_merge_conflict_best_answer(self):
         """api errors on merge conflict, returning list of available best answers"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.other_category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.other_category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -482,12 +482,12 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_best_answer_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.other_category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.other_category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -515,12 +515,12 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_unmark_all_best_answers(self):
         """api unmarks all best answers when unmark all choice is selected"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.other_category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.other_category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -552,12 +552,12 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_first_best_answer(self):
         """api unmarks other best answer on merge"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.other_category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.other_category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -594,12 +594,12 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_other_best_answer(self):
         """api unmarks first best answer on merge"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.other_category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.other_category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -636,8 +636,8 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_poll(self):
         """api merges two threads successfully, keeping poll from other thread"""
-        other_thread = testutils.post_thread(self.other_category)
-        poll = testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.other_category)
+        poll = test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link, {"other_thread": other_thread.get_absolute_url()}
@@ -671,8 +671,8 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_moved_poll(self):
         """api merges two threads successfully, moving poll from old thread"""
-        other_thread = testutils.post_thread(self.other_category)
-        poll = testutils.post_poll(self.thread, self.user)
+        other_thread = test.post_thread(self.other_category)
+        poll = test.post_poll(self.thread, self.user)
 
         response = self.client.post(
             self.api_link, {"other_thread": other_thread.get_absolute_url()}
@@ -706,9 +706,9 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_polls(self):
         """api errors on merge conflict, returning list of available polls"""
-        other_thread = testutils.post_thread(self.other_category)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.other_category)
+        poll = test.post_poll(self.thread, self.user)
+        other_poll = test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link, {"other_thread": other_thread.get_absolute_url()}
@@ -736,10 +736,10 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_poll_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
-        testutils.post_poll(self.thread, self.user)
-        testutils.post_poll(other_thread, self.user)
+        test.post_poll(self.thread, self.user)
+        test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,
@@ -759,9 +759,9 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_delete_all_polls(self):
         """api deletes all polls when delete all choice is selected"""
-        other_thread = testutils.post_thread(self.other_category)
-        testutils.post_poll(self.thread, self.user)
-        testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.other_category)
+        test.post_poll(self.thread, self.user)
+        test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link, {"other_thread": other_thread.get_absolute_url(), "poll": 0}
@@ -791,9 +791,9 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_first_poll(self):
         """api deletes other poll on merge"""
-        other_thread = testutils.post_thread(self.other_category)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.other_category)
+        poll = test.post_poll(self.thread, self.user)
+        other_poll = test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,
@@ -831,9 +831,9 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_other_poll(self):
         """api deletes first poll on merge"""
-        other_thread = testutils.post_thread(self.other_category)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.other_category)
+        poll = test.post_poll(self.thread, self.user)
+        other_poll = test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,

+ 5 - 5
misago/threads/tests/test_thread_model.py

@@ -4,9 +4,9 @@ from django.test import TestCase
 from django.utils import timezone
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Poll, Post, Thread, ThreadParticipant
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class ThreadModelTests(TestCase):
@@ -317,7 +317,7 @@ class ThreadModelTests(TestCase):
         """set_best_answer implements some assertions for data integrity"""
         user = create_test_user("User", "user@example.com")
 
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
         with self.assertRaises(ValueError):
             self.thread.set_best_answer(user, other_thread.first_post)
 
@@ -325,11 +325,11 @@ class ThreadModelTests(TestCase):
             self.thread.set_best_answer(user, self.thread.first_post)
 
         with self.assertRaises(ValueError):
-            reply = testutils.reply_thread(self.thread, is_hidden=True)
+            reply = test.reply_thread(self.thread, is_hidden=True)
             self.thread.set_best_answer(user, reply)
 
         with self.assertRaises(ValueError):
-            reply = testutils.reply_thread(self.thread, is_unapproved=True)
+            reply = test.reply_thread(self.thread, is_unapproved=True)
             self.thread.set_best_answer(user, reply)
 
     def test_move(self):

+ 28 - 28
misago/threads/tests/test_thread_patch_api.py

@@ -5,7 +5,7 @@ from django.utils import timezone
 
 from misago.categories.models import Category
 from misago.readtracker import poststracker
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.test import patch_category_acl, patch_other_category_acl
 from misago.threads.models import Thread
 
@@ -983,7 +983,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2})
     def test_mark_best_answer(self):
         """api makes it possible to mark best answer"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,
@@ -1020,7 +1020,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
         """api validates that user is authenticated before marking best answer"""
         self.logout_user()
 
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,
@@ -1037,7 +1037,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 0})
     def test_mark_best_answer_no_permission(self):
         """api validates permission to mark best answers"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,
@@ -1060,7 +1060,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 1})
     def test_mark_best_answer_not_thread_starter(self):
         """api validates permission to mark best answers in owned thread"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,
@@ -1094,7 +1094,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @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"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         self.category.is_closed = True
         self.category.save()
@@ -1121,7 +1121,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @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)
+        best_answer = test.reply_thread(self.thread)
 
         self.category.is_closed = True
         self.category.save()
@@ -1135,7 +1135,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @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"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         self.thread.is_closed = True
         self.thread.save()
@@ -1162,7 +1162,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @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)
+        best_answer = test.reply_thread(self.thread)
 
         self.thread.is_closed = True
         self.thread.save()
@@ -1213,7 +1213,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2})
     def test_mark_best_answer_post_invisible(self):
         """api validates post visibility to action author"""
-        unapproved_post = testutils.reply_thread(self.thread, is_unapproved=True)
+        unapproved_post = test.reply_thread(self.thread, is_unapproved=True)
 
         response = self.patch(
             self.api_link,
@@ -1230,7 +1230,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2})
     def test_mark_best_answer_post_other_thread(self):
         """api validates post belongs to same thread"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.patch(
             self.api_link,
@@ -1253,7 +1253,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2})
     def test_mark_best_answer_event_id(self):
         """api validates that post is not an event"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         best_answer.is_event = True
         best_answer.save()
 
@@ -1301,7 +1301,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2})
     def test_mark_best_answer_hidden_post(self):
         """api validates that post is not hidden"""
-        best_answer = testutils.reply_thread(self.thread, is_hidden=True)
+        best_answer = test.reply_thread(self.thread, is_hidden=True)
 
         response = self.patch(
             self.api_link,
@@ -1322,7 +1322,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2})
     def test_mark_best_answer_unapproved_post(self):
         """api validates that post is not unapproved"""
-        best_answer = testutils.reply_thread(
+        best_answer = test.reply_thread(
             self.thread, poster=self.user, is_unapproved=True
         )
 
@@ -1345,7 +1345,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @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"""
-        best_answer = testutils.reply_thread(self.thread, is_protected=True)
+        best_answer = test.reply_thread(self.thread, is_protected=True)
 
         response = self.patch(
             self.api_link,
@@ -1369,7 +1369,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @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)
+        best_answer = test.reply_thread(self.thread, is_protected=True)
 
         response = self.patch(
             self.api_link,
@@ -1382,14 +1382,14 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.best_answer = testutils.reply_thread(self.thread)
+        self.best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, self.best_answer)
         self.thread.save()
 
     @patch_category_acl({"can_mark_best_answers": 2, "can_change_marked_answers": 2})
     def test_change_best_answer(self):
         """api makes it possible to change best answer"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,
@@ -1443,7 +1443,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 0, "can_change_marked_answers": 2})
     def test_change_best_answer_no_permission_to_mark(self):
         """api validates permission to mark best answers before allowing answer change"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,
@@ -1466,7 +1466,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2, "can_change_marked_answers": 0})
     def test_change_best_answer_no_permission(self):
         """api validates permission to change best answers"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,
@@ -1490,7 +1490,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2, "can_change_marked_answers": 1})
     def test_change_best_answer_not_starter(self):
         """api validates permission to change best answers"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,
@@ -1530,7 +1530,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     )
     def test_change_best_answer_timelimit_out_of_time(self):
         """api validates permission for starter to change best answers within timelimit"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         self.thread.best_answer_marked_on = timezone.now() - timedelta(minutes=6)
         self.thread.starter = self.user
@@ -1564,7 +1564,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     )
     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)
+        best_answer = test.reply_thread(self.thread)
 
         self.thread.best_answer_marked_on = timezone.now() - timedelta(minutes=1)
         self.thread.starter = self.user
@@ -1585,7 +1585,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     )
     def test_change_best_answer_protected_no_permission(self):
         """api validates permission to change protected best answers"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         self.thread.best_answer_is_protected = True
         self.thread.save()
@@ -1618,7 +1618,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     )
     def test_change_best_answer_protected(self):
         """api validates permission to change protected best answers"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         self.thread.best_answer_is_protected = True
         self.thread.save()
@@ -1632,7 +1632,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 2, "can_change_marked_answers": 2})
     def test_change_best_answer_post_validation(self):
         """api validates new post'"""
-        best_answer = testutils.reply_thread(self.thread, is_hidden=True)
+        best_answer = test.reply_thread(self.thread, is_hidden=True)
 
         response = self.patch(
             self.api_link,
@@ -1648,7 +1648,7 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.best_answer = testutils.reply_thread(self.thread)
+        self.best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, self.best_answer)
         self.thread.save()
 
@@ -1716,7 +1716,7 @@ class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
     @patch_category_acl({"can_mark_best_answers": 0, "can_change_marked_answers": 2})
     def test_unmark_best_answer_wrong_post(self):
         """api validates if post given to unmark is best answer"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         response = self.patch(
             self.api_link,

+ 4 - 4
misago/threads/tests/test_thread_poll_api.py

@@ -3,8 +3,8 @@ import json
 from django.urls import reverse
 
 from misago.categories.models import Category
-from misago.threads import testutils
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.threads import test
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ThreadPollApiTestCase(AuthenticatedUserTestCase):
@@ -12,7 +12,7 @@ class ThreadPollApiTestCase(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(self.category, poster=self.user)
+        self.thread = test.post_thread(self.category, poster=self.user)
 
         self.api_link = reverse(
             "misago:api:thread-poll-list", kwargs={"thread_pk": self.thread.pk}
@@ -29,7 +29,7 @@ class ThreadPollApiTestCase(AuthenticatedUserTestCase):
         )
 
     def mock_poll(self):
-        self.poll = self.thread.poll = testutils.post_poll(self.thread, self.user)
+        self.poll = self.thread.poll = test.post_poll(self.thread, self.user)
 
         self.api_link = reverse(
             "misago:api:thread-poll-detail",

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

@@ -4,7 +4,7 @@ from datetime import timedelta
 from django.urls import reverse
 from django.utils import timezone
 
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Post, Thread
 from misago.threads.test import patch_category_acl
 
@@ -16,9 +16,9 @@ class PostBulkDeleteApiTests(ThreadsApiTestCase):
         super().setUp()
 
         self.posts = [
-            testutils.reply_thread(self.thread, poster=self.user),
-            testutils.reply_thread(self.thread),
-            testutils.reply_thread(self.thread, poster=self.user),
+            test.reply_thread(self.thread, poster=self.user),
+            test.reply_thread(self.thread),
+            test.reply_thread(self.thread, poster=self.user),
         ]
 
         self.api_link = reverse(
@@ -123,8 +123,8 @@ class PostBulkDeleteApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_hide_posts": 2})
     def test_validate_posts_same_thread(self):
         """api validates that ids are same thread posts"""
-        other_thread = testutils.post_thread(category=self.category)
-        self.posts.append(testutils.reply_thread(other_thread, poster=self.user))
+        other_thread = test.post_thread(category=self.category)
+        self.posts.append(test.reply_thread(other_thread, poster=self.user))
 
         response = self.delete(self.api_link, [p.id for p in self.posts])
         self.assertEqual(response.status_code, 403)

+ 8 - 8
misago/threads/tests/test_thread_postbulkpatch_api.py

@@ -5,10 +5,10 @@ from django.urls import reverse
 from django.utils import timezone
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Post, Thread
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ThreadPostBulkPatchApiTestCase(AuthenticatedUserTestCase):
@@ -16,11 +16,11 @@ class ThreadPostBulkPatchApiTestCase(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
         self.posts = [
-            testutils.reply_thread(self.thread, poster=self.user),
-            testutils.reply_thread(self.thread),
-            testutils.reply_thread(self.thread, poster=self.user),
+            test.reply_thread(self.thread, poster=self.user),
+            test.reply_thread(self.thread),
+            test.reply_thread(self.thread, poster=self.user),
         ]
 
         self.ids = [p.id for p in self.posts]
@@ -115,8 +115,8 @@ class BulkPatchSerializerTests(ThreadPostBulkPatchApiTestCase):
     def test_posts_not_found(self):
         """api fails to find posts"""
         posts = [
-            testutils.reply_thread(self.thread, is_hidden=True),
-            testutils.reply_thread(self.thread, is_unapproved=True),
+            test.reply_thread(self.thread, is_hidden=True),
+            test.reply_thread(self.thread, is_unapproved=True),
         ]
 
         response = self.patch(

+ 3 - 3
misago/threads/tests/test_thread_postdelete_api.py

@@ -3,7 +3,7 @@ from datetime import timedelta
 from django.urls import reverse
 from django.utils import timezone
 
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Post, Thread
 from misago.threads.test import patch_category_acl
 
@@ -14,7 +14,7 @@ class PostDeleteApiTests(ThreadsApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.post = testutils.reply_thread(self.thread, poster=self.user)
+        self.post = test.reply_thread(self.thread, poster=self.user)
 
         self.api_link = reverse(
             "misago:api:thread-post-detail",
@@ -182,7 +182,7 @@ class EventDeleteApiTests(ThreadsApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.event = testutils.reply_thread(
+        self.event = test.reply_thread(
             self.thread, poster=self.user, is_event=True
         )
 

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

@@ -1,6 +1,6 @@
 from django.urls import reverse
 
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.test import patch_category_acl
 
 from .test_threads_api import ThreadsApiTestCase
@@ -10,7 +10,7 @@ class ThreadPostEditsApiTestCase(ThreadsApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.post = testutils.reply_thread(self.thread, poster=self.user)
+        self.post = test.reply_thread(self.thread, poster=self.user)
 
         self.api_link = reverse(
             "misago:api:thread-post-edits",

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

@@ -1,6 +1,6 @@
 from django.urls import reverse
 
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.test import patch_category_acl
 from misago.threads.serializers import PostLikeSerializer
 
@@ -11,7 +11,7 @@ class ThreadPostLikesApiTestCase(ThreadsApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.post = testutils.reply_thread(self.thread, poster=self.user)
+        self.post = test.reply_thread(self.thread, poster=self.user)
 
         self.api_link = reverse(
             "misago:api:thread-post-likes",
@@ -46,8 +46,8 @@ class ThreadPostLikesApiTestCase(ThreadsApiTestCase):
     @patch_category_acl({"can_see_posts_likes": 2})
     def test_likes(self):
         """api returns list of likes"""
-        like = testutils.like_post(self.post, self.user)
-        other_like = testutils.like_post(self.post, self.user)
+        like = test.like_post(self.post, self.user)
+        other_like = test.like_post(self.post, self.user)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)

+ 42 - 42
misago/threads/tests/test_thread_postmerge_api.py

@@ -4,11 +4,11 @@ from django.urls import reverse
 
 from misago.categories.models import Category
 from misago.readtracker import poststracker
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Post, Thread
 from misago.threads.serializers.moderation import POSTS_LIMIT
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
@@ -16,8 +16,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
-        self.post = testutils.reply_thread(self.thread, poster=self.user)
+        self.thread = test.post_thread(category=self.category)
+        self.post = test.reply_thread(self.thread, poster=self.user)
 
         self.api_link = reverse(
             "misago:api:thread-post-merge", kwargs={"thread_pk": self.thread.pk}
@@ -177,7 +177,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_event(self):
         """api recjects events"""
-        event = testutils.reply_thread(self.thread, is_event=True, poster=self.user)
+        event = test.reply_thread(self.thread, is_event=True, poster=self.user)
 
         response = self.client.post(
             self.api_link,
@@ -204,8 +204,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_cross_threads(self):
         """api recjects attempt to merge with post made in other thread"""
-        other_thread = testutils.post_thread(category=self.category)
-        other_post = testutils.reply_thread(other_thread, poster=self.user)
+        other_thread = test.post_thread(category=self.category)
+        other_post = test.reply_thread(other_thread, poster=self.user)
 
         response = self.client.post(
             self.api_link,
@@ -221,7 +221,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_authenticated_with_guest_post(self):
         """api recjects attempt to merge with post made by deleted user"""
-        other_post = testutils.reply_thread(self.thread)
+        other_post = test.reply_thread(self.thread)
 
         response = self.client.post(
             self.api_link,
@@ -237,7 +237,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_guest_with_authenticated_post(self):
         """api recjects attempt to merge with post made by deleted user"""
-        other_post = testutils.reply_thread(self.thread)
+        other_post = test.reply_thread(self.thread)
 
         response = self.client.post(
             self.api_link,
@@ -258,8 +258,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "posts": [
-                        testutils.reply_thread(self.thread, poster="Bob").pk,
-                        testutils.reply_thread(self.thread, poster="Miku").pk,
+                        test.reply_thread(self.thread, poster="Bob").pk,
+                        test.reply_thread(self.thread, poster="Miku").pk,
                     ]
                 }
             ),
@@ -279,10 +279,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "posts": [
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster=self.user, is_hidden=True
                         ).pk,
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster=self.user, is_hidden=False
                         ).pk,
                     ]
@@ -304,10 +304,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "posts": [
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster=self.user, is_unapproved=True
                         ).pk,
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster=self.user, is_unapproved=False
                         ).pk,
                     ]
@@ -328,8 +328,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         self.thread.save()
 
         posts = [
-            testutils.reply_thread(self.thread, poster=self.user).pk,
-            testutils.reply_thread(self.thread, poster=self.user).pk,
+            test.reply_thread(self.thread, poster=self.user).pk,
+            test.reply_thread(self.thread, poster=self.user).pk,
         ]
 
         response = self.client.post(
@@ -348,8 +348,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         self.thread.save()
 
         posts = [
-            testutils.reply_thread(self.thread, poster=self.user).pk,
-            testutils.reply_thread(self.thread, poster=self.user).pk,
+            test.reply_thread(self.thread, poster=self.user).pk,
+            test.reply_thread(self.thread, poster=self.user).pk,
         ]
 
         response = self.client.post(
@@ -364,8 +364,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         self.category.save()
 
         posts = [
-            testutils.reply_thread(self.thread, poster=self.user).pk,
-            testutils.reply_thread(self.thread, poster=self.user).pk,
+            test.reply_thread(self.thread, poster=self.user).pk,
+            test.reply_thread(self.thread, poster=self.user).pk,
         ]
 
         response = self.client.post(
@@ -384,8 +384,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         self.category.save()
 
         posts = [
-            testutils.reply_thread(self.thread, poster=self.user).pk,
-            testutils.reply_thread(self.thread, poster=self.user).pk,
+            test.reply_thread(self.thread, poster=self.user).pk,
+            test.reply_thread(self.thread, poster=self.user).pk,
         ]
 
         response = self.client.post(
@@ -421,10 +421,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_posts(self):
         """api merges two posts"""
-        post_a = testutils.reply_thread(
+        post_a = test.reply_thread(
             self.thread, poster=self.user, message="Battęry"
         )
-        post_b = testutils.reply_thread(self.thread, poster=self.user, message="Hórse")
+        post_b = test.reply_thread(self.thread, poster=self.user, message="Hórse")
 
         thread_replies = self.thread.replies
 
@@ -452,8 +452,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "posts": [
-                        testutils.reply_thread(self.thread, poster="Bob").pk,
-                        testutils.reply_thread(self.thread, poster="Bob").pk,
+                        test.reply_thread(self.thread, poster="Bob").pk,
+                        test.reply_thread(self.thread, poster="Bob").pk,
                     ]
                 }
             ),
@@ -469,10 +469,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "posts": [
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster=self.user, is_hidden=True
                         ).pk,
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster=self.user, is_hidden=True
                         ).pk,
                     ]
@@ -490,10 +490,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "posts": [
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster=self.user, is_unapproved=True
                         ).pk,
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster=self.user, is_unapproved=True
                         ).pk,
                     ]
@@ -510,7 +510,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         self.thread.first_post.poster = self.user
         self.thread.first_post.save()
 
-        post_visible = testutils.reply_thread(
+        post_visible = test.reply_thread(
             self.thread, poster=self.user, is_hidden=False
         )
 
@@ -529,10 +529,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "posts": [
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster="Bob", is_protected=True
                         ).pk,
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster="Bob", is_protected=False
                         ).pk,
                     ]
@@ -548,7 +548,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_best_answer(self):
         """api merges best answer with other post"""
-        best_answer = testutils.reply_thread(self.thread, poster="Bob")
+        best_answer = test.reply_thread(self.thread, poster="Bob")
 
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
@@ -559,7 +559,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
                 {
                     "posts": [
                         best_answer.pk,
-                        testutils.reply_thread(self.thread, poster="Bob").pk,
+                        test.reply_thread(self.thread, poster="Bob").pk,
                     ]
                 }
             ),
@@ -573,8 +573,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_best_answer_in(self):
         """api merges best answer into other post"""
-        other_post = testutils.reply_thread(self.thread, poster="Bob")
-        best_answer = testutils.reply_thread(self.thread, poster="Bob")
+        other_post = test.reply_thread(self.thread, poster="Bob")
+        best_answer = test.reply_thread(self.thread, poster="Bob")
 
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
@@ -592,7 +592,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_best_answer_in_protected(self):
         """api merges best answer into protected post"""
-        best_answer = testutils.reply_thread(self.thread, poster="Bob")
+        best_answer = test.reply_thread(self.thread, poster="Bob")
 
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
@@ -603,7 +603,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
                 {
                     "posts": [
                         best_answer.pk,
-                        testutils.reply_thread(
+                        test.reply_thread(
                             self.thread, poster="Bob", is_protected=True
                         ).pk,
                     ]
@@ -623,10 +623,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_merge_posts": True})
     def test_merge_remove_reads(self):
         """two posts merge removes read tracker from post"""
-        post_a = testutils.reply_thread(
+        post_a = test.reply_thread(
             self.thread, poster=self.user, message="Battęry"
         )
-        post_b = testutils.reply_thread(self.thread, poster=self.user, message="Hórse")
+        post_b = test.reply_thread(self.thread, poster=self.user, message="Hórse")
 
         poststracker.save_read(self.user, post_a)
         poststracker.save_read(self.user, post_b)

+ 36 - 36
misago/threads/tests/test_thread_postmove_api.py

@@ -4,11 +4,11 @@ from django.urls import reverse
 
 from misago.categories.models import Category
 from misago.readtracker import poststracker
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread
 from misago.threads.serializers.moderation import POSTS_LIMIT
 from misago.threads.test import patch_category_acl, patch_other_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
@@ -16,7 +16,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
 
         self.api_link = reverse(
             "misago:api:thread-post-move", kwargs={"thread_pk": self.thread.pk}
@@ -123,7 +123,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_other_thread_exists(self):
         """api validates if other thread exists"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         response = self.client.post(
             self.api_link, {"new_thread": other_thread.get_absolute_url()}
@@ -143,7 +143,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_other_thread_is_invisible(self):
         """api validates if other thread is visible"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         response = self.client.post(
             self.api_link, {"new_thread": other_thread.get_absolute_url()}
@@ -163,7 +163,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_other_thread_isnt_replyable(self):
         """api validates if other thread can be replied"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         response = self.client.post(
             self.api_link, {"new_thread": other_thread.get_absolute_url()}
@@ -177,7 +177,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_empty_data(self):
         """api handles empty data"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 400)
@@ -186,7 +186,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_empty_posts_data_json(self):
         """api handles empty json data"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
@@ -202,7 +202,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_empty_posts_data_form(self):
         """api handles empty form data"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link, {"new_thread": other_thread.get_absolute_url()}
@@ -216,7 +216,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_no_posts_ids(self):
         """api rejects no posts ids"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
@@ -232,7 +232,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_invalid_posts_data(self):
         """api handles invalid data"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
@@ -249,7 +249,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_invalid_posts_ids(self):
         """api handles invalid post id"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
@@ -269,7 +269,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_limit(self):
         """api rejects more posts than move limit"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
@@ -293,7 +293,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_invisible(self):
         """api validates posts visibility"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
@@ -301,7 +301,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
                 {
                     "new_thread": other_thread.get_absolute_url(),
                     "posts": [
-                        testutils.reply_thread(self.thread, is_unapproved=True).pk
+                        test.reply_thread(self.thread, is_unapproved=True).pk
                     ],
                 }
             ),
@@ -315,14 +315,14 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_other_thread_posts(self):
         """api recjects attempt to move other thread's post"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
             json.dumps(
                 {
                     "new_thread": other_thread.get_absolute_url(),
-                    "posts": [testutils.reply_thread(other_thread, is_hidden=True).pk],
+                    "posts": [test.reply_thread(other_thread, is_hidden=True).pk],
                 }
             ),
             content_type="application/json",
@@ -335,14 +335,14 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_event(self):
         """api rejects events move"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
             json.dumps(
                 {
                     "new_thread": other_thread.get_absolute_url(),
-                    "posts": [testutils.reply_thread(self.thread, is_event=True).pk],
+                    "posts": [test.reply_thread(self.thread, is_event=True).pk],
                 }
             ),
             content_type="application/json",
@@ -353,7 +353,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_first_post(self):
         """api rejects first post move"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
@@ -373,14 +373,14 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_hidden_posts(self):
         """api recjects attempt to move urneadable hidden post"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
             json.dumps(
                 {
                     "new_thread": other_thread.get_absolute_url(),
-                    "posts": [testutils.reply_thread(self.thread, is_hidden=True).pk],
+                    "posts": [test.reply_thread(self.thread, is_hidden=True).pk],
                 }
             ),
             content_type="application/json",
@@ -394,7 +394,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True, "can_close_threads": False})
     def test_move_posts_closed_thread_no_permission(self):
         """api recjects attempt to move posts from closed thread"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         self.thread.is_closed = True
         self.thread.save()
@@ -404,7 +404,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "new_thread": other_thread.get_absolute_url(),
-                    "posts": [testutils.reply_thread(self.thread).pk],
+                    "posts": [test.reply_thread(self.thread).pk],
                 }
             ),
             content_type="application/json",
@@ -419,7 +419,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_posts_closed_category_no_permission(self):
         """api recjects attempt to move posts from closed thread"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         self.category.is_closed = True
         self.category.save()
@@ -429,7 +429,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             json.dumps(
                 {
                     "new_thread": other_thread.get_absolute_url(),
-                    "posts": [testutils.reply_thread(self.thread).pk],
+                    "posts": [test.reply_thread(self.thread).pk],
                 }
             ),
             content_type="application/json",
@@ -444,13 +444,13 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_posts(self):
         """api moves posts to other thread"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         posts = (
-            testutils.reply_thread(self.thread).pk,
-            testutils.reply_thread(self.thread).pk,
-            testutils.reply_thread(self.thread).pk,
-            testutils.reply_thread(self.thread).pk,
+            test.reply_thread(self.thread).pk,
+            test.reply_thread(self.thread).pk,
+            test.reply_thread(self.thread).pk,
+            test.reply_thread(self.thread).pk,
         )
 
         self.thread.refresh_from_db()
@@ -475,8 +475,8 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_best_answer(self):
         """api moves best answer to other thread"""
-        other_thread = testutils.post_thread(self.other_category)
-        best_answer = testutils.reply_thread(self.thread)
+        other_thread = test.post_thread(self.other_category)
+        best_answer = test.reply_thread(self.thread)
 
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.synchronize()
@@ -511,11 +511,11 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_move_posts_reads(self):
         """api moves posts reads together with posts"""
-        other_thread = testutils.post_thread(self.other_category)
+        other_thread = test.post_thread(self.other_category)
 
         posts = (
-            testutils.reply_thread(self.thread),
-            testutils.reply_thread(self.thread),
+            test.reply_thread(self.thread),
+            test.reply_thread(self.thread),
         )
 
         self.thread.refresh_from_db()

+ 11 - 11
misago/threads/tests/test_thread_postpatch_api.py

@@ -5,10 +5,10 @@ from django.urls import reverse
 from django.utils import timezone
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread, Post
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ThreadPostPatchApiTestCase(AuthenticatedUserTestCase):
@@ -16,8 +16,8 @@ class ThreadPostPatchApiTestCase(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
-        self.post = testutils.reply_thread(self.thread, poster=self.user)
+        self.thread = test.post_thread(category=self.category)
+        self.post = test.reply_thread(self.thread, poster=self.user)
 
         self.api_link = reverse(
             "misago:api:thread-post-detail",
@@ -762,10 +762,10 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
 
     def test_like_liked_post(self):
         """api adds user like to post"""
-        testutils.like_post(self.post, username="Myo")
-        testutils.like_post(self.post, username="Mugi")
-        testutils.like_post(self.post, username="Bob")
-        testutils.like_post(self.post, username="Miku")
+        test.like_post(self.post, username="Myo")
+        test.like_post(self.post, username="Mugi")
+        test.like_post(self.post, username="Bob")
+        test.like_post(self.post, username="Miku")
 
         response = self.patch(
             self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
@@ -791,7 +791,7 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
 
     def test_unlike_post(self):
         """api removes user like from post"""
-        testutils.like_post(self.post, self.user)
+        test.like_post(self.post, self.user)
 
         response = self.patch(
             self.api_link, [{"op": "replace", "path": "is-liked", "value": False}]
@@ -809,7 +809,7 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
 
     def test_like_post_no_change(self):
         """api does no state change if we are linking liked post"""
-        testutils.like_post(self.post, self.user)
+        test.like_post(self.post, self.user)
 
         response = self.patch(
             self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
@@ -845,7 +845,7 @@ class ThreadEventPatchApiTestCase(ThreadPostPatchApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.event = testutils.reply_thread(
+        self.event = test.reply_thread(
             self.thread, poster=self.user, is_event=True
         )
 

+ 2 - 2
misago/threads/tests/test_thread_postread_api.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 from django.utils import timezone
 
-from misago.threads import testutils
+from misago.threads import test
 
 from .test_threads_api import ThreadsApiTestCase
 
@@ -10,7 +10,7 @@ class PostReadApiTests(ThreadsApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.post = testutils.reply_thread(
+        self.post = test.reply_thread(
             self.thread, poster=self.user, posted_on=timezone.now()
         )
 

+ 13 - 13
misago/threads/tests/test_thread_postsplit_api.py

@@ -4,11 +4,11 @@ from django.urls import reverse
 
 from misago.categories.models import Category
 from misago.readtracker import poststracker
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Post, Thread
 from misago.threads.serializers.moderation import POSTS_LIMIT
 from misago.threads.test import patch_category_acl, patch_other_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
@@ -16,10 +16,10 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
         self.posts = [
-            testutils.reply_thread(self.thread).pk,
-            testutils.reply_thread(self.thread).pk,
+            test.reply_thread(self.thread).pk,
+            test.reply_thread(self.thread).pk,
         ]
 
         self.api_link = reverse(
@@ -180,7 +180,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
             self.api_link,
             json.dumps(
-                {"posts": [testutils.reply_thread(self.thread, is_unapproved=True).pk]}
+                {"posts": [test.reply_thread(self.thread, is_unapproved=True).pk]}
             ),
             content_type="application/json",
         )
@@ -196,7 +196,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
             self.api_link,
             json.dumps(
-                {"posts": [testutils.reply_thread(self.thread, is_event=True).pk]}
+                {"posts": [test.reply_thread(self.thread, is_event=True).pk]}
             ),
             content_type="application/json",
         )
@@ -222,7 +222,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
             self.api_link,
             json.dumps(
-                {"posts": [testutils.reply_thread(self.thread, is_hidden=True).pk]}
+                {"posts": [test.reply_thread(self.thread, is_hidden=True).pk]}
             ),
             content_type="application/json",
         )
@@ -240,7 +240,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
 
         response = self.client.post(
             self.api_link,
-            json.dumps({"posts": [testutils.reply_thread(self.thread).pk]}),
+            json.dumps({"posts": [test.reply_thread(self.thread).pk]}),
             content_type="application/json",
         )
         self.assertEqual(response.status_code, 400)
@@ -257,7 +257,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
 
         response = self.client.post(
             self.api_link,
-            json.dumps({"posts": [testutils.reply_thread(self.thread).pk]}),
+            json.dumps({"posts": [test.reply_thread(self.thread).pk]}),
             content_type="application/json",
         )
         self.assertEqual(response.status_code, 400)
@@ -269,12 +269,12 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_split_other_thread_posts(self):
         """api recjects attempt to split other thread's post"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         response = self.client.post(
             self.api_link,
             json.dumps(
-                {"posts": [testutils.reply_thread(other_thread, is_hidden=True).pk]}
+                {"posts": [test.reply_thread(other_thread, is_hidden=True).pk]}
             ),
             content_type="application/json",
         )
@@ -638,7 +638,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_split_best_answer(self):
         """api splits best answer to new thread"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
 
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.synchronize()

+ 3 - 3
misago/threads/tests/test_thread_reply_api.py

@@ -2,10 +2,10 @@ from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ReplyThreadTests(AuthenticatedUserTestCase):
@@ -13,7 +13,7 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
 
         self.api_link = reverse(
             "misago:api:thread-post-list", kwargs={"thread_pk": self.thread.pk}

+ 1 - 1
misago/threads/tests/test_thread_start_api.py

@@ -3,7 +3,7 @@ from django.urls import reverse
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class StartThreadTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/threads/tests/test_threadparticipant_model.py

@@ -3,7 +3,7 @@ from django.utils import timezone
 
 from misago.categories.models import Category
 from misago.threads.models import Post, Thread, ThreadParticipant
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class ThreadParticipantTests(TestCase):

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

@@ -5,11 +5,11 @@ from django.urls import reverse
 
 from misago.categories import THREADS_ROOT_NAME
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread
 from misago.threads.test import patch_category_acl
 from misago.threads.threadtypes import trees_map
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ThreadsApiTestCase(AuthenticatedUserTestCase):
@@ -21,7 +21,7 @@ class ThreadsApiTestCase(AuthenticatedUserTestCase):
         self.root = Category.objects.get(tree_id=threads_tree_id, level=0)
         self.category = Category.objects.get(slug="first-category")
 
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
         self.api_link = self.thread.get_api_url()
 
     def get_thread_json(self):
@@ -85,7 +85,7 @@ class ThreadRetrieveApiTests(ThreadsApiTestCase):
 
     def test_api_validates_posts_visibility(self):
         """api validates posts visiblity"""
-        hidden_post = testutils.reply_thread(
+        hidden_post = test.reply_thread(
             self.thread, is_hidden=True, message="I'am hidden test message!"
         )
 
@@ -103,7 +103,7 @@ class ThreadRetrieveApiTests(ThreadsApiTestCase):
             )  # hidden post's body is visible with permission
 
         # unapproved posts shouldn't show at all
-        unapproved_post = testutils.reply_thread(self.thread, is_unapproved=True)
+        unapproved_post = test.reply_thread(self.thread, is_unapproved=True)
 
         with patch_category_acl({"can_approve_content": False}):
             response = self.client.get(self.tested_links[1])
@@ -144,7 +144,7 @@ class ThreadDeleteApiTests(ThreadsApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.last_thread = testutils.post_thread(category=self.category)
+        self.last_thread = test.post_thread(category=self.category)
         self.api_link = self.last_thread.get_api_url()
 
     def test_delete_thread_no_permission(self):

+ 4 - 4
misago/threads/tests/test_threads_bulkdelete_api.py

@@ -4,7 +4,7 @@ from django.urls import reverse
 
 from misago.categories import PRIVATE_THREADS_ROOT_NAME
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Thread
 from misago.threads.serializers.moderation import THREADS_LIMIT
 from misago.threads.test import patch_category_acl
@@ -20,9 +20,9 @@ class ThreadsBulkDeleteApiTests(ThreadsApiTestCase):
         self.api_link = reverse("misago:api:thread-list")
 
         self.threads = [
-            testutils.post_thread(category=self.category, poster=self.user),
-            testutils.post_thread(category=self.category),
-            testutils.post_thread(category=self.category, poster=self.user),
+            test.post_thread(category=self.category, poster=self.user),
+            test.post_thread(category=self.category),
+            test.post_thread(category=self.category, poster=self.user),
         ]
 
     def delete(self, url, data=None):

+ 11 - 11
misago/threads/tests/test_threads_editor_api.py

@@ -6,11 +6,11 @@ from misago.acl import useracl
 from misago.acl.objectacl import add_acl_to_obj
 from misago.categories.models import Category
 from misago.conftest import get_cache_versions
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Attachment
 from misago.threads.serializers import AttachmentSerializer
 from misago.threads.test import patch_category_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 TESTFILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "testfiles")
 TEST_DOCUMENT_PATH = os.path.join(TESTFILES_DIR, "document.pdf")
@@ -207,7 +207,7 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
         self.api_link = reverse(
             "misago:api:thread-post-editor", kwargs={"thread_pk": self.thread.pk}
         )
@@ -297,7 +297,7 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
         """api validates replied post visibility"""
 
         # unapproved reply can't be replied to
-        unapproved_reply = testutils.reply_thread(self.thread, is_unapproved=True)
+        unapproved_reply = test.reply_thread(self.thread, is_unapproved=True)
 
         with patch_category_acl({"can_reply_threads": True}):
             response = self.client.get(
@@ -306,7 +306,7 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
             self.assertEqual(response.status_code, 404)
 
         # hidden reply can't be replied to
-        hidden_reply = testutils.reply_thread(self.thread, is_hidden=True)
+        hidden_reply = test.reply_thread(self.thread, is_hidden=True)
 
         with patch_category_acl({"can_reply_threads": True}):
             response = self.client.get("%s?reply=%s" % (self.api_link, hidden_reply.pk))
@@ -317,8 +317,8 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
 
     def test_reply_to_other_thread_post(self):
         """api validates is replied post belongs to same thread"""
-        other_thread = testutils.post_thread(category=self.category)
-        reply_to = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(category=self.category)
+        reply_to = test.reply_thread(other_thread)
 
         response = self.client.get("%s?reply=%s" % (self.api_link, reply_to.pk))
         self.assertEqual(response.status_code, 404)
@@ -326,7 +326,7 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
     @patch_category_acl({"can_reply_threads": True})
     def test_reply_to_event(self):
         """events can't be replied to"""
-        reply_to = testutils.reply_thread(self.thread, is_event=True)
+        reply_to = test.reply_thread(self.thread, is_event=True)
 
         response = self.client.get("%s?reply=%s" % (self.api_link, reply_to.pk))
         self.assertEqual(response.status_code, 403)
@@ -335,7 +335,7 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
     @patch_category_acl({"can_reply_threads": True})
     def test_reply_to(self):
         """api includes replied to post details in response"""
-        reply_to = testutils.reply_thread(self.thread)
+        reply_to = test.reply_thread(self.thread)
 
         response = self.client.get("%s?reply=%s" % (self.api_link, reply_to.pk))
 
@@ -354,8 +354,8 @@ class EditReplyEditorApiTests(EditorApiTestCase):
     def setUp(self):
         super().setUp()
 
-        self.thread = testutils.post_thread(category=self.category)
-        self.post = testutils.reply_thread(self.thread, poster=self.user)
+        self.thread = test.post_thread(category=self.category)
+        self.post = test.reply_thread(self.thread, poster=self.user)
 
         self.api_link = reverse(
             "misago:api:thread-post-editor",

+ 57 - 57
misago/threads/tests/test_threads_merge_api.py

@@ -7,7 +7,7 @@ from misago.acl.objectacl import add_acl_to_obj
 from misago.categories.models import Category
 from misago.conftest import get_cache_versions
 from misago.readtracker import poststracker
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.models import Poll, PollVote, Post, Thread
 from misago.threads.serializers import ThreadsListSerializer
 from misago.threads.serializers.moderation import THREADS_LIMIT
@@ -98,7 +98,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
     def test_merge_with_invisible_thread(self):
         """api validates if we are trying to merge with inaccesible thread"""
-        unaccesible_thread = testutils.post_thread(category=self.other_category)
+        unaccesible_thread = test.post_thread(category=self.other_category)
 
         response = self.client.post(
             self.api_link,
@@ -113,7 +113,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
     def test_merge_no_permission(self):
         """api validates permission to merge threads"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -147,7 +147,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_close_threads": False})
     def test_thread_category_is_closed(self):
         """api validates if thread's category is open"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         self.category.is_closed = True
         self.category.save()
@@ -188,7 +188,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_close_threads": False})
     def test_thread_is_closed(self):
         """api validates if thread is open"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
         other_thread.is_closed = True
         other_thread.save()
@@ -223,7 +223,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         """api rejects too many threads to merge"""
         threads = []
         for _ in range(THREADS_LIMIT + 1):
-            threads.append(testutils.post_thread(category=self.category).pk)
+            threads.append(test.post_thread(category=self.category).pk)
 
         response = self.client.post(
             self.api_link,
@@ -242,7 +242,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_no_final_thread(self):
         """api rejects merge because no data to merge threads was specified"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -261,7 +261,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_invalid_final_title(self):
         """api rejects merge because final thread title was invalid"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -287,7 +287,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_invalid_category(self):
         """api rejects merge because final category was invalid"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -308,7 +308,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_start_threads": False})
     def test_merge_unallowed_start_thread(self):
         """api rejects merge because category isn't allowing starting threads"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -330,7 +330,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_invalid_weight(self):
         """api rejects merge because final weight was invalid"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -353,7 +353,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_unallowed_global_weight(self):
         """api rejects merge because global weight was unallowed"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -380,7 +380,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_unallowed_local_weight(self):
         """api rejects merge because local weight was unallowed"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -403,7 +403,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_pin_threads": 1})
     def test_merge_allowed_local_weight(self):
         """api allows local weight"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -430,7 +430,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_pin_threads": 2})
     def test_merge_allowed_global_weight(self):
         """api allows global weight"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -457,7 +457,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_close_threads": False})
     def test_merge_unallowed_close(self):
         """api rejects merge because closing thread was unallowed"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -484,7 +484,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_close_threads": True})
     def test_merge_with_close(self):
         """api allows for closing thread"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -512,7 +512,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_hide_threads": 0})
     def test_merge_unallowed_hidden(self):
         """api rejects merge because hidden thread was unallowed"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -539,7 +539,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True, "can_hide_threads": 1})
     def test_merge_with_hide(self):
         """api allows for hiding thread"""
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -568,7 +568,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     def test_merge(self):
         """api performs basic merge"""
         posts_ids = [p.id for p in Post.objects.all()]
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         response = self.client.post(
             self.api_link,
@@ -614,7 +614,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     def test_merge_kitchensink(self):
         """api performs merge"""
         posts_ids = [p.id for p in Post.objects.all()]
-        thread = testutils.post_thread(category=self.category)
+        thread = test.post_thread(category=self.category)
 
         poststracker.save_read(self.user, self.thread.first_post)
         poststracker.save_read(self.user, thread.first_post)
@@ -690,9 +690,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_merged_best_answer(self):
         """api merges two threads successfully, moving best answer to old thread"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
@@ -716,12 +716,12 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_merge_conflict_best_answer(self):
         """api errors on merge conflict, returning list of available best answers"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -761,12 +761,12 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_best_answer_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -798,12 +798,12 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_unmark_all_best_answers(self):
         """api unmarks all best answers when unmark all choice is selected"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -829,12 +829,12 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_first_best_answer(self):
         """api unmarks other best answer on merge"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -859,12 +859,12 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_other_best_answer(self):
         """api unmarks first best answer on merge"""
-        best_answer = testutils.reply_thread(self.thread)
+        best_answer = test.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
 
-        other_thread = testutils.post_thread(self.category)
-        other_best_answer = testutils.reply_thread(other_thread)
+        other_thread = test.post_thread(self.category)
+        other_best_answer = test.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
 
@@ -889,8 +889,8 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_poll(self):
         """api merges two threads successfully, keeping poll from other thread"""
-        other_thread = testutils.post_thread(self.category)
-        poll = testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.category)
+        poll = test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,
@@ -919,8 +919,8 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_moved_poll(self):
         """api merges two threads successfully, moving poll from old thread"""
-        other_thread = testutils.post_thread(self.category)
-        poll = testutils.post_poll(self.thread, self.user)
+        other_thread = test.post_thread(self.category)
+        poll = test.post_poll(self.thread, self.user)
 
         response = self.client.post(
             self.api_link,
@@ -949,9 +949,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_poll(self):
         """api errors on merge conflict, returning list of available polls"""
-        other_thread = testutils.post_thread(self.category)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.category)
+        poll = test.post_poll(self.thread, self.user)
+        other_poll = test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,
@@ -987,10 +987,10 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_poll_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
-        testutils.post_poll(self.thread, self.user)
-        testutils.post_poll(other_thread, self.user)
+        test.post_poll(self.thread, self.user)
+        test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,
@@ -1015,10 +1015,10 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_delete_all_polls(self):
         """api deletes all polls when delete all choice is selected"""
-        other_thread = testutils.post_thread(self.category)
+        other_thread = test.post_thread(self.category)
 
-        testutils.post_poll(self.thread, self.user)
-        testutils.post_poll(other_thread, self.user)
+        test.post_poll(self.thread, self.user)
+        test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,
@@ -1041,9 +1041,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_first_poll(self):
         """api deletes other poll on merge"""
-        other_thread = testutils.post_thread(self.category)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.category)
+        poll = test.post_poll(self.thread, self.user)
+        other_poll = test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,
@@ -1070,9 +1070,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_other_poll(self):
         """api deletes first poll on merge"""
-        other_thread = testutils.post_thread(self.category)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+        other_thread = test.post_thread(self.category)
+        poll = test.post_poll(self.thread, self.user)
+        other_poll = test.post_poll(other_thread, self.user)
 
         response = self.client.post(
             self.api_link,

+ 4 - 4
misago/threads/tests/test_threads_moderation.py

@@ -1,7 +1,7 @@
 from misago.categories.models import Category
-from misago.threads import moderation, testutils
+from misago.threads import moderation, test
 from misago.threads.models import Thread
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class MockRequest(object):
@@ -16,7 +16,7 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
 
         self.request = MockRequest(self.user)
         self.category = Category.objects.all_categories()[:1][0]
-        self.thread = testutils.post_thread(self.category)
+        self.thread = test.post_thread(self.category)
 
     def tearDown(self):
         super().tearDown()
@@ -120,7 +120,7 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
 
     def test_approve_thread(self):
         """approve_thread approves unapproved thread"""
-        self.thread = testutils.post_thread(self.category, is_unapproved=True)
+        self.thread = test.post_thread(self.category, is_unapproved=True)
 
         self.assertTrue(self.thread.is_unapproved)
         self.assertTrue(self.thread.first_post.is_unapproved)

+ 46 - 46
misago/threads/tests/test_threadslists.py

@@ -8,9 +8,9 @@ from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.conf import settings
 from misago.readtracker import poststracker
-from misago.threads import testutils
+from misago.threads import test
 from misago.users.models import AnonymousUser
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 LISTS_URLS = ("", "my/", "new/", "unread/", "subscribed/")
 
@@ -243,7 +243,7 @@ class AllThreadsListTests(ThreadsListTestCase):
         )
         test_category = Category.objects.get(slug="hidden-category")
 
-        testutils.post_thread(category=self.category_b)
+        test.post_thread(category=self.category_b)
 
         response = self.client.get("/")
         self.assertEqual(response.status_code, 200)
@@ -289,11 +289,11 @@ class AllThreadsListTests(ThreadsListTestCase):
         threads list displays globally pinned threads first
         and locally ones inbetween other
         """
-        globally = testutils.post_thread(category=self.first_category, is_global=True)
+        globally = test.post_thread(category=self.first_category, is_global=True)
 
-        locally = testutils.post_thread(category=self.first_category, is_pinned=True)
+        locally = test.post_thread(category=self.first_category, is_pinned=True)
 
-        standard = testutils.post_thread(category=self.first_category)
+        standard = test.post_thread(category=self.first_category)
 
         response = self.client.get("/")
         self.assertEqual(response.status_code, 200)
@@ -346,7 +346,7 @@ class AllThreadsListTests(ThreadsListTestCase):
 
         threads = []
         for _ in range(settings.MISAGO_THREADS_PER_PAGE * 3):
-            threads.append(testutils.post_thread(category=self.first_category))
+            threads.append(test.post_thread(category=self.first_category))
 
         # secondary page renders
         response = self.client.get("/?page=2")
@@ -425,11 +425,11 @@ class CategoryThreadsListTests(ThreadsListTestCase):
         category threads list displays globally pinned threads first
         then locally ones and unpinned last
         """
-        globally = testutils.post_thread(category=self.first_category, is_global=True)
+        globally = test.post_thread(category=self.first_category, is_global=True)
 
-        locally = testutils.post_thread(category=self.first_category, is_pinned=True)
+        locally = test.post_thread(category=self.first_category, is_pinned=True)
 
-        standard = testutils.post_thread(category=self.first_category)
+        standard = test.post_thread(category=self.first_category)
 
         response = self.client.get(self.first_category.get_absolute_url())
         self.assertEqual(response.status_code, 200)
@@ -481,7 +481,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_renders_test_thread(self):
         """list renders test thread with valid top category"""
-        test_thread = testutils.post_thread(category=self.category_c)
+        test_thread = test.post_thread(category=self.category_c)
 
         response = self.client.get("/")
         self.assertEqual(response.status_code, 200)
@@ -539,7 +539,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
         )
 
         test_category = Category.objects.get(slug="hidden-category")
-        test_thread = testutils.post_thread(category=test_category)
+        test_thread = test.post_thread(category=test_category)
 
         response = self.client.get("/")
         self.assertEqual(response.status_code, 200)
@@ -554,7 +554,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
 
         test_category = Category.objects.get(slug="hidden-category")
 
-        testutils.post_thread(category=test_category)
+        test.post_thread(category=test_category)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -565,7 +565,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_user_see_own_unapproved_thread(self):
         """list renders unapproved thread that belongs to viewer"""
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a, poster=self.user, is_unapproved=True
         )
 
@@ -583,7 +583,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_user_cant_see_unapproved_thread(self):
         """list hides unapproved thread that belongs to other user"""
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a, is_unapproved=True
         )
 
@@ -601,7 +601,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_user_cant_see_hidden_thread(self):
         """list hides hidden thread that belongs to other user"""
-        test_thread = testutils.post_thread(category=self.category_a, is_hidden=True)
+        test_thread = test.post_thread(category=self.category_a, is_hidden=True)
 
         response = self.client.get("/")
         self.assertEqual(response.status_code, 200)
@@ -617,7 +617,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_user_cant_see_own_hidden_thread(self):
         """list hides hidden thread that belongs to viewer"""
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a, poster=self.user, is_hidden=True
         )
 
@@ -635,7 +635,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
     @patch_categories_acl({"can_hide_threads": 1})
     def test_list_user_can_see_own_hidden_thread(self):
         """list shows hidden thread that belongs to viewer due to permission"""
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a, poster=self.user, is_hidden=True
         )
 
@@ -653,7 +653,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
     @patch_categories_acl({"can_hide_threads": 1})
     def test_list_user_can_see_hidden_thread(self):
         """list shows hidden thread that belongs to other user due to permission"""
-        test_thread = testutils.post_thread(category=self.category_a, is_hidden=True)
+        test_thread = test.post_thread(category=self.category_a, is_hidden=True)
 
         response = self.client.get("/")
         self.assertEqual(response.status_code, 200)
@@ -669,7 +669,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
     @patch_categories_acl({"can_approve_content": 1})
     def test_list_user_can_see_unapproved_thread(self):
         """list shows hidden thread that belongs to other user due to permission"""
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a, is_unapproved=True
         )
 
@@ -714,9 +714,9 @@ class MyThreadsListTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_renders_test_thread(self):
         """list renders only threads posted by user"""
-        test_thread = testutils.post_thread(category=self.category_a, poster=self.user)
+        test_thread = test.post_thread(category=self.category_a, poster=self.user)
 
-        other_thread = testutils.post_thread(category=self.category_a)
+        other_thread = test.post_thread(category=self.category_a)
 
         response = self.client.get("/my/")
         self.assertEqual(response.status_code, 200)
@@ -775,7 +775,7 @@ class NewThreadsListTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_renders_new_thread(self):
         """list renders new thread"""
-        test_thread = testutils.post_thread(category=self.category_a)
+        test_thread = test.post_thread(category=self.category_a)
 
         response = self.client.get("/new/")
         self.assertEqual(response.status_code, 200)
@@ -808,11 +808,11 @@ class NewThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=10)
         self.user.save()
 
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
         )
 
-        testutils.reply_thread(
+        test.reply_thread(
             test_thread, posted_on=self.user.joined_on + timedelta(days=4)
         )
 
@@ -847,7 +847,7 @@ class NewThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=10)
         self.user.save()
 
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a,
             started_on=timezone.now()
             - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 1),
@@ -882,7 +882,7 @@ class NewThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=5)
         self.user.save()
 
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a,
             started_on=self.user.joined_on - timedelta(minutes=1),
         )
@@ -916,7 +916,7 @@ class NewThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=5)
         self.user.save()
 
-        test_thread = testutils.post_thread(category=self.category_a)
+        test_thread = test.post_thread(category=self.category_a)
         poststracker.save_read(self.user, test_thread.first_post)
 
         response = self.client.get("/new/")
@@ -976,9 +976,9 @@ class UnreadThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=5)
         self.user.save()
 
-        test_thread = testutils.post_thread(category=self.category_a)
+        test_thread = test.post_thread(category=self.category_a)
         poststracker.save_read(self.user, test_thread.first_post)
-        testutils.reply_thread(test_thread)
+        test.reply_thread(test_thread)
 
         response = self.client.get("/unread/")
         self.assertEqual(response.status_code, 200)
@@ -1011,7 +1011,7 @@ class UnreadThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=5)
         self.user.save()
 
-        test_thread = testutils.post_thread(category=self.category_a)
+        test_thread = test.post_thread(category=self.category_a)
 
         response = self.client.get("/unread/")
         self.assertEqual(response.status_code, 200)
@@ -1042,7 +1042,7 @@ class UnreadThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=5)
         self.user.save()
 
-        test_thread = testutils.post_thread(category=self.category_a)
+        test_thread = test.post_thread(category=self.category_a)
         poststracker.save_read(self.user, test_thread.first_post)
 
         response = self.client.get("/unread/")
@@ -1074,14 +1074,14 @@ class UnreadThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=10)
         self.user.save()
 
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a,
             started_on=timezone.now()
             - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 5),
         )
 
         poststracker.save_read(self.user, test_thread.first_post)
-        testutils.reply_thread(
+        test.reply_thread(
             test_thread, posted_on=test_thread.started_on + timedelta(days=1)
         )
 
@@ -1114,13 +1114,13 @@ class UnreadThreadsListTests(ThreadsListTestCase):
         self.user.joined_on = timezone.now() - timedelta(days=10)
         self.user.save()
 
-        test_thread = testutils.post_thread(
+        test_thread = test.post_thread(
             category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
         )
 
         poststracker.save_read(self.user, test_thread.first_post)
 
-        testutils.reply_thread(
+        test.reply_thread(
             test_thread, posted_on=test_thread.started_on + timedelta(days=1)
         )
 
@@ -1152,7 +1152,7 @@ class SubscribedThreadsListTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_shows_subscribed_thread(self):
         """list shows subscribed thread"""
-        test_thread = testutils.post_thread(category=self.category_a)
+        test_thread = test.post_thread(category=self.category_a)
         self.user.subscription_set.create(
             thread=test_thread,
             category=self.category_a,
@@ -1187,7 +1187,7 @@ class SubscribedThreadsListTests(ThreadsListTestCase):
     @patch_categories_acl()
     def test_list_hides_unsubscribed_thread(self):
         """list shows subscribed thread"""
-        test_thread = testutils.post_thread(category=self.category_a)
+        test_thread = test.post_thread(category=self.category_a)
 
         response = self.client.get("/subscribed/")
         self.assertEqual(response.status_code, 200)
@@ -1246,11 +1246,11 @@ class UnapprovedListTests(ThreadsListTestCase):
     )
     def test_list_shows_all_threads_for_approving_user(self):
         """list shows all threads with unapproved posts when user has perm"""
-        visible_thread = testutils.post_thread(
+        visible_thread = test.post_thread(
             category=self.category_b, is_unapproved=True
         )
 
-        hidden_thread = testutils.post_thread(
+        hidden_thread = test.post_thread(
             category=self.category_b, is_unapproved=False
         )
 
@@ -1273,11 +1273,11 @@ class UnapprovedListTests(ThreadsListTestCase):
     @patch_categories_acl(base_acl={"can_see_unapproved_content_lists": True})
     def test_list_shows_owned_threads_for_unapproving_user(self):
         """list shows owned threads with unapproved posts for user without perm"""
-        visible_thread = testutils.post_thread(
+        visible_thread = test.post_thread(
             poster=self.user, category=self.category_b, is_unapproved=True
         )
 
-        hidden_thread = testutils.post_thread(
+        hidden_thread = test.post_thread(
             category=self.category_b, is_unapproved=True
         )
 
@@ -1316,11 +1316,11 @@ class OwnerOnlyThreadsVisibilityTests(AuthenticatedUserTestCase):
 
     def test_owned_threads_visibility(self):
         """only user-posted threads are visible in category"""
-        visible_thread = testutils.post_thread(
+        visible_thread = test.post_thread(
             poster=self.user, category=self.category, is_unapproved=True
         )
 
-        hidden_thread = testutils.post_thread(
+        hidden_thread = test.post_thread(
             category=self.category, is_unapproved=True
         )
 
@@ -1334,11 +1334,11 @@ class OwnerOnlyThreadsVisibilityTests(AuthenticatedUserTestCase):
         """anons can't see any threads in limited visibility category"""
         self.logout_user()
 
-        user_thread = testutils.post_thread(
+        user_thread = test.post_thread(
             poster=self.user, category=self.category, is_unapproved=True
         )
 
-        guest_thread = testutils.post_thread(category=self.category, is_unapproved=True)
+        guest_thread = test.post_thread(category=self.category, is_unapproved=True)
 
         with patch_category_see_all_threads_acl():
             response = self.client.get(self.category.get_absolute_url())

+ 18 - 18
misago/threads/tests/test_threadview.py

@@ -5,12 +5,12 @@ from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.conf import settings
 from misago.conftest import get_cache_versions
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.checksums import update_post_checksum
 from misago.threads.events import record_event
 from misago.threads.moderation import threads as threads_moderation
 from misago.threads.moderation import hide_post
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 cache_versions = get_cache_versions()
 
@@ -49,7 +49,7 @@ class ThreadViewTestCase(AuthenticatedUserTestCase):
         super().setUp()
 
         self.category = Category.objects.get(slug="first-category")
-        self.thread = testutils.post_thread(category=self.category)
+        self.thread = test.post_thread(category=self.category)
 
 
 class ThreadVisibilityTests(ThreadViewTestCase):
@@ -128,14 +128,14 @@ class ThreadVisibilityTests(ThreadViewTestCase):
 class ThreadPostsVisibilityTests(ThreadViewTestCase):
     def test_post_renders(self):
         """post renders"""
-        post = testutils.reply_thread(self.thread, poster=self.user)
+        post = test.reply_thread(self.thread, poster=self.user)
 
         response = self.client.get(self.thread.get_absolute_url())
         self.assertContains(response, post.get_absolute_url())
 
     def test_invalid_post_renders(self):
         """invalid post renders"""
-        post = testutils.reply_thread(self.thread, poster=self.user)
+        post = test.reply_thread(self.thread, poster=self.user)
 
         post.parsed = "fiddled post content"
         post.save()
@@ -147,7 +147,7 @@ class ThreadPostsVisibilityTests(ThreadViewTestCase):
 
     def test_hidden_post_visibility(self):
         """hidden post renders correctly"""
-        post = testutils.reply_thread(self.thread, message="Hello, I'm hidden post!")
+        post = test.reply_thread(self.thread, message="Hello, I'm hidden post!")
         hide_post(self.user, post)
 
         response = self.client.get(self.thread.get_absolute_url())
@@ -192,7 +192,7 @@ class ThreadPostsVisibilityTests(ThreadViewTestCase):
 
     def test_unapproved_post_visibility(self):
         """unapproved post renders for its author and users with perm to approve content"""
-        post = testutils.reply_thread(self.thread, is_unapproved=True)
+        post = test.reply_thread(self.thread, is_unapproved=True)
 
         # post is hdden because we aren't its author nor user with permission to approve
         response = self.client.get(self.thread.get_absolute_url())
@@ -309,7 +309,7 @@ class ThreadEventVisibilityTests(ThreadViewTestCase):
 
         posts = []
         for _ in range(posts_limit - 1):
-            post = testutils.reply_thread(self.thread)
+            post = test.reply_thread(self.thread)
             posts.append(post)
 
         # test that all events and posts within limits were rendered
@@ -322,7 +322,7 @@ class ThreadEventVisibilityTests(ThreadViewTestCase):
 
         # add second page to thread with more events
         for _ in range(posts_limit):
-            post = testutils.reply_thread(self.thread)
+            post = test.reply_thread(self.thread)
         for _ in range(events_limit):
             request = Mock(user=self.user, user_ip="127.0.0.1")
             event = record_event(request, self.thread, "closed")
@@ -378,7 +378,7 @@ class ThreadEventVisibilityTests(ThreadViewTestCase):
     def test_thread_merged_event_renders(self):
         """merged thread event renders"""
         request = Mock(user=self.user, user_ip="127.0.0.1")
-        other_thread = testutils.post_thread(category=self.category)
+        other_thread = test.post_thread(category=self.category)
         threads_moderation.merge_thread(request, self.thread, other_thread)
 
         event = self.thread.post_set.filter(is_event=True)[0]
@@ -460,7 +460,7 @@ class ThreadAttachmentsViewTests(ThreadViewTestCase):
 class ThreadPollViewTests(ThreadViewTestCase):
     def test_poll_voted_display(self):
         """view has no showstoppers when displaying voted poll"""
-        poll = testutils.post_poll(self.thread, self.user)
+        poll = test.post_poll(self.thread, self.user)
 
         response = self.client.get(self.thread.get_absolute_url())
         self.assertContains(response, poll.question)
@@ -469,7 +469,7 @@ class ThreadPollViewTests(ThreadViewTestCase):
 
     def test_poll_unvoted_display(self):
         """view has no showstoppers when displaying poll vote form"""
-        poll = testutils.post_poll(self.thread, self.user)
+        poll = test.post_poll(self.thread, self.user)
         poll.pollvote_set.all().delete()
 
         response = self.client.get(self.thread.get_absolute_url())
@@ -478,7 +478,7 @@ class ThreadPollViewTests(ThreadViewTestCase):
 
     def test_poll_anonymous_view(self):
         """view has no showstoppers when displaying poll to anon user"""
-        poll = testutils.post_poll(self.thread, self.user)
+        poll = test.post_poll(self.thread, self.user)
 
         self.logout_user()
 
@@ -491,7 +491,7 @@ class ThreadPollViewTests(ThreadViewTestCase):
 class ThreadLikedPostsViewTests(ThreadViewTestCase):
     def test_liked_posts_display(self):
         """view has no showstoppers on displaying posts with likes"""
-        testutils.like_post(self.thread.first_post, self.user)
+        test.like_post(self.thread.first_post, self.user)
 
         response = self.client.get(self.thread.get_absolute_url())
         self.assertContains(response, '"is_liked": true')
@@ -500,7 +500,7 @@ class ThreadLikedPostsViewTests(ThreadViewTestCase):
         """
         view has no showstoppers on displaying posts with likes without perm
         """
-        testutils.like_post(self.thread.first_post, self.user)
+        test.like_post(self.thread.first_post, self.user)
 
         with patch_category_acl({"can_see_posts_likes": 0}):
             response = self.client.get(self.thread.get_absolute_url())
@@ -514,14 +514,14 @@ class ThreadAnonViewTests(ThreadViewTestCase):
         """kitchensink thread view has no showstoppers for anons"""
         request = Mock(user=self.user, user_ip="127.0.0.1")
 
-        poll = testutils.post_poll(self.thread, self.user)
+        poll = test.post_poll(self.thread, self.user)
         event = record_event(request, self.thread, "closed")
 
         hidden_event = record_event(request, self.thread, "opened")
         hide_post(self.user, hidden_event)
 
-        unapproved_post = testutils.reply_thread(self.thread, is_unapproved=True)
-        post = testutils.reply_thread(self.thread)
+        unapproved_post = test.reply_thread(self.thread, is_unapproved=True)
+        post = test.reply_thread(self.thread)
 
         self.logout_user()
 

+ 3 - 3
misago/threads/tests/test_updatepostschecksums.py

@@ -4,7 +4,7 @@ from django.core.management import call_command
 from django.test import TestCase
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.management.commands import updatepostschecksums
 from misago.threads.models import Post
 
@@ -24,9 +24,9 @@ class UpdatePostsChecksumsTests(TestCase):
         """command updates posts checksums"""
         category = Category.objects.all_categories()[:1][0]
 
-        threads = [testutils.post_thread(category) for _ in range(5)]
+        threads = [test.post_thread(category) for _ in range(5)]
         for _, thread in enumerate(threads):
-            [testutils.reply_thread(thread) for _ in range(3)]
+            [test.reply_thread(thread) for _ in range(3)]
             thread.save()
 
         Post.objects.update(parsed="Hello world!")

+ 9 - 9
misago/threads/tests/test_utils.py

@@ -1,7 +1,7 @@
 from django.test import TestCase
 
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.utils import add_categories_to_items, get_thread_id_from_url
 
 
@@ -68,56 +68,56 @@ class AddCategoriesToItemsTests(TestCase):
 
     def test_root_thread_from_root(self):
         """thread in root category is handled"""
-        thread = testutils.post_thread(category=self.root)
+        thread = test.post_thread(category=self.root)
         add_categories_to_items(self.root, self.categories, [thread])
 
         self.assertEqual(thread.category, self.root)
 
     def test_root_thread_from_elsewhere(self):
         """thread in root category is handled"""
-        thread = testutils.post_thread(category=self.root)
+        thread = test.post_thread(category=self.root)
         add_categories_to_items(self.category_e, self.categories, [thread])
 
         self.assertEqual(thread.category, self.root)
 
     def test_direct_child_thread_from_parent(self):
         """thread in direct child category is handled"""
-        thread = testutils.post_thread(category=self.category_e)
+        thread = test.post_thread(category=self.category_e)
         add_categories_to_items(self.root, self.categories, [thread])
 
         self.assertEqual(thread.category, self.category_e)
 
     def test_direct_child_thread_from_elsewhere(self):
         """thread in direct child category is handled"""
-        thread = testutils.post_thread(category=self.category_e)
+        thread = test.post_thread(category=self.category_e)
         add_categories_to_items(self.category_b, self.categories, [thread])
 
         self.assertEqual(thread.category, self.category_e)
 
     def test_child_thread_from_root(self):
         """thread in child category is handled"""
-        thread = testutils.post_thread(category=self.category_d)
+        thread = test.post_thread(category=self.category_d)
         add_categories_to_items(self.root, self.categories, [thread])
 
         self.assertEqual(thread.category, self.category_d)
 
     def test_child_thread_from_parent(self):
         """thread in child category is handled"""
-        thread = testutils.post_thread(category=self.category_d)
+        thread = test.post_thread(category=self.category_d)
         add_categories_to_items(self.category_a, self.categories, [thread])
 
         self.assertEqual(thread.category, self.category_d)
 
     def test_child_thread_from_category(self):
         """thread in child category is handled"""
-        thread = testutils.post_thread(category=self.category_d)
+        thread = test.post_thread(category=self.category_d)
         add_categories_to_items(self.category_d, self.categories, [thread])
 
         self.assertEqual(thread.category, self.category_d)
 
     def test_child_thread_from_elsewhere(self):
         """thread in child category is handled"""
-        thread = testutils.post_thread(category=self.category_d)
+        thread = test.post_thread(category=self.category_d)
         add_categories_to_items(self.category_f, self.categories, [thread])
 
         self.assertEqual(thread.category, self.category_d)

+ 1 - 1
misago/threads/tests/test_validate_post.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.categories.models import Category
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class ValidatePostTests(AuthenticatedUserTestCase):

+ 0 - 218
misago/threads/testutils.py

@@ -1,218 +0,0 @@
-from datetime import timedelta
-
-from django.contrib.auth import get_user_model
-from django.utils import timezone
-
-from misago.core.utils import slugify
-from misago.users.testutils import create_test_user
-
-from .checksums import update_post_checksum
-from .models import Poll, Post, Thread
-
-
-User = get_user_model()
-
-
-def post_thread(
-    category,
-    title="Test thread",
-    poster="Tester",
-    is_global=False,
-    is_pinned=False,
-    is_unapproved=False,
-    is_hidden=False,
-    is_closed=False,
-    started_on=None,
-):
-    started_on = started_on or timezone.now()
-
-    kwargs = {
-        "category": category,
-        "title": title,
-        "slug": slugify(title),
-        "started_on": started_on,
-        "last_post_on": started_on,
-        "is_unapproved": is_unapproved,
-        "is_hidden": is_hidden,
-        "is_closed": is_closed,
-    }
-
-    if is_global:
-        kwargs["weight"] = 2
-    elif is_pinned:
-        kwargs["weight"] = 1
-
-    try:
-        kwargs.update(
-            {
-                "starter": poster,
-                "starter_name": poster.username,
-                "starter_slug": poster.slug,
-                "last_poster": poster,
-                "last_poster_name": poster.username,
-                "last_poster_slug": poster.slug,
-            }
-        )
-    except AttributeError:
-        kwargs.update(
-            {
-                "starter_name": poster,
-                "starter_slug": slugify(poster),
-                "last_poster_name": poster,
-                "last_poster_slug": slugify(poster),
-            }
-        )
-
-    thread = Thread.objects.create(**kwargs)
-    reply_thread(
-        thread,
-        poster=poster,
-        posted_on=started_on,
-        is_hidden=is_hidden,
-        is_unapproved=is_unapproved,
-    )
-
-    return thread
-
-
-def reply_thread(
-    thread,
-    poster="Tester",
-    message="I am test message",
-    is_unapproved=False,
-    is_hidden=False,
-    is_event=False,
-    is_protected=False,
-    has_reports=False,
-    has_open_reports=False,
-    posted_on=None,
-):
-    posted_on = posted_on or thread.last_post_on + timedelta(minutes=5)
-
-    kwargs = {
-        "category": thread.category,
-        "thread": thread,
-        "original": message,
-        "parsed": message,
-        "checksum": "nope",
-        "posted_on": posted_on,
-        "updated_on": posted_on,
-        "is_event": is_event,
-        "is_unapproved": is_unapproved,
-        "is_hidden": is_hidden,
-        "is_protected": is_protected,
-        "has_reports": has_reports,
-        "has_open_reports": has_open_reports,
-    }
-
-    try:
-        kwargs.update({"poster": poster, "poster_name": poster.username})
-    except AttributeError:
-        kwargs.update({"poster_name": poster})
-
-    post = Post.objects.create(**kwargs)
-
-    update_post_checksum(post)
-    post.save()
-
-    thread.synchronize()
-    thread.save()
-    thread.category.synchronize()
-    thread.category.save()
-
-    return post
-
-
-def post_poll(thread, poster):
-    poll = Poll.objects.create(
-        category=thread.category,
-        thread=thread,
-        poster=poster,
-        poster_name=poster.username,
-        poster_slug=poster.slug,
-        question="Lorem ipsum dolor met?",
-        choices=[
-            {"hash": "aaaaaaaaaaaa", "label": "Alpha", "votes": 1},
-            {"hash": "bbbbbbbbbbbb", "label": "Beta", "votes": 0},
-            {"hash": "gggggggggggg", "label": "Gamma", "votes": 2},
-            {"hash": "dddddddddddd", "label": "Delta", "votes": 1},
-        ],
-        allowed_choices=2,
-        votes=4,
-    )
-
-    # one user voted for Alpha choice
-    try:
-        user = User.objects.get(slug="user")
-    except User.DoesNotExist:
-        user = create_test_user("User", "user@example.com")
-
-    poll.pollvote_set.create(
-        category=thread.category,
-        thread=thread,
-        voter=user,
-        voter_name=user.username,
-        voter_slug=user.slug,
-        choice_hash="aaaaaaaaaaaa",
-    )
-
-    # test user voted on third and last choices
-    poll.pollvote_set.create(
-        category=thread.category,
-        thread=thread,
-        voter=poster,
-        voter_name=poster.username,
-        voter_slug=poster.slug,
-        choice_hash="gggggggggggg",
-    )
-    poll.pollvote_set.create(
-        category=thread.category,
-        thread=thread,
-        voter=poster,
-        voter_name=poster.username,
-        voter_slug=poster.slug,
-        choice_hash="dddddddddddd",
-    )
-
-    # somebody else voted on third option before being deleted
-    poll.pollvote_set.create(
-        category=thread.category,
-        thread=thread,
-        voter_name="deleted",
-        voter_slug="deleted",
-        choice_hash="gggggggggggg",
-    )
-
-    return poll
-
-
-def like_post(post, liker=None, username=None):
-    if not post.last_likes:
-        post.last_likes = []
-
-    if liker:
-        like = post.postlike_set.create(
-            category=post.category,
-            thread=post.thread,
-            liker=liker,
-            liker_name=liker.username,
-            liker_slug=liker.slug,
-        )
-
-        post.last_likes = [
-            {"id": liker.id, "username": liker.username}
-        ] + post.last_likes
-    else:
-        like = post.postlike_set.create(
-            category=post.category,
-            thread=post.thread,
-            liker_name=username,
-            liker_slug=slugify(username),
-        )
-
-        post.last_likes = [{"id": None, "username": username}] + post.last_likes
-
-    post.likes += 1
-    post.save()
-
-    return like

+ 0 - 0
misago/users/testutils.py → misago/users/test.py


+ 1 - 1
misago/users/tests/test_activation_views.py

@@ -4,7 +4,7 @@ from django.urls import reverse
 
 from misago.core.utils import encode_json_html
 from misago.users.models import Ban
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 from misago.users.tokens import make_activation_token
 
 User = get_user_model()

+ 2 - 2
misago/users/tests/test_activepostersranking.py

@@ -3,12 +3,12 @@ from datetime import timedelta
 from django.utils import timezone
 
 from misago.categories.models import Category
-from misago.threads.testutils import post_thread
+from misago.threads.test import post_thread
 from misago.users.activepostersranking import (
     build_active_posters_ranking,
     get_active_posters_ranking,
 )
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class TestActivePostersRanking(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_audittrail.py

@@ -6,7 +6,7 @@ from django.utils import timezone
 from misago.users.audittrail import create_audit_trail, create_user_audit_trail
 from misago.users.models import AuditTrail
 from misago.users.signals import remove_old_ips
-from misago.users.testutils import UserTestCase, create_test_user
+from misago.users.test import UserTestCase, create_test_user
 
 User = get_user_model()
 

+ 1 - 1
misago/users/tests/test_auth_api.py

@@ -3,7 +3,7 @@ from django.core import mail
 from django.test import TestCase
 
 from misago.users.models import Ban
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 from misago.users.tokens import make_password_change_token
 
 

+ 1 - 1
misago/users/tests/test_auth_backend.py

@@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model
 from django.test import TestCase
 
 from misago.users.authbackends import MisagoBackend
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 backend = MisagoBackend()
 

+ 1 - 1
misago/users/tests/test_avatars.py

@@ -17,7 +17,7 @@ from misago.users.avatars import (
     uploaded,
 )
 from misago.users.models import Avatar, AvatarGallery
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class AvatarsStoreTests(TestCase):

+ 1 - 1
misago/users/tests/test_avatarserver_views.py

@@ -2,7 +2,7 @@ from django.test import TestCase
 from django.urls import reverse
 
 from misago.conf import settings
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class AvatarServerTests(TestCase):

+ 1 - 1
misago/users/tests/test_banadmin_views.py

@@ -2,7 +2,7 @@ from datetime import datetime, timedelta
 
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.users.models import Ban
 
 

+ 1 - 1
misago/users/tests/test_bans.py

@@ -15,7 +15,7 @@ from misago.users.bans import (
 )
 from misago.users.constants import BANS_CACHE
 from misago.users.models import Ban
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 cache_versions = get_cache_versions()
 

+ 1 - 1
misago/users/tests/test_bio_profilefield.py

@@ -1,7 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 
 
 User = get_user_model()

+ 1 - 1
misago/users/tests/test_credentialchange.py

@@ -1,7 +1,7 @@
 from django.test import TestCase
 
 from misago.users import credentialchange
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class MockRequest(object):

+ 2 - 2
misago/users/tests/test_datadownloads.py

@@ -4,7 +4,7 @@ from django.core.files import File
 
 from misago.categories.models import Category
 from misago.threads.models import Attachment, AttachmentType
-from misago.threads.testutils import post_thread, post_poll
+from misago.threads.test import post_thread, post_poll
 from misago.users.audittrail import create_user_audit_trail
 from misago.users.datadownloads import (
     expire_user_data_download,
@@ -13,7 +13,7 @@ from misago.users.datadownloads import (
     user_has_data_download_request,
 )
 from misago.users.models import DataDownload
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 TESTFILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "testfiles")

+ 1 - 1
misago/users/tests/test_datadownloads_dataarchive.py

@@ -11,7 +11,7 @@ from misago.users.datadownloads.dataarchive import (
     DataArchive,
     trim_long_filename,
 )
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 DATA_DOWNLOADS_WORKING_DIR = settings.MISAGO_USER_DATA_DOWNLOADS_WORKING_DIR

+ 2 - 2
misago/users/tests/test_datadownloadsadmin_views.py

@@ -3,10 +3,10 @@ import os
 from django.core.files import File
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.users.datadownloads import request_user_data_download
 from misago.users.models import DataDownload
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 TESTFILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "testfiles")
 TEST_FILE_PATH = os.path.join(TESTFILES_DIR, "avatar.png")

+ 1 - 1
misago/users/tests/test_decorators.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 
 from misago.core.utils import encode_json_html
 from misago.users.models import Ban
-from misago.users.testutils import UserTestCase
+from misago.users.test import UserTestCase
 
 
 class DenyAuthenticatedTests(UserTestCase):

+ 1 - 1
misago/users/tests/test_deleteinactiveusers.py

@@ -7,7 +7,7 @@ from django.test import TestCase, override_settings
 from django.utils import timezone
 
 from misago.users.management.commands import deleteinactiveusers
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 User = get_user_model()
 

+ 1 - 1
misago/users/tests/test_deletemarkedusers.py

@@ -5,7 +5,7 @@ from django.core.management import call_command
 from django.test import TestCase, override_settings
 
 from misago.users.management.commands import deletemarkedusers
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 User = get_user_model()
 

+ 1 - 1
misago/users/tests/test_deleteprofilefield.py

@@ -4,7 +4,7 @@ from django.core.management import call_command
 from django.test import TestCase
 
 from misago.users.management.commands import deleteprofilefield
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class DeleteProfileFieldTests(TestCase):

+ 1 - 1
misago/users/tests/test_djangoadmin_auth.py

@@ -1,7 +1,7 @@
 from django.test import override_settings
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 
 
 @override_settings(ROOT_URLCONF="misago.core.testproject.urls")

+ 2 - 2
misago/users/tests/test_djangoadmin_user.py

@@ -3,9 +3,9 @@ from django.test import override_settings
 from django.urls import reverse
 from django.utils import formats
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.users.djangoadmin import UserAdminModel
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 @override_settings(ROOT_URLCONF="misago.core.testproject.urls")

+ 1 - 1
misago/users/tests/test_expireuserdatadownloads.py

@@ -8,7 +8,7 @@ from django.core.management import call_command
 from misago.users.datadownloads import request_user_data_download
 from misago.users.management.commands import expireuserdatadownloads
 from misago.users.models import DataDownload
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 TESTFILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "testfiles")

+ 1 - 1
misago/users/tests/test_forgottenpassword_views.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 
 from misago.core.utils import encode_json_html
 from misago.users.models import Ban
-from misago.users.testutils import UserTestCase, create_test_user
+from misago.users.test import UserTestCase, create_test_user
 from misago.users.tokens import make_password_change_token
 
 

+ 1 - 1
misago/users/tests/test_gender_profilefield.py

@@ -1,7 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 
 
 User = get_user_model()

+ 1 - 1
misago/users/tests/test_getting_user_status.py

@@ -1,7 +1,7 @@
 from unittest.mock import Mock
 
 from misago.users.online.utils import get_user_status
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class GetUserStatusTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_invalidatebans.py

@@ -10,7 +10,7 @@ from misago.cache.versions import get_cache_versions
 from misago.users import bans
 from misago.users.management.commands import invalidatebans
 from misago.users.models import Ban, BanCache
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class InvalidateBansTests(TestCase):

+ 1 - 1
misago/users/tests/test_joinip_profilefield.py

@@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model
 from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 
 
 User = get_user_model()

+ 2 - 2
misago/users/tests/test_lists_views.py

@@ -2,10 +2,10 @@ from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
-from misago.threads.testutils import post_thread
+from misago.threads.test import post_thread
 from misago.users.activepostersranking import build_active_posters_ranking
 from misago.users.models import Rank
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class UsersListTestCase(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_listusedprofilefields.py

@@ -4,7 +4,7 @@ from django.core.management import call_command
 from django.test import TestCase
 
 from misago.users.management.commands import listusedprofilefields
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class ListUsedProfileFieldsTests(TestCase):

+ 1 - 1
misago/users/tests/test_mention_api.py

@@ -2,7 +2,7 @@ from django.test import TestCase
 from django.urls import reverse
 
 from misago.conf import settings
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 
 class AuthenticateApiTests(TestCase):

+ 1 - 1
misago/users/tests/test_options_views.py

@@ -1,7 +1,7 @@
 from django.core import mail
 from django.urls import reverse
 
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class OptionsViewsTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_prepareuserdatadownloads.py

@@ -7,7 +7,7 @@ from misago.conf import settings
 from misago.users.datadownloads import request_user_data_download
 from misago.users.management.commands import prepareuserdatadownloads
 from misago.users.models import DataDownload
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class PrepareUserDataDownloadsTests(AuthenticatedUserTestCase):

+ 8 - 8
misago/users/tests/test_profile_views.py

@@ -2,9 +2,9 @@ from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
-from misago.threads import testutils
+from misago.threads import test
 from misago.users.models import Ban
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class UserProfileViewsTests(AuthenticatedUserTestCase):
@@ -53,14 +53,14 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, "You have posted no messages")
 
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = test.post_thread(category=self.category, poster=self.user)
 
         response = self.client.get(link)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, thread.get_absolute_url())
 
-        post = testutils.reply_thread(thread, poster=self.user)
-        other_post = testutils.reply_thread(thread, poster=self.user)
+        post = test.reply_thread(thread, poster=self.user)
+        other_post = test.reply_thread(thread, poster=self.user)
 
         response = self.client.get(link)
         self.assertEqual(response.status_code, 200)
@@ -75,14 +75,14 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, "You have no started threads.")
 
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = test.post_thread(category=self.category, poster=self.user)
 
         response = self.client.get(link)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, thread.get_absolute_url())
 
-        post = testutils.reply_thread(thread, poster=self.user)
-        other_post = testutils.reply_thread(thread, poster=self.user)
+        post = test.reply_thread(thread, poster=self.user)
+        other_post = test.reply_thread(thread, poster=self.user)
 
         response = self.client.get(link)
         self.assertEqual(response.status_code, 200)

+ 1 - 1
misago/users/tests/test_rankadmin_views.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 
 from misago.acl import ACL_CACHE
 from misago.acl.models import Role
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.cache.test import assert_invalidates_cache
 from misago.users.models import Rank
 

+ 1 - 1
misago/users/tests/test_rest_permissions.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.users.models import Ban
-from misago.users.testutils import UserTestCase
+from misago.users.test import UserTestCase
 
 
 class UnbannedOnlyTests(UserTestCase):

+ 1 - 1
misago/users/tests/test_search.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class SearchApiTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_social_pipeline.py

@@ -24,7 +24,7 @@ from misago.users.social.pipeline import (
     validate_ip_not_banned,
     validate_user_not_banned,
 )
-from misago.users.testutils import UserTestCase
+from misago.users.test import UserTestCase
 
 
 User = get_user_model()

+ 1 - 1
misago/users/tests/test_testutils.py

@@ -1,6 +1,6 @@
 from django.urls import reverse
 
-from misago.users.testutils import (
+from misago.users.test import (
     AuthenticatedUserTestCase,
     SuperUserTestCase,
     UserTestCase,

+ 1 - 1
misago/users/tests/test_twitter_profilefield.py

@@ -1,7 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 
 
 User = get_user_model()

+ 1 - 1
misago/users/tests/test_user_avatar_api.py

@@ -9,7 +9,7 @@ from misago.conf import settings
 from misago.conf.test import override_dynamic_settings
 from misago.users.avatars import gallery, store
 from misago.users.models import AvatarGallery
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 TESTFILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "testfiles")
 TEST_AVATAR_PATH = os.path.join(TESTFILES_DIR, "avatar.png")

+ 1 - 1
misago/users/tests/test_user_changeemail_api.py

@@ -1,7 +1,7 @@
 from django.core import mail
 from django.urls import reverse
 
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class UserChangeEmailTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_user_changepassword_api.py

@@ -1,7 +1,7 @@
 from django.core import mail
 from django.urls import reverse
 
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class UserChangePasswordTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_user_create_api.py

@@ -5,7 +5,7 @@ from django.urls import reverse
 from misago.conf.test import override_dynamic_settings
 from misago.legal.models import Agreement
 from misago.users.models import Ban, Online
-from misago.users.testutils import UserTestCase
+from misago.users.test import UserTestCase
 
 User = get_user_model()
 

+ 1 - 1
misago/users/tests/test_user_datadownloads_api.py

@@ -1,5 +1,5 @@
 from misago.users.datadownloads import request_user_data_download
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class UserDataDownloadsApiTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_user_details_api.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class UserDetailsApiTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_user_editdetails_api.py

@@ -1,7 +1,7 @@
 from django.urls import reverse
 
 from misago.acl.test import patch_user_acl
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class UserEditDetailsApiTests(AuthenticatedUserTestCase):

+ 17 - 17
misago/users/tests/test_user_feeds_api.py

@@ -1,6 +1,6 @@
 from django.urls import reverse
 
-from misago.threads import testutils
+from misago.threads import test
 from misago.threads.tests.test_threads_api import ThreadsApiTestCase
 
 
@@ -30,7 +30,7 @@ class UserThreadsApiTests(ThreadsApiTestCase):
 
     def test_user_post(self):
         """user post doesn't show in feed because its not first post in thread"""
-        testutils.reply_thread(self.thread, poster=self.user)
+        test.reply_thread(self.thread, poster=self.user)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -38,7 +38,7 @@ class UserThreadsApiTests(ThreadsApiTestCase):
 
     def test_user_event(self):
         """events don't show in feeds at all"""
-        testutils.reply_thread(self.thread, poster=self.user, is_event=True)
+        test.reply_thread(self.thread, poster=self.user, is_event=True)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -46,10 +46,10 @@ class UserThreadsApiTests(ThreadsApiTestCase):
 
     def test_user_thread(self):
         """user thread shows in feed"""
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = test.post_thread(category=self.category, poster=self.user)
 
         # this post will not show in feed
-        testutils.reply_thread(thread, poster=self.user)
+        test.reply_thread(thread, poster=self.user)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -58,7 +58,7 @@ class UserThreadsApiTests(ThreadsApiTestCase):
 
     def test_user_thread_anonymous(self):
         """user thread shows in feed requested by unauthenticated user"""
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = test.post_thread(category=self.category, poster=self.user)
 
         self.logout_user()
 
@@ -94,7 +94,7 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_event(self):
         """events don't show in feeds at all"""
-        testutils.reply_thread(self.thread, poster=self.user, is_event=True)
+        test.reply_thread(self.thread, poster=self.user, is_event=True)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -102,7 +102,7 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_hidden_post(self):
         """hidden posts don't show in feeds at all"""
-        testutils.reply_thread(self.thread, poster=self.user, is_hidden=True)
+        test.reply_thread(self.thread, poster=self.user, is_hidden=True)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -110,7 +110,7 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_unapproved_post(self):
         """unapproved posts don't show in feeds at all"""
-        testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True)
+        test.reply_thread(self.thread, poster=self.user, is_unapproved=True)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -118,8 +118,8 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_posts(self):
         """user posts show in feed"""
-        post = testutils.reply_thread(self.thread, poster=self.user)
-        other_post = testutils.reply_thread(self.thread, poster=self.user)
+        post = test.reply_thread(self.thread, poster=self.user)
+        other_post = test.reply_thread(self.thread, poster=self.user)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -129,8 +129,8 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_thread(self):
         """user thread shows in feed"""
-        thread = testutils.post_thread(category=self.category, poster=self.user)
-        post = testutils.reply_thread(thread, poster=self.user)
+        thread = test.post_thread(category=self.category, poster=self.user)
+        post = test.reply_thread(thread, poster=self.user)
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -140,8 +140,8 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_post_anonymous(self):
         """user post shows in feed requested by unauthenticated user"""
-        post = testutils.reply_thread(self.thread, poster=self.user)
-        other_post = testutils.reply_thread(self.thread, poster=self.user)
+        post = test.reply_thread(self.thread, poster=self.user)
+        other_post = test.reply_thread(self.thread, poster=self.user)
 
         self.logout_user()
 
@@ -153,8 +153,8 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_thread_anonymous(self):
         """user thread shows in feed requested by unauthenticated user"""
-        thread = testutils.post_thread(category=self.category, poster=self.user)
-        post = testutils.reply_thread(thread, poster=self.user)
+        thread = test.post_thread(category=self.category, poster=self.user)
+        post = test.reply_thread(thread, poster=self.user)
 
         self.logout_user()
 

+ 1 - 1
misago/users/tests/test_user_middleware.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 
 from misago.users.bans import ban_ip, ban_user
 from misago.users.models import Ban
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class UserMiddlewareTest(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_user_requestdatadownload_api.py

@@ -1,7 +1,7 @@
 from django.test.utils import override_settings
 
 from misago.users.datadownloads import request_user_data_download
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class UserRequestDataDownload(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_user_signature_api.py

@@ -1,5 +1,5 @@
 from misago.acl.test import patch_user_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class UserSignatureTests(AuthenticatedUserTestCase):

+ 1 - 1
misago/users/tests/test_user_username_api.py

@@ -2,7 +2,7 @@ import json
 
 from misago.acl.test import patch_user_acl
 from misago.conf.test import override_dynamic_settings
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 
 class UserUsernameTests(AuthenticatedUserTestCase):

+ 3 - 3
misago/users/tests/test_useradmin_views.py

@@ -3,15 +3,15 @@ from django.core import mail
 from django.urls import reverse
 
 from misago.acl.models import Role
-from misago.admin.testutils import AdminTestCase
+from misago.admin.test import AdminTestCase
 from misago.categories.models import Category
 from misago.legal.models import Agreement
 from misago.legal.utils import save_user_agreement_acceptance
-from misago.threads.testutils import post_thread, reply_thread
+from misago.threads.test import post_thread, reply_thread
 
 from misago.users.datadownloads import request_user_data_download
 from misago.users.models import Ban, DataDownload, Rank
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 
 User = get_user_model()
 

+ 1 - 1
misago/users/tests/test_usernamechanges_api.py

@@ -1,5 +1,5 @@
 from misago.acl.test import patch_user_acl
-from misago.users.testutils import AuthenticatedUserTestCase
+from misago.users.test import AuthenticatedUserTestCase
 
 
 class UsernameChangesApiTests(AuthenticatedUserTestCase):

+ 2 - 2
misago/users/tests/test_users_api.py

@@ -9,10 +9,10 @@ from django.utils.encoding import smart_str
 from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.threads.models import Post, Thread
-from misago.threads.testutils import post_thread
+from misago.threads.test import post_thread
 from misago.users.activepostersranking import build_active_posters_ranking
 from misago.users.models import Ban, Rank
-from misago.users.testutils import AuthenticatedUserTestCase, create_test_user
+from misago.users.test import AuthenticatedUserTestCase, create_test_user
 
 User = get_user_model()
 

+ 1 - 1
misago/users/tests/test_validators.py

@@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
 from django.test import TestCase
 
 from misago.users.models import Ban
-from misago.users.testutils import create_test_user
+from misago.users.test import create_test_user
 from misago.users.validators import (
     validate_email,
     validate_email_available,