test_thread_merge_api.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. from django.urls import reverse
  2. from misago.acl.testutils import override_acl
  3. from misago.categories.models import Category
  4. from misago.threads import testutils
  5. from misago.threads.models import Poll, PollVote, Thread
  6. from .test_threads_api import ThreadsApiTestCase
  7. class ThreadMergeApiTests(ThreadsApiTestCase):
  8. def setUp(self):
  9. super(ThreadMergeApiTests, self).setUp()
  10. Category(
  11. name='Category B',
  12. slug='category-b',
  13. ).insert_at(
  14. self.category, position='last-child', save=True
  15. )
  16. self.category_b = Category.objects.get(slug='category-b')
  17. self.api_link = reverse('misago:api:thread-merge', kwargs={'pk': self.thread.pk})
  18. def override_other_acl(self, acl=None):
  19. other_category_acl = self.user.acl_cache['categories'][self.category.pk].copy()
  20. other_category_acl.update({
  21. 'can_see': 1,
  22. 'can_browse': 1,
  23. 'can_see_all_threads': 1,
  24. 'can_see_own_threads': 0,
  25. 'can_hide_threads': 0,
  26. 'can_approve_content': 0,
  27. 'can_edit_posts': 0,
  28. 'can_hide_posts': 0,
  29. 'can_hide_own_posts': 0,
  30. 'can_merge_threads': 0
  31. })
  32. if acl:
  33. other_category_acl.update(acl)
  34. categories_acl = self.user.acl_cache['categories']
  35. categories_acl[self.category_b.pk] = other_category_acl
  36. visible_categories = [self.category.pk]
  37. if other_category_acl['can_see']:
  38. visible_categories.append(self.category_b.pk)
  39. override_acl(
  40. self.user, {
  41. 'visible_categories': visible_categories,
  42. 'categories': categories_acl,
  43. }
  44. )
  45. def test_merge_no_permission(self):
  46. """api validates if thread can be merged with other one"""
  47. self.override_acl({'can_merge_threads': 0})
  48. response = self.client.post(self.api_link)
  49. self.assertContains(
  50. response,
  51. "You don't have permission to merge this thread with others.",
  52. status_code=403
  53. )
  54. def test_merge_no_url(self):
  55. """api validates if thread url was given"""
  56. self.override_acl({'can_merge_threads': 1})
  57. response = self.client.post(self.api_link)
  58. self.assertContains(response, "This is not a valid thread link.", status_code=400)
  59. def test_invalid_url(self):
  60. """api validates thread url"""
  61. self.override_acl({'can_merge_threads': 1})
  62. response = self.client.post(self.api_link, {'thread_url': self.user.get_absolute_url()})
  63. self.assertContains(response, "This is not a valid thread link.", status_code=400)
  64. def test_current_thread_url(self):
  65. """api validates if thread url given is to current thread"""
  66. self.override_acl({'can_merge_threads': 1})
  67. response = self.client.post(self.api_link, {'thread_url': self.thread.get_absolute_url()})
  68. self.assertContains(response, "You can't merge thread with itself.", status_code=400)
  69. def test_other_thread_exists(self):
  70. """api validates if other thread exists"""
  71. self.override_acl({'can_merge_threads': 1})
  72. self.override_other_acl()
  73. other_thread = testutils.post_thread(self.category_b)
  74. other_thread_url = other_thread.get_absolute_url()
  75. other_thread.delete()
  76. response = self.client.post(self.api_link, {'thread_url': other_thread_url})
  77. self.assertContains(
  78. response, "The thread you have entered link to doesn't exist", status_code=400
  79. )
  80. def test_other_thread_is_invisible(self):
  81. """api validates if other thread is visible"""
  82. self.override_acl({'can_merge_threads': 1})
  83. self.override_other_acl({'can_see': 0})
  84. other_thread = testutils.post_thread(self.category_b)
  85. response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
  86. self.assertContains(
  87. response, "The thread you have entered link to doesn't exist", status_code=400
  88. )
  89. def test_other_thread_isnt_mergeable(self):
  90. """api validates if other thread can be merged"""
  91. self.override_acl({'can_merge_threads': 1})
  92. self.override_other_acl({'can_merge_threads': 0})
  93. other_thread = testutils.post_thread(self.category_b)
  94. response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
  95. self.assertContains(
  96. response, "You don't have permission to merge this thread", status_code=400
  97. )
  98. def test_other_thread_isnt_replyable(self):
  99. """api validates if other thread can be replied, which is condition for merg"""
  100. self.override_acl({'can_merge_threads': 1})
  101. self.override_other_acl({'can_reply_threads': 0})
  102. other_thread = testutils.post_thread(self.category_b)
  103. response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
  104. self.assertContains(
  105. response, "You can't merge this thread into thread you can't reply.", status_code=400
  106. )
  107. def test_merge_threads(self):
  108. """api merges two threads successfully"""
  109. self.override_acl({'can_merge_threads': 1})
  110. self.override_other_acl({'can_merge_threads': 1})
  111. other_thread = testutils.post_thread(self.category_b)
  112. response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
  113. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  114. # other thread has two posts now
  115. self.assertEqual(other_thread.post_set.count(), 3)
  116. # first thread is gone
  117. with self.assertRaises(Thread.DoesNotExist):
  118. Thread.objects.get(pk=self.thread.pk)
  119. def test_merge_threads_kept_poll(self):
  120. """api merges two threads successfully, keeping poll from old thread"""
  121. self.override_acl({'can_merge_threads': 1})
  122. self.override_other_acl({'can_merge_threads': 1})
  123. other_thread = testutils.post_thread(self.category_b)
  124. poll = testutils.post_poll(other_thread, self.user)
  125. response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
  126. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  127. # other thread has two posts now
  128. self.assertEqual(other_thread.post_set.count(), 3)
  129. # first thread is gone
  130. with self.assertRaises(Thread.DoesNotExist):
  131. Thread.objects.get(pk=self.thread.pk)
  132. # poll and its votes were kept
  133. self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=other_thread).count(), 1)
  134. self.assertEqual(PollVote.objects.filter(poll=poll, thread=other_thread).count(), 4)
  135. def test_merge_threads_moved_poll(self):
  136. """api merges two threads successfully, moving poll from other thread"""
  137. self.override_acl({'can_merge_threads': 1})
  138. self.override_other_acl({'can_merge_threads': 1})
  139. other_thread = testutils.post_thread(self.category_b)
  140. poll = testutils.post_poll(self.thread, self.user)
  141. response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
  142. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  143. # other thread has two posts now
  144. self.assertEqual(other_thread.post_set.count(), 3)
  145. # first thread is gone
  146. with self.assertRaises(Thread.DoesNotExist):
  147. Thread.objects.get(pk=self.thread.pk)
  148. # poll and its votes were moved
  149. self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=other_thread).count(), 1)
  150. self.assertEqual(PollVote.objects.filter(poll=poll, thread=other_thread).count(), 4)
  151. def test_threads_merge_conflict(self):
  152. """api errors on merge conflict, returning list of available polls"""
  153. self.override_acl({'can_merge_threads': 1})
  154. self.override_other_acl({'can_merge_threads': 1})
  155. other_thread = testutils.post_thread(self.category_b)
  156. poll = testutils.post_poll(self.thread, self.user)
  157. other_poll = testutils.post_poll(other_thread, self.user)
  158. response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
  159. self.assertEqual(response.status_code, 400)
  160. self.assertEqual(
  161. response.json(), {
  162. 'polls': [[0, "Delete all polls"],
  163. [poll.pk, poll.question],
  164. [other_poll.pk, other_poll.question]]
  165. }
  166. )
  167. # polls and votes were untouched
  168. self.assertEqual(Poll.objects.count(), 2)
  169. self.assertEqual(PollVote.objects.count(), 8)
  170. def test_threads_merge_conflict_invalid_resolution(self):
  171. """api errors on invalid merge conflict resolution"""
  172. self.override_acl({'can_merge_threads': 1})
  173. self.override_other_acl({'can_merge_threads': 1})
  174. other_thread = testutils.post_thread(self.category_b)
  175. testutils.post_poll(self.thread, self.user)
  176. testutils.post_poll(other_thread, self.user)
  177. response = self.client.post(
  178. self.api_link, {'thread_url': other_thread.get_absolute_url(),
  179. 'poll': 'jhdkajshdsak'}
  180. )
  181. self.assertEqual(response.status_code, 400)
  182. self.assertEqual(response.json(), {'detail': "Invalid choice."})
  183. # polls and votes were untouched
  184. self.assertEqual(Poll.objects.count(), 2)
  185. self.assertEqual(PollVote.objects.count(), 8)
  186. def test_threads_merge_conflict_delete_all(self):
  187. """api deletes all polls when delete all choice is selected"""
  188. self.override_acl({'can_merge_threads': 1})
  189. self.override_other_acl({'can_merge_threads': 1})
  190. other_thread = testutils.post_thread(self.category_b)
  191. testutils.post_poll(self.thread, self.user)
  192. testutils.post_poll(other_thread, self.user)
  193. response = self.client.post(
  194. self.api_link, {'thread_url': other_thread.get_absolute_url(),
  195. 'poll': 0}
  196. )
  197. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  198. # other thread has two posts now
  199. self.assertEqual(other_thread.post_set.count(), 3)
  200. # first thread is gone
  201. with self.assertRaises(Thread.DoesNotExist):
  202. Thread.objects.get(pk=self.thread.pk)
  203. # polls and votes are gone
  204. self.assertEqual(Poll.objects.count(), 0)
  205. self.assertEqual(PollVote.objects.count(), 0)
  206. def test_threads_merge_conflict_keep_first_poll(self):
  207. """api deletes other poll on merge"""
  208. self.override_acl({'can_merge_threads': 1})
  209. self.override_other_acl({'can_merge_threads': 1})
  210. other_thread = testutils.post_thread(self.category_b)
  211. poll = testutils.post_poll(self.thread, self.user)
  212. other_poll = testutils.post_poll(other_thread, self.user)
  213. response = self.client.post(
  214. self.api_link, {'thread_url': other_thread.get_absolute_url(),
  215. 'poll': poll.pk}
  216. )
  217. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  218. # other thread has two posts now
  219. self.assertEqual(other_thread.post_set.count(), 3)
  220. # first thread is gone
  221. with self.assertRaises(Thread.DoesNotExist):
  222. Thread.objects.get(pk=self.thread.pk)
  223. # other poll and its votes are gone
  224. self.assertEqual(Poll.objects.filter(thread=self.thread).count(), 0)
  225. self.assertEqual(PollVote.objects.filter(thread=self.thread).count(), 0)
  226. self.assertEqual(Poll.objects.filter(thread=other_thread).count(), 1)
  227. self.assertEqual(PollVote.objects.filter(thread=other_thread).count(), 4)
  228. Poll.objects.get(pk=poll.pk)
  229. with self.assertRaises(Poll.DoesNotExist):
  230. Poll.objects.get(pk=other_poll.pk)
  231. def test_threads_merge_conflict_keep_other_poll(self):
  232. """api deletes first poll on merge"""
  233. self.override_acl({'can_merge_threads': 1})
  234. self.override_other_acl({'can_merge_threads': 1})
  235. other_thread = testutils.post_thread(self.category_b)
  236. poll = testutils.post_poll(self.thread, self.user)
  237. other_poll = testutils.post_poll(other_thread, self.user)
  238. response = self.client.post(
  239. self.api_link, {'thread_url': other_thread.get_absolute_url(),
  240. 'poll': other_poll.pk}
  241. )
  242. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  243. # other thread has two posts now
  244. self.assertEqual(other_thread.post_set.count(), 3)
  245. # first thread is gone
  246. with self.assertRaises(Thread.DoesNotExist):
  247. Thread.objects.get(pk=self.thread.pk)
  248. # other poll and its votes are gone
  249. self.assertEqual(Poll.objects.filter(thread=self.thread).count(), 0)
  250. self.assertEqual(PollVote.objects.filter(thread=self.thread).count(), 0)
  251. self.assertEqual(Poll.objects.filter(thread=other_thread).count(), 1)
  252. self.assertEqual(PollVote.objects.filter(thread=other_thread).count(), 4)
  253. Poll.objects.get(pk=other_poll.pk)
  254. with self.assertRaises(Poll.DoesNotExist):
  255. Poll.objects.get(pk=poll.pk)