test_threads_bulkdelete_api.py 6.8 KB

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