test_thread_postbulkdelete_api.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import json
  2. from datetime import timedelta
  3. from django.urls import reverse
  4. from django.utils import timezone
  5. from misago.threads import testutils
  6. from misago.threads.models import Post, Thread
  7. from .test_threads_api import ThreadsApiTestCase
  8. class PostBulkDeleteApiTests(ThreadsApiTestCase):
  9. def setUp(self):
  10. super().setUp()
  11. self.posts = [
  12. testutils.reply_thread(self.thread, poster=self.user),
  13. testutils.reply_thread(self.thread),
  14. testutils.reply_thread(self.thread, poster=self.user),
  15. ]
  16. self.api_link = reverse(
  17. 'misago:api:thread-post-list',
  18. kwargs={
  19. 'thread_pk': self.thread.pk,
  20. }
  21. )
  22. def delete(self, url, data=None):
  23. return self.client.delete(url, json.dumps(data), content_type="application/json")
  24. def test_delete_anonymous(self):
  25. """api validates if deleting user is authenticated"""
  26. self.logout_user()
  27. response = self.delete(self.api_link)
  28. self.assertContains(response, "This action is not available to guests.", status_code=403)
  29. def test_delete_no_data(self):
  30. """api handles empty data"""
  31. response = self.client.delete(self.api_link, content_type="application/json")
  32. self.assertContains(response, "Expected a list of items", status_code=400)
  33. def test_delete_no_ids(self):
  34. """api requires ids to delete"""
  35. response = self.delete(self.api_link)
  36. self.assertContains(response, "You have to specify at least one post to delete.", status_code=400)
  37. def test_delete_empty_ids(self):
  38. """api requires ids to delete"""
  39. response = self.delete(self.api_link, [])
  40. self.assertContains(response, "You have to specify at least one post to delete.", status_code=400)
  41. def test_validate_ids(self):
  42. """api validates that ids are list of ints"""
  43. self.override_acl({
  44. 'can_hide_own_posts': 2,
  45. 'can_hide_posts': 2,
  46. })
  47. response = self.delete(self.api_link, True)
  48. self.assertContains(response, "Expected a list of items", status_code=400)
  49. response = self.delete(self.api_link, 'abbss')
  50. self.assertContains(response, "Expected a list of items", status_code=400)
  51. response = self.delete(self.api_link, [1, 2, 3, 'a', 'b', 'x'])
  52. self.assertContains(response, "One or more post ids received were invalid.", status_code=400)
  53. def test_validate_ids_length(self):
  54. """api validates that ids are list of ints"""
  55. self.override_acl({
  56. 'can_hide_own_posts': 2,
  57. 'can_hide_posts': 2,
  58. })
  59. response = self.delete(self.api_link, list(range(100)))
  60. self.assertContains(response, "No more than 24 posts can be deleted at single time.", status_code=400)
  61. def test_validate_posts_exist(self):
  62. """api validates that ids are visible posts"""
  63. self.override_acl({
  64. 'can_hide_own_posts': 2,
  65. 'can_hide_posts': 0,
  66. })
  67. response = self.delete(self.api_link, [p.id * 10 for p in self.posts])
  68. self.assertContains(response, "One or more posts to delete could not be found.", status_code=403)
  69. def test_validate_posts_visibility(self):
  70. """api validates that ids are visible posts"""
  71. self.override_acl({
  72. 'can_hide_own_posts': 2,
  73. 'can_hide_posts': 0,
  74. })
  75. self.posts[1].is_unapproved = True
  76. self.posts[1].save()
  77. response = self.delete(self.api_link, [p.id for p in self.posts])
  78. self.assertContains(response, "One or more posts to delete could not be found.", status_code=403)
  79. def test_validate_posts_same_thread(self):
  80. """api validates that ids are same thread posts"""
  81. self.override_acl({
  82. 'can_hide_own_posts': 2,
  83. 'can_hide_posts': 2,
  84. })
  85. other_thread = testutils.post_thread(category=self.category)
  86. self.posts.append(testutils.reply_thread(other_thread, poster=self.user))
  87. response = self.delete(self.api_link, [p.id for p in self.posts])
  88. self.assertContains(response, "One or more posts to delete could not be found.", status_code=403)
  89. def test_no_permission(self):
  90. """api validates permission to delete"""
  91. self.override_acl({
  92. 'can_hide_own_posts': 1,
  93. 'can_hide_posts': 1,
  94. })
  95. response = self.delete(self.api_link, [p.id for p in self.posts])
  96. self.assertContains(response, "You can't delete posts in this category.", status_code=403)
  97. def test_delete_other_user_post_no_permission(self):
  98. """api valdiates if user can delete other users posts"""
  99. self.override_acl({
  100. 'post_edit_time': 0,
  101. 'can_hide_own_posts': 2,
  102. 'can_hide_posts': 0,
  103. })
  104. response = self.delete(self.api_link, [p.id for p in self.posts])
  105. self.assertContains(
  106. response, "You can't delete other users posts in this category", status_code=403
  107. )
  108. def test_delete_protected_post_no_permission(self):
  109. """api validates if user can delete protected post"""
  110. self.override_acl({
  111. 'can_protect_posts': 0,
  112. 'can_hide_own_posts': 2,
  113. 'can_hide_posts': 0,
  114. })
  115. self.posts[0].is_protected = True
  116. self.posts[0].save()
  117. response = self.delete(self.api_link, [p.id for p in self.posts])
  118. self.assertContains(
  119. response, "This post is protected. You can't delete it.", status_code=403
  120. )
  121. def test_delete_protected_post_after_edit_time(self):
  122. """api validates if user can delete delete post after edit time"""
  123. self.override_acl({
  124. 'post_edit_time': 1,
  125. 'can_hide_own_posts': 2,
  126. 'can_hide_posts': 0,
  127. })
  128. self.posts[0].posted_on = timezone.now() - timedelta(minutes=10)
  129. self.posts[0].save()
  130. response = self.delete(self.api_link, [p.id for p in self.posts])
  131. self.assertContains(
  132. response, "You can't delete posts that are older than 1 minute.", status_code=403
  133. )
  134. def test_delete_post_closed_thread_no_permission(self):
  135. """api valdiates if user can delete posts in closed threads"""
  136. self.override_acl({
  137. 'can_hide_own_posts': 2,
  138. 'can_hide_posts': 0,
  139. })
  140. self.thread.is_closed = True
  141. self.thread.save()
  142. response = self.delete(self.api_link, [p.id for p in self.posts])
  143. self.assertContains(
  144. response, "This thread is closed. You can't delete posts in it.", status_code=403
  145. )
  146. def test_delete_post_closed_category_no_permission(self):
  147. """api valdiates if user can delete posts in closed categories"""
  148. self.override_acl({
  149. 'can_hide_own_posts': 2,
  150. 'can_hide_posts': 0,
  151. })
  152. self.category.is_closed = True
  153. self.category.save()
  154. response = self.delete(self.api_link, [p.id for p in self.posts])
  155. self.assertContains(
  156. response, "This category is closed. You can't delete posts in it.", status_code=403
  157. )
  158. def test_delete_first_post(self):
  159. """api disallows first post's deletion"""
  160. self.override_acl({
  161. 'can_hide_own_posts': 2,
  162. 'can_hide_posts': 2,
  163. })
  164. ids = [p.id for p in self.posts]
  165. ids.append(self.thread.first_post_id)
  166. response = self.delete(self.api_link, ids)
  167. self.assertContains(response, "You can't delete thread's first post.", status_code=403)
  168. def test_delete_best_answer(self):
  169. """api disallows best answer deletion"""
  170. self.override_acl({'can_hide_own_posts': 2, 'can_hide_posts': 2})
  171. self.thread.set_best_answer(self.user, self.posts[0])
  172. self.thread.save()
  173. response = self.delete(self.api_link, [p.id for p in self.posts])
  174. self.assertEqual(response.status_code, 403)
  175. self.assertEqual(response.json(), {
  176. 'detail': "You can't delete this post because its marked as best answer.",
  177. })
  178. def test_delete_event(self):
  179. """api differs posts from events"""
  180. self.override_acl({
  181. 'can_hide_own_posts': 2,
  182. 'can_hide_posts': 2,
  183. 'can_hide_events': 0,
  184. })
  185. self.posts[1].is_event = True
  186. self.posts[1].save()
  187. response = self.delete(self.api_link, [p.id for p in self.posts])
  188. self.assertContains(response, "You can't delete events in this category.", status_code=403)
  189. def test_delete_owned_posts(self):
  190. """api deletes owned thread posts"""
  191. self.override_acl({
  192. 'post_edit_time': 0,
  193. 'can_hide_own_posts': 2,
  194. 'can_hide_posts': 0,
  195. })
  196. ids = [self.posts[0].id, self.posts[-1].id]
  197. response = self.delete(self.api_link, ids)
  198. self.thread = Thread.objects.get(pk=self.thread.pk)
  199. self.assertNotEqual(self.thread.last_post_id, ids[-1])
  200. for post in ids:
  201. with self.assertRaises(Post.DoesNotExist):
  202. self.thread.post_set.get(pk=post)
  203. def test_delete_posts(self):
  204. """api deletes thread posts"""
  205. self.override_acl({
  206. 'can_hide_own_posts': 0,
  207. 'can_hide_posts': 2,
  208. })
  209. response = self.delete(self.api_link, [p.id for p in self.posts])
  210. self.assertEqual(response.status_code, 200)
  211. self.thread = Thread.objects.get(pk=self.thread.pk)
  212. self.assertNotEqual(self.thread.last_post_id, self.posts[-1].pk)
  213. for post in self.posts:
  214. with self.assertRaises(Post.DoesNotExist):
  215. self.thread.post_set.get(pk=post.pk)