test_threads_bulkdelete_api.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import json
  2. from django.urls import reverse
  3. from .. import test
  4. from ...categories import PRIVATE_THREADS_ROOT_NAME
  5. from ...categories.models import Category
  6. from ...conf.test import override_dynamic_settings
  7. from ..models import Thread
  8. from ..test import patch_category_acl
  9. from ..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. @override_dynamic_settings(threads_per_page=4)
  59. @patch_category_acl({"can_hide_threads": 2, "can_hide_own_threads": 2})
  60. def test_validate_ids_length(self):
  61. """api validates that ids are list of ints"""
  62. response = self.delete(self.api_link, list(range(5)))
  63. self.assertEqual(response.status_code, 403)
  64. self.assertEqual(
  65. response.json(),
  66. {"detail": "No more than 4 threads can be deleted at a single time."},
  67. )
  68. @patch_category_acl({"can_hide_threads": 2, "can_hide_own_threads": 2})
  69. def test_validate_thread_visibility(self):
  70. """api valdiates if user can see deleted thread"""
  71. unapproved_thread = self.threads[1]
  72. unapproved_thread.is_unapproved = True
  73. unapproved_thread.save()
  74. threads_ids = [p.id for p in self.threads]
  75. response = self.delete(self.api_link, threads_ids)
  76. self.assertEqual(response.status_code, 403)
  77. self.assertEqual(
  78. 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(
  91. response.json(),
  92. [
  93. {
  94. "thread": {"id": other_thread.pk, "title": other_thread.title},
  95. "error": "You can't delete other users theads in this category.",
  96. }
  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, "can_hide_own_threads": 2, "can_close_threads": False}
  104. )
  105. def test_delete_thread_closed_category_no_permission(self):
  106. """api tests category's closed state"""
  107. self.category.is_closed = True
  108. self.category.save()
  109. response = self.delete(self.api_link, [p.id for p in self.threads])
  110. self.assertEqual(response.status_code, 400)
  111. self.assertEqual(
  112. response.json(),
  113. [
  114. {
  115. "thread": {"id": thread.pk, "title": thread.title},
  116. "error": "This category is closed. You can't delete threads in it.",
  117. }
  118. for thread in sorted(self.threads, key=lambda i: i.pk)
  119. ],
  120. )
  121. @patch_category_acl(
  122. {"can_hide_threads": 2, "can_hide_own_threads": 2, "can_close_threads": False}
  123. )
  124. def test_delete_thread_closed_no_permission(self):
  125. """api tests thread's closed state"""
  126. closed_thread = self.threads[1]
  127. closed_thread.is_closed = True
  128. closed_thread.save()
  129. response = self.delete(self.api_link, [p.id for p in self.threads])
  130. self.assertEqual(response.status_code, 400)
  131. self.assertEqual(
  132. response.json(),
  133. [
  134. {
  135. "thread": {"id": closed_thread.pk, "title": closed_thread.title},
  136. "error": "This thread is closed. You can't delete it.",
  137. }
  138. ],
  139. )
  140. @patch_category_acl({"can_hide_threads": 2, "can_hide_own_threads": 2})
  141. def test_delete_private_thread(self):
  142. """attempt to delete private thread fails"""
  143. private_thread = self.threads[0]
  144. private_thread.category = Category.objects.get(
  145. tree_id=trees_map.get_tree_id_for_root(PRIVATE_THREADS_ROOT_NAME)
  146. )
  147. private_thread.save()
  148. private_thread.threadparticipant_set.create(user=self.user, is_owner=True)
  149. threads_ids = [p.id for p in self.threads]
  150. response = self.delete(self.api_link, threads_ids)
  151. self.assertEqual(response.status_code, 403)
  152. self.assertEqual(
  153. response.json(),
  154. {"detail": "One or more threads to delete could not be found."},
  155. )
  156. Thread.objects.get(pk=private_thread.pk)