test_threads_api.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. from datetime import timedelta
  2. from django.utils import timezone
  3. from .. import test
  4. from ...categories import THREADS_ROOT_NAME
  5. from ...categories.models import Category
  6. from ...users.test import AuthenticatedUserTestCase
  7. from ..models import Thread
  8. from ..test import patch_category_acl
  9. from ..threadtypes import trees_map
  10. class ThreadsApiTestCase(AuthenticatedUserTestCase):
  11. def setUp(self):
  12. super().setUp()
  13. threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
  14. self.root = Category.objects.get(tree_id=threads_tree_id, level=0)
  15. self.category = Category.objects.get(slug="first-category")
  16. self.thread = test.post_thread(category=self.category)
  17. self.api_link = self.thread.get_api_url()
  18. def get_thread_json(self):
  19. response = self.client.get(self.thread.get_api_url())
  20. self.assertEqual(response.status_code, 200)
  21. return response.json()
  22. class ThreadRetrieveApiTests(ThreadsApiTestCase):
  23. def setUp(self):
  24. super().setUp()
  25. self.tested_links = [
  26. self.api_link,
  27. "%sposts/" % self.api_link,
  28. "%sposts/?page=1" % self.api_link,
  29. ]
  30. @patch_category_acl()
  31. def test_api_returns_thread(self):
  32. """api has no showstoppers"""
  33. for link in self.tested_links:
  34. response = self.client.get(link)
  35. self.assertEqual(response.status_code, 200)
  36. response_json = response.json()
  37. self.assertEqual(response_json["id"], self.thread.pk)
  38. self.assertEqual(response_json["title"], self.thread.title)
  39. if "posts" in link:
  40. self.assertIn("post_set", response_json)
  41. @patch_category_acl({"can_see_all_threads": False})
  42. def test_api_shows_owned_thread(self):
  43. """api handles "owned threads only"""
  44. for link in self.tested_links:
  45. response = self.client.get(link)
  46. self.assertEqual(response.status_code, 404)
  47. self.thread.starter = self.user
  48. self.thread.save()
  49. for link in self.tested_links:
  50. response = self.client.get(link)
  51. self.assertEqual(response.status_code, 200)
  52. @patch_category_acl({"can_see": False})
  53. def test_api_validates_category_see_permission(self):
  54. """api validates category visiblity"""
  55. for link in self.tested_links:
  56. response = self.client.get(link)
  57. self.assertEqual(response.status_code, 404)
  58. @patch_category_acl({"can_browse": False})
  59. def test_api_validates_category_browse_permission(self):
  60. """api validates category browsability"""
  61. for link in self.tested_links:
  62. response = self.client.get(link)
  63. self.assertEqual(response.status_code, 404)
  64. def test_api_validates_posts_visibility(self):
  65. """api validates posts visiblity"""
  66. hidden_post = test.reply_thread(
  67. self.thread, is_hidden=True, message="I'am hidden test message!"
  68. )
  69. with patch_category_acl({"can_hide_posts": 0}):
  70. response = self.client.get(self.tested_links[1])
  71. self.assertNotContains(
  72. response, hidden_post.parsed
  73. ) # post's body is hidden
  74. # add permission to see hidden posts
  75. with patch_category_acl({"can_hide_posts": 1}):
  76. response = self.client.get(self.tested_links[1])
  77. self.assertContains(
  78. response, hidden_post.parsed
  79. ) # hidden post's body is visible with permission
  80. # unapproved posts shouldn't show at all
  81. unapproved_post = test.reply_thread(self.thread, is_unapproved=True)
  82. with patch_category_acl({"can_approve_content": False}):
  83. response = self.client.get(self.tested_links[1])
  84. self.assertNotContains(response, unapproved_post.get_absolute_url())
  85. # add permission to see unapproved posts
  86. with patch_category_acl({"can_approve_content": True}):
  87. response = self.client.get(self.tested_links[1])
  88. self.assertContains(response, unapproved_post.get_absolute_url())
  89. def test_api_validates_has_unapproved_posts_visibility(self):
  90. """api checks acl before exposing unapproved flag"""
  91. self.thread.has_unapproved_posts = True
  92. self.thread.save()
  93. with patch_category_acl({"can_approve_content": False}):
  94. for link in self.tested_links:
  95. response = self.client.get(link)
  96. self.assertEqual(response.status_code, 200)
  97. response_json = response.json()
  98. self.assertEqual(response_json["id"], self.thread.pk)
  99. self.assertEqual(response_json["title"], self.thread.title)
  100. self.assertFalse(response_json["has_unapproved_posts"])
  101. with patch_category_acl({"can_approve_content": True}):
  102. for link in self.tested_links:
  103. response = self.client.get(link)
  104. self.assertEqual(response.status_code, 200)
  105. response_json = response.json()
  106. self.assertEqual(response_json["id"], self.thread.pk)
  107. self.assertEqual(response_json["title"], self.thread.title)
  108. self.assertTrue(response_json["has_unapproved_posts"])
  109. class ThreadDeleteApiTests(ThreadsApiTestCase):
  110. def setUp(self):
  111. super().setUp()
  112. self.last_thread = test.post_thread(category=self.category)
  113. self.api_link = self.last_thread.get_api_url()
  114. def test_delete_thread_no_permission(self):
  115. """api tests permission to delete threads"""
  116. with patch_category_acl({"can_hide_threads": 0}):
  117. response = self.client.delete(self.api_link)
  118. self.assertEqual(response.status_code, 403)
  119. self.assertEqual(
  120. response.json()["detail"], "You can't delete threads in this category."
  121. )
  122. with patch_category_acl({"can_hide_threads": 1}):
  123. response = self.client.delete(self.api_link)
  124. self.assertEqual(response.status_code, 403)
  125. self.assertEqual(
  126. response.json()["detail"], "You can't delete threads in this category."
  127. )
  128. @patch_category_acl({"can_hide_threads": 1, "can_hide_own_threads": 2})
  129. def test_delete_other_user_thread_no_permission(self):
  130. """api tests thread owner when deleting own thread"""
  131. response = self.client.delete(self.api_link)
  132. self.assertEqual(response.status_code, 403)
  133. self.assertEqual(
  134. response.json()["detail"],
  135. "You can't delete other users theads in this category.",
  136. )
  137. @patch_category_acl(
  138. {"can_hide_threads": 2, "can_hide_own_threads": 2, "can_close_threads": False}
  139. )
  140. def test_delete_thread_closed_category_no_permission(self):
  141. """api tests category's closed state"""
  142. self.category.is_closed = True
  143. self.category.save()
  144. response = self.client.delete(self.api_link)
  145. self.assertEqual(response.status_code, 403)
  146. self.assertEqual(
  147. response.json()["detail"],
  148. "This category is closed. You can't delete threads in it.",
  149. )
  150. @patch_category_acl(
  151. {"can_hide_threads": 2, "can_hide_own_threads": 2, "can_close_threads": False}
  152. )
  153. def test_delete_thread_closed_no_permission(self):
  154. """api tests thread's closed state"""
  155. self.last_thread.is_closed = True
  156. self.last_thread.save()
  157. response = self.client.delete(self.api_link)
  158. self.assertEqual(response.status_code, 403)
  159. self.assertEqual(
  160. response.json()["detail"], "This thread is closed. You can't delete it."
  161. )
  162. @patch_category_acl(
  163. {"can_hide_threads": 1, "can_hide_own_threads": 2, "thread_edit_time": 1}
  164. )
  165. def test_delete_owned_thread_no_time(self):
  166. """api tests permission to delete owned thread within time limit"""
  167. self.last_thread.starter = self.user
  168. self.last_thread.started_on = timezone.now() - timedelta(minutes=10)
  169. self.last_thread.save()
  170. response = self.client.delete(self.api_link)
  171. self.assertEqual(response.status_code, 403)
  172. self.assertEqual(
  173. response.json()["detail"],
  174. "You can't delete threads that are older than 1 minute.",
  175. )
  176. @patch_category_acl({"can_hide_threads": 2})
  177. def test_delete_thread(self):
  178. """DELETE to API link with permission deletes thread"""
  179. category = Category.objects.get(slug="first-category")
  180. self.assertEqual(category.last_thread_id, self.last_thread.pk)
  181. response = self.client.delete(self.api_link)
  182. self.assertEqual(response.status_code, 200)
  183. with self.assertRaises(Thread.DoesNotExist):
  184. Thread.objects.get(pk=self.last_thread.pk)
  185. # category was synchronised after deletion
  186. category = Category.objects.get(slug="first-category")
  187. self.assertEqual(category.last_thread_id, self.thread.pk)
  188. # test that last thread's deletion triggers category sync
  189. response = self.client.delete(self.thread.get_api_url())
  190. self.assertEqual(response.status_code, 200)
  191. with self.assertRaises(Thread.DoesNotExist):
  192. Thread.objects.get(pk=self.thread.pk)
  193. category = Category.objects.get(slug="first-category")
  194. self.assertIsNone(category.last_thread_id)
  195. @patch_category_acl(
  196. {"can_hide_threads": 1, "can_hide_own_threads": 2, "thread_edit_time": 30}
  197. )
  198. def test_delete_owned_thread(self):
  199. """api lets owner to delete owned thread within time limit"""
  200. self.last_thread.starter = self.user
  201. self.last_thread.started_on = timezone.now() - timedelta(minutes=10)
  202. self.last_thread.save()
  203. response = self.client.delete(self.api_link)
  204. self.assertEqual(response.status_code, 200)