test_threads_bulkdelete_api.py 7.9 KB

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