test_threads_bulkdelete_api.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import json
  2. from django.urls import reverse
  3. from misago.categories import PRIVATE_THREADS_ROOT_NAME
  4. from misago.categories.models import Category
  5. from misago.threads import test
  6. from misago.threads.models import Thread
  7. from misago.threads.serializers.moderation import THREADS_LIMIT
  8. from misago.threads.test import patch_category_acl
  9. from misago.threads.threadtypes import trees_map
  10. from .test_threads_api import ThreadsApiTestCase
  11. class ThreadsBulkDeleteApiTests(ThreadsApiTestCase):
  12. def setUp(self):
  13. super().setUp()
  14. self.api_link = reverse("misago:api:thread-list")
  15. self.threads = [
  16. test.post_thread(category=self.category, poster=self.user),
  17. test.post_thread(category=self.category),
  18. test.post_thread(category=self.category, poster=self.user),
  19. ]
  20. def delete(self, url, data=None):
  21. return self.client.delete(
  22. url, json.dumps(data), content_type="application/json"
  23. )
  24. def test_delete_anonymous(self):
  25. """anonymous users can't bulk delete threads"""
  26. self.logout_user()
  27. response = self.delete(self.api_link)
  28. self.assertEqual(response.status_code, 403)
  29. self.assertEqual(
  30. response.json(), {"detail": "This action is not available to guests."}
  31. )
  32. @patch_category_acl({"can_hide_threads": 2, "can_hide_own_threads": 2})
  33. def test_delete_no_ids(self):
  34. """api requires ids to delete"""
  35. response = self.delete(self.api_link)
  36. self.assertEqual(response.status_code, 403)
  37. self.assertEqual(
  38. response.json(),
  39. {"detail": "You have to specify at least one thread to delete."},
  40. )
  41. @patch_category_acl({"can_hide_threads": 2, "can_hide_own_threads": 2})
  42. def test_validate_ids(self):
  43. response = self.delete(self.api_link, True)
  44. self.assertEqual(response.status_code, 403)
  45. self.assertEqual(
  46. response.json(), {"detail": 'Expected a list of items but got type "bool".'}
  47. )
  48. response = self.delete(self.api_link, "abbss")
  49. self.assertEqual(response.status_code, 403)
  50. self.assertEqual(
  51. response.json(), {"detail": 'Expected a list of items but got type "str".'}
  52. )
  53. response = self.delete(self.api_link, [1, 2, 3, "a", "b", "x"])
  54. self.assertEqual(response.status_code, 403)
  55. self.assertEqual(
  56. response.json(), {"detail": "One or more thread ids received were invalid."}
  57. )
  58. @patch_category_acl({"can_hide_threads": 2, "can_hide_own_threads": 2})
  59. def test_validate_ids_length(self):
  60. """api validates that ids are list of ints"""
  61. response = self.delete(self.api_link, list(range(THREADS_LIMIT + 1)))
  62. self.assertEqual(response.status_code, 403)
  63. self.assertEqual(
  64. response.json(),
  65. {
  66. "detail": "No more than %s threads can be deleted at single time."
  67. % THREADS_LIMIT
  68. },
  69. )
  70. @patch_category_acl({"can_hide_threads": 2, "can_hide_own_threads": 2})
  71. def test_validate_thread_visibility(self):
  72. """api valdiates if user can see deleted thread"""
  73. unapproved_thread = self.threads[1]
  74. unapproved_thread.is_unapproved = True
  75. unapproved_thread.save()
  76. threads_ids = [p.id for p in self.threads]
  77. response = self.delete(self.api_link, threads_ids)
  78. self.assertEqual(response.status_code, 403)
  79. self.assertEqual(
  80. response.json(),
  81. {"detail": "One or more threads to delete could not be found."},
  82. )
  83. # no thread was deleted
  84. for thread in self.threads:
  85. Thread.objects.get(pk=thread.pk)
  86. @patch_category_acl({"can_hide_threads": 0, "can_hide_own_threads": 2})
  87. def test_delete_other_user_thread_no_permission(self):
  88. """api valdiates if user can delete other users threads"""
  89. other_thread = self.threads[1]
  90. response = self.delete(self.api_link, [p.id for p in self.threads])
  91. self.assertEqual(response.status_code, 400)
  92. self.assertEqual(
  93. response.json(),
  94. [
  95. {
  96. "thread": {"id": other_thread.pk, "title": other_thread.title},
  97. "error": "You can't delete other users theads in this category.",
  98. }
  99. ],
  100. )
  101. # no threads are removed on failed attempt
  102. for thread in self.threads:
  103. Thread.objects.get(pk=thread.pk)
  104. @patch_category_acl(
  105. {"can_hide_threads": 2, "can_hide_own_threads": 2, "can_close_threads": False}
  106. )
  107. def test_delete_thread_closed_category_no_permission(self):
  108. """api tests category's closed state"""
  109. self.category.is_closed = True
  110. self.category.save()
  111. response = self.delete(self.api_link, [p.id for p in self.threads])
  112. self.assertEqual(response.status_code, 400)
  113. self.assertEqual(
  114. response.json(),
  115. [
  116. {
  117. "thread": {"id": thread.pk, "title": thread.title},
  118. "error": "This category is closed. You can't delete threads in it.",
  119. }
  120. for thread in sorted(self.threads, key=lambda i: i.pk)
  121. ],
  122. )
  123. @patch_category_acl(
  124. {"can_hide_threads": 2, "can_hide_own_threads": 2, "can_close_threads": False}
  125. )
  126. def test_delete_thread_closed_no_permission(self):
  127. """api tests thread's closed state"""
  128. closed_thread = self.threads[1]
  129. closed_thread.is_closed = True
  130. closed_thread.save()
  131. response = self.delete(self.api_link, [p.id for p in self.threads])
  132. self.assertEqual(response.status_code, 400)
  133. self.assertEqual(
  134. response.json(),
  135. [
  136. {
  137. "thread": {"id": closed_thread.pk, "title": closed_thread.title},
  138. "error": "This thread is closed. You can't delete it.",
  139. }
  140. ],
  141. )
  142. @patch_category_acl({"can_hide_threads": 2, "can_hide_own_threads": 2})
  143. def test_delete_private_thread(self):
  144. """attempt to delete private thread fails"""
  145. private_thread = self.threads[0]
  146. private_thread.category = Category.objects.get(
  147. tree_id=trees_map.get_tree_id_for_root(PRIVATE_THREADS_ROOT_NAME)
  148. )
  149. private_thread.save()
  150. private_thread.threadparticipant_set.create(user=self.user, is_owner=True)
  151. threads_ids = [p.id for p in self.threads]
  152. response = self.delete(self.api_link, threads_ids)
  153. self.assertEqual(response.status_code, 403)
  154. self.assertEqual(
  155. response.json(),
  156. {"detail": "One or more threads to delete could not be found."},
  157. )
  158. Thread.objects.get(pk=private_thread.pk)