test_thread_merge_api.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. from django.urls import reverse
  2. from misago.acl.testutils import override_acl
  3. from misago.categories.models import Category
  4. from misago.readtracker import poststracker
  5. from misago.threads import testutils
  6. from misago.threads.models import Poll, PollVote, Thread
  7. from .test_threads_api import ThreadsApiTestCase
  8. class ThreadMergeApiTests(ThreadsApiTestCase):
  9. def setUp(self):
  10. super(ThreadMergeApiTests, self).setUp()
  11. Category(
  12. name='Category B',
  13. slug='category-b',
  14. ).insert_at(
  15. self.category,
  16. position='last-child',
  17. save=True,
  18. )
  19. self.category_b = Category.objects.get(slug='category-b')
  20. self.api_link = reverse(
  21. 'misago:api:thread-merge', kwargs={
  22. 'pk': self.thread.pk,
  23. }
  24. )
  25. def override_other_acl(self, acl=None):
  26. other_category_acl = self.user.acl_cache['categories'][self.category.pk].copy()
  27. other_category_acl.update({
  28. 'can_see': 1,
  29. 'can_browse': 1,
  30. 'can_see_all_threads': 1,
  31. 'can_see_own_threads': 0,
  32. 'can_hide_threads': 0,
  33. 'can_approve_content': 0,
  34. 'can_edit_posts': 0,
  35. 'can_hide_posts': 0,
  36. 'can_hide_own_posts': 0,
  37. 'can_merge_threads': 0,
  38. 'can_close_threads': 0,
  39. })
  40. if acl:
  41. other_category_acl.update(acl)
  42. categories_acl = self.user.acl_cache['categories']
  43. categories_acl[self.category_b.pk] = other_category_acl
  44. visible_categories = [self.category.pk]
  45. if other_category_acl['can_see']:
  46. visible_categories.append(self.category_b.pk)
  47. override_acl(
  48. self.user, {
  49. 'visible_categories': visible_categories,
  50. 'categories': categories_acl,
  51. }
  52. )
  53. def test_merge_no_permission(self):
  54. """api validates if thread can be merged with other one"""
  55. self.override_acl({'can_merge_threads': 0})
  56. response = self.client.post(self.api_link)
  57. self.assertContains(
  58. response,
  59. "You can't merge threads in this category.",
  60. status_code=403
  61. )
  62. def test_merge_no_url(self):
  63. """api validates if thread url was given"""
  64. self.override_acl({'can_merge_threads': 1})
  65. response = self.client.post(self.api_link)
  66. self.assertContains(response, "Enter link to new thread.", status_code=400)
  67. def test_invalid_url(self):
  68. """api validates thread url"""
  69. self.override_acl({'can_merge_threads': 1})
  70. response = self.client.post(self.api_link, {
  71. 'other_thread': self.user.get_absolute_url(),
  72. })
  73. self.assertContains(response, "This is not a valid thread link.", status_code=400)
  74. def test_current_other_thread(self):
  75. """api validates if thread url given is to current thread"""
  76. self.override_acl({'can_merge_threads': 1})
  77. response = self.client.post(
  78. self.api_link, {
  79. 'other_thread': self.thread.get_absolute_url(),
  80. }
  81. )
  82. self.assertContains(response, "You can't merge thread with itself.", status_code=400)
  83. def test_other_thread_exists(self):
  84. """api validates if other thread exists"""
  85. self.override_acl({'can_merge_threads': 1})
  86. self.override_other_acl()
  87. other_thread = testutils.post_thread(self.category_b)
  88. other_other_thread = other_thread.get_absolute_url()
  89. other_thread.delete()
  90. response = self.client.post(self.api_link, {
  91. 'other_thread': other_other_thread,
  92. })
  93. self.assertContains(
  94. response, "The thread you have entered link to doesn't exist", status_code=400
  95. )
  96. def test_other_thread_is_invisible(self):
  97. """api validates if other thread is visible"""
  98. self.override_acl({'can_merge_threads': 1})
  99. self.override_other_acl({'can_see': 0})
  100. other_thread = testutils.post_thread(self.category_b)
  101. response = self.client.post(
  102. self.api_link, {
  103. 'other_thread': other_thread.get_absolute_url(),
  104. }
  105. )
  106. self.assertContains(
  107. response, "The thread you have entered link to doesn't exist", status_code=400
  108. )
  109. def test_other_thread_isnt_mergeable(self):
  110. """api validates if other thread can be merged"""
  111. self.override_acl({'can_merge_threads': 1})
  112. self.override_other_acl({'can_merge_threads': 0})
  113. other_thread = testutils.post_thread(self.category_b)
  114. response = self.client.post(
  115. self.api_link, {
  116. 'other_thread': other_thread.get_absolute_url(),
  117. }
  118. )
  119. self.assertContains(
  120. response, "Other thread can't be merged with.", status_code=400
  121. )
  122. def test_thread_category_is_closed(self):
  123. """api validates if thread's category is open"""
  124. self.override_acl({'can_merge_threads': 1})
  125. self.override_other_acl({
  126. 'can_merge_threads': 1,
  127. 'can_reply_threads': 0,
  128. 'can_close_threads': 0,
  129. })
  130. other_thread = testutils.post_thread(self.category_b)
  131. self.category.is_closed = True
  132. self.category.save()
  133. response = self.client.post(
  134. self.api_link, {
  135. 'other_thread': other_thread.get_absolute_url(),
  136. }
  137. )
  138. self.assertContains(
  139. response,
  140. "This category is closed. You can't merge it's threads.",
  141. status_code=403,
  142. )
  143. def test_thread_is_closed(self):
  144. """api validates if thread is open"""
  145. self.override_acl({'can_merge_threads': 1})
  146. self.override_other_acl({
  147. 'can_merge_threads': 1,
  148. 'can_reply_threads': 0,
  149. 'can_close_threads': 0,
  150. })
  151. other_thread = testutils.post_thread(self.category_b)
  152. self.thread.is_closed = True
  153. self.thread.save()
  154. response = self.client.post(
  155. self.api_link, {
  156. 'other_thread': other_thread.get_absolute_url(),
  157. }
  158. )
  159. self.assertContains(
  160. response,
  161. "This thread is closed. You can't merge it with other threads.",
  162. status_code=403,
  163. )
  164. def test_other_thread_category_is_closed(self):
  165. """api validates if other thread's category is open"""
  166. self.override_acl({'can_merge_threads': 1})
  167. self.override_other_acl({
  168. 'can_merge_threads': 1,
  169. 'can_reply_threads': 0,
  170. 'can_close_threads': 0,
  171. })
  172. other_thread = testutils.post_thread(self.category_b)
  173. self.category_b.is_closed = True
  174. self.category_b.save()
  175. response = self.client.post(
  176. self.api_link, {
  177. 'other_thread': other_thread.get_absolute_url(),
  178. }
  179. )
  180. self.assertContains(
  181. response, "Other thread's category is closed. You can't merge with it.", status_code=400
  182. )
  183. def test_other_thread_is_closed(self):
  184. """api validates if other thread is open"""
  185. self.override_acl({'can_merge_threads': 1})
  186. self.override_other_acl({
  187. 'can_merge_threads': 1,
  188. 'can_reply_threads': 0,
  189. 'can_close_threads': 0,
  190. })
  191. other_thread = testutils.post_thread(self.category_b)
  192. other_thread.is_closed = True
  193. other_thread.save()
  194. response = self.client.post(
  195. self.api_link, {
  196. 'other_thread': other_thread.get_absolute_url(),
  197. }
  198. )
  199. self.assertContains(
  200. response, "Other thread is closed and can't be merged with", status_code=400
  201. )
  202. def test_other_thread_isnt_replyable(self):
  203. """api validates if other thread can be replied, which is condition for merge"""
  204. self.override_acl({'can_merge_threads': 1})
  205. self.override_other_acl({
  206. 'can_merge_threads': 1,
  207. 'can_reply_threads': 0,
  208. })
  209. other_thread = testutils.post_thread(self.category_b)
  210. response = self.client.post(
  211. self.api_link, {
  212. 'other_thread': other_thread.get_absolute_url(),
  213. }
  214. )
  215. self.assertContains(
  216. response, "You can't merge this thread into thread you can't reply.", status_code=400
  217. )
  218. def test_merge_threads(self):
  219. """api merges two threads successfully"""
  220. self.override_acl({'can_merge_threads': 1})
  221. self.override_other_acl({'can_merge_threads': 1})
  222. other_thread = testutils.post_thread(self.category_b)
  223. response = self.client.post(
  224. self.api_link, {
  225. 'other_thread': other_thread.get_absolute_url(),
  226. }
  227. )
  228. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  229. # other thread has two posts and an event now
  230. self.assertEqual(other_thread.post_set.count(), 3)
  231. # first thread is gone
  232. with self.assertRaises(Thread.DoesNotExist):
  233. Thread.objects.get(pk=self.thread.pk)
  234. def test_merge_threads_kept_reads(self):
  235. """api keeps both threads readtrackers after merge"""
  236. self.override_acl({'can_merge_threads': 1})
  237. self.override_other_acl({'can_merge_threads': 1})
  238. other_thread = testutils.post_thread(self.category_b)
  239. poststracker.save_read(self.user, self.thread.first_post)
  240. poststracker.save_read(self.user, other_thread.first_post)
  241. response = self.client.post(
  242. self.api_link, {
  243. 'other_thread': other_thread.get_absolute_url(),
  244. }
  245. )
  246. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  247. # posts reads are kept
  248. postreads = self.user.postread_set.filter(post__is_event=False).order_by('id')
  249. self.assertEqual(
  250. list(postreads.values_list('post_id', flat=True)),
  251. [self.thread.first_post_id, other_thread.first_post_id]
  252. )
  253. self.assertEqual(postreads.filter(thread=other_thread).count(), 2)
  254. self.assertEqual(postreads.filter(category=self.category_b).count(), 2)
  255. def test_merge_threads_kept_subs(self):
  256. """api keeps other thread's subscription after merge"""
  257. self.override_acl({'can_merge_threads': 1})
  258. self.override_other_acl({'can_merge_threads': 1})
  259. other_thread = testutils.post_thread(self.category_b)
  260. self.user.subscription_set.create(
  261. thread=self.thread,
  262. category=self.thread.category,
  263. last_read_on=self.thread.last_post_on,
  264. send_email=False,
  265. )
  266. self.assertEqual(self.user.subscription_set.count(), 1)
  267. self.user.subscription_set.get(thread=self.thread)
  268. self.user.subscription_set.get(category=self.category)
  269. response = self.client.post(
  270. self.api_link, {
  271. 'other_thread': other_thread.get_absolute_url(),
  272. }
  273. )
  274. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  275. # subscriptions are kept
  276. self.assertEqual(self.user.subscription_set.count(), 1)
  277. self.user.subscription_set.get(thread=other_thread)
  278. self.user.subscription_set.get(category=self.category_b)
  279. def test_merge_threads_moved_subs(self):
  280. """api keeps other thread's subscription after merge"""
  281. self.override_acl({'can_merge_threads': 1})
  282. self.override_other_acl({'can_merge_threads': 1})
  283. other_thread = testutils.post_thread(self.category_b)
  284. self.user.subscription_set.create(
  285. thread=other_thread,
  286. category=other_thread.category,
  287. last_read_on=other_thread.last_post_on,
  288. send_email=False,
  289. )
  290. self.assertEqual(self.user.subscription_set.count(), 1)
  291. self.user.subscription_set.get(thread=other_thread)
  292. self.user.subscription_set.get(category=self.category_b)
  293. response = self.client.post(
  294. self.api_link, {
  295. 'other_thread': other_thread.get_absolute_url(),
  296. }
  297. )
  298. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  299. # subscriptions are kept
  300. self.assertEqual(self.user.subscription_set.count(), 1)
  301. self.user.subscription_set.get(thread=other_thread)
  302. self.user.subscription_set.get(category=self.category_b)
  303. def test_merge_threads_handle_subs_colision(self):
  304. """api resolves conflicting thread subscriptions after merge"""
  305. self.override_acl({'can_merge_threads': 1})
  306. self.override_other_acl({'can_merge_threads': 1})
  307. self.user.subscription_set.create(
  308. thread=self.thread,
  309. category=self.thread.category,
  310. last_read_on=self.thread.last_post_on,
  311. send_email=False,
  312. )
  313. other_thread = testutils.post_thread(self.category_b)
  314. self.user.subscription_set.create(
  315. thread=other_thread,
  316. category=other_thread.category,
  317. last_read_on=other_thread.last_post_on,
  318. send_email=False,
  319. )
  320. self.assertEqual(self.user.subscription_set.count(), 2)
  321. self.user.subscription_set.get(thread=self.thread)
  322. self.user.subscription_set.get(category=self.category)
  323. self.user.subscription_set.get(thread=other_thread)
  324. self.user.subscription_set.get(category=self.category_b)
  325. response = self.client.post(
  326. self.api_link, {
  327. 'other_thread': other_thread.get_absolute_url(),
  328. }
  329. )
  330. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  331. # subscriptions are kept
  332. self.assertEqual(self.user.subscription_set.count(), 1)
  333. self.user.subscription_set.get(thread=other_thread)
  334. self.user.subscription_set.get(category=self.category_b)
  335. def test_merge_threads_kept_best_answer(self):
  336. """api merges two threads successfully, keeping best answer from old thread"""
  337. self.override_acl({'can_merge_threads': 1})
  338. self.override_other_acl({'can_merge_threads': 1})
  339. other_thread = testutils.post_thread(self.category_b)
  340. best_answer = testutils.reply_thread(other_thread)
  341. other_thread.set_best_answer(self.user, best_answer)
  342. other_thread.save()
  343. response = self.client.post(
  344. self.api_link, {
  345. 'other_thread': other_thread.get_absolute_url(),
  346. }
  347. )
  348. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  349. # other thread has three posts and an event now
  350. self.assertEqual(other_thread.post_set.count(), 4)
  351. # first thread is gone
  352. with self.assertRaises(Thread.DoesNotExist):
  353. Thread.objects.get(pk=self.thread.pk)
  354. # best answer is kept sin other thread
  355. other_thread = Thread.objects.get(pk=other_thread.pk)
  356. self.assertEqual(other_thread.best_answer, best_answer)
  357. def test_merge_threads_moved_best_answer(self):
  358. """api merges two threads successfully, moving best answer to old thread"""
  359. self.override_acl({'can_merge_threads': 1})
  360. self.override_other_acl({'can_merge_threads': 1})
  361. other_thread = testutils.post_thread(self.category_b)
  362. best_answer = testutils.reply_thread(self.thread)
  363. self.thread.set_best_answer(self.user, best_answer)
  364. self.thread.save()
  365. response = self.client.post(
  366. self.api_link, {
  367. 'other_thread': other_thread.get_absolute_url(),
  368. }
  369. )
  370. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  371. # other thread has three posts and an event now
  372. self.assertEqual(other_thread.post_set.count(), 4)
  373. # first thread is gone
  374. with self.assertRaises(Thread.DoesNotExist):
  375. Thread.objects.get(pk=self.thread.pk)
  376. # best answer is kept sin other thread
  377. other_thread = Thread.objects.get(pk=other_thread.pk)
  378. self.assertEqual(other_thread.best_answer, best_answer)
  379. def test_merge_threads_merge_conflict_best_answer(self):
  380. """api errors on merge conflict, returning list of available best answers"""
  381. self.override_acl({'can_merge_threads': 1})
  382. self.override_other_acl({'can_merge_threads': 1})
  383. best_answer = testutils.reply_thread(self.thread)
  384. self.thread.set_best_answer(self.user, best_answer)
  385. self.thread.save()
  386. other_thread = testutils.post_thread(self.category_b)
  387. other_best_answer = testutils.reply_thread(other_thread)
  388. other_thread.set_best_answer(self.user, other_best_answer)
  389. other_thread.save()
  390. response = self.client.post(
  391. self.api_link, {
  392. 'other_thread': other_thread.get_absolute_url(),
  393. }
  394. )
  395. self.assertEqual(response.status_code, 400)
  396. self.assertEqual(response.json(), {
  397. 'best_answers': [
  398. ['0', "Unmark all best answers"],
  399. [str(self.thread.id), self.thread.title],
  400. [str(other_thread.id), other_thread.title],
  401. ]
  402. })
  403. # best answers were untouched
  404. self.assertEqual(self.thread.post_set.count(), 2)
  405. self.assertEqual(other_thread.post_set.count(), 2)
  406. self.assertEqual(Thread.objects.get(pk=self.thread.pk).best_answer_id, best_answer.id)
  407. self.assertEqual(
  408. Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
  409. def test_threads_merge_conflict_best_answer_invalid_resolution(self):
  410. """api errors on invalid merge conflict resolution"""
  411. self.override_acl({'can_merge_threads': 1})
  412. self.override_other_acl({'can_merge_threads': 1})
  413. best_answer = testutils.reply_thread(self.thread)
  414. self.thread.set_best_answer(self.user, best_answer)
  415. self.thread.save()
  416. other_thread = testutils.post_thread(self.category_b)
  417. other_best_answer = testutils.reply_thread(other_thread)
  418. other_thread.set_best_answer(self.user, other_best_answer)
  419. other_thread.save()
  420. response = self.client.post(
  421. self.api_link, {
  422. 'other_thread': other_thread.get_absolute_url(),
  423. 'best_answer': other_thread.id + 10,
  424. }
  425. )
  426. self.assertEqual(response.status_code, 400)
  427. self.assertEqual(response.json(), {'detail': "Invalid choice."})
  428. # best answers were untouched
  429. self.assertEqual(self.thread.post_set.count(), 2)
  430. self.assertEqual(other_thread.post_set.count(), 2)
  431. self.assertEqual(Thread.objects.get(pk=self.thread.pk).best_answer_id, best_answer.id)
  432. self.assertEqual(
  433. Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
  434. def test_threads_merge_conflict_delete_all_best_answers(self):
  435. """api unmarks all best answers when delete all choice is selected"""
  436. self.override_acl({'can_merge_threads': 1})
  437. self.override_other_acl({'can_merge_threads': 1})
  438. best_answer = testutils.reply_thread(self.thread)
  439. self.thread.set_best_answer(self.user, best_answer)
  440. self.thread.save()
  441. other_thread = testutils.post_thread(self.category_b)
  442. other_best_answer = testutils.reply_thread(other_thread)
  443. other_thread.set_best_answer(self.user, other_best_answer)
  444. other_thread.save()
  445. response = self.client.post(
  446. self.api_link, {
  447. 'other_thread': other_thread.get_absolute_url(),
  448. 'best_answer': 0,
  449. }
  450. )
  451. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  452. # other thread has four posts and an event now
  453. self.assertEqual(other_thread.post_set.count(), 5)
  454. # first thread is gone
  455. with self.assertRaises(Thread.DoesNotExist):
  456. Thread.objects.get(pk=self.thread.pk)
  457. # final thread has no marked best answer
  458. self.assertIsNone(Thread.objects.get(pk=other_thread.pk).best_answer_id)
  459. def test_threads_merge_conflict_keep_first_best_answer(self):
  460. """api unmarks other best answer on merge"""
  461. self.override_acl({'can_merge_threads': 1})
  462. self.override_other_acl({'can_merge_threads': 1})
  463. best_answer = testutils.reply_thread(self.thread)
  464. self.thread.set_best_answer(self.user, best_answer)
  465. self.thread.save()
  466. other_thread = testutils.post_thread(self.category_b)
  467. other_best_answer = testutils.reply_thread(other_thread)
  468. other_thread.set_best_answer(self.user, other_best_answer)
  469. other_thread.save()
  470. response = self.client.post(
  471. self.api_link, {
  472. 'other_thread': other_thread.get_absolute_url(),
  473. 'best_answer': self.thread.pk,
  474. }
  475. )
  476. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  477. # other thread has four posts and an event now
  478. self.assertEqual(other_thread.post_set.count(), 5)
  479. # first thread is gone
  480. with self.assertRaises(Thread.DoesNotExist):
  481. Thread.objects.get(pk=self.thread.pk)
  482. # other thread's best answer was unchanged
  483. self.assertEqual(Thread.objects.get(pk=other_thread.pk).best_answer_id, best_answer.id)
  484. def test_threads_merge_conflict_keep_other_best_answer(self):
  485. """api unmarks first best answer on merge"""
  486. self.override_acl({'can_merge_threads': 1})
  487. self.override_other_acl({'can_merge_threads': 1})
  488. best_answer = testutils.reply_thread(self.thread)
  489. self.thread.set_best_answer(self.user, best_answer)
  490. self.thread.save()
  491. other_thread = testutils.post_thread(self.category_b)
  492. other_best_answer = testutils.reply_thread(other_thread)
  493. other_thread.set_best_answer(self.user, other_best_answer)
  494. other_thread.save()
  495. response = self.client.post(
  496. self.api_link, {
  497. 'other_thread': other_thread.get_absolute_url(),
  498. 'best_answer': other_thread.pk,
  499. }
  500. )
  501. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  502. # other thread has four posts and an event now
  503. self.assertEqual(other_thread.post_set.count(), 5)
  504. # first thread is gone
  505. with self.assertRaises(Thread.DoesNotExist):
  506. Thread.objects.get(pk=self.thread.pk)
  507. # other thread's best answer was changed to merged in thread's answer
  508. self.assertEqual(
  509. Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
  510. def test_merge_threads_kept_poll(self):
  511. """api merges two threads successfully, keeping poll from old thread"""
  512. self.override_acl({'can_merge_threads': 1})
  513. self.override_other_acl({'can_merge_threads': 1})
  514. other_thread = testutils.post_thread(self.category_b)
  515. poll = testutils.post_poll(other_thread, self.user)
  516. response = self.client.post(
  517. self.api_link, {
  518. 'other_thread': other_thread.get_absolute_url(),
  519. }
  520. )
  521. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  522. # other thread has two posts and an event now
  523. self.assertEqual(other_thread.post_set.count(), 3)
  524. # first thread is gone
  525. with self.assertRaises(Thread.DoesNotExist):
  526. Thread.objects.get(pk=self.thread.pk)
  527. # poll and its votes were kept
  528. self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=other_thread).count(), 1)
  529. self.assertEqual(PollVote.objects.filter(poll=poll, thread=other_thread).count(), 4)
  530. def test_merge_threads_moved_poll(self):
  531. """api merges two threads successfully, moving poll from other thread"""
  532. self.override_acl({'can_merge_threads': 1})
  533. self.override_other_acl({'can_merge_threads': 1})
  534. other_thread = testutils.post_thread(self.category_b)
  535. poll = testutils.post_poll(self.thread, self.user)
  536. response = self.client.post(
  537. self.api_link, {
  538. 'other_thread': other_thread.get_absolute_url(),
  539. }
  540. )
  541. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  542. # other thread has two posts and an event now
  543. self.assertEqual(other_thread.post_set.count(), 3)
  544. # first thread is gone
  545. with self.assertRaises(Thread.DoesNotExist):
  546. Thread.objects.get(pk=self.thread.pk)
  547. # poll and its votes were moved
  548. self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=other_thread).count(), 1)
  549. self.assertEqual(PollVote.objects.filter(poll=poll, thread=other_thread).count(), 4)
  550. def test_threads_merge_conflict_polls(self):
  551. """api errors on merge conflict, returning list of available polls"""
  552. self.override_acl({'can_merge_threads': 1})
  553. self.override_other_acl({'can_merge_threads': 1})
  554. other_thread = testutils.post_thread(self.category_b)
  555. poll = testutils.post_poll(self.thread, self.user)
  556. other_poll = testutils.post_poll(other_thread, self.user)
  557. response = self.client.post(
  558. self.api_link, {
  559. 'other_thread': other_thread.get_absolute_url(),
  560. }
  561. )
  562. self.assertEqual(response.status_code, 400)
  563. self.assertEqual(
  564. response.json(), {
  565. 'polls': [
  566. ['0', "Delete all polls"],
  567. [
  568. str(poll.pk),
  569. u'{} ({})'.format(poll.question, poll.thread.title),
  570. ],
  571. [
  572. str(other_poll.pk),
  573. u'{} ({})'.format(other_poll.question, other_poll.thread.title),
  574. ],
  575. ]
  576. }
  577. )
  578. # polls and votes were untouched
  579. self.assertEqual(Poll.objects.count(), 2)
  580. self.assertEqual(PollVote.objects.count(), 8)
  581. def test_threads_merge_conflict_poll_invalid_resolution(self):
  582. """api errors on invalid merge conflict resolution"""
  583. self.override_acl({'can_merge_threads': 1})
  584. self.override_other_acl({'can_merge_threads': 1})
  585. other_thread = testutils.post_thread(self.category_b)
  586. testutils.post_poll(self.thread, self.user)
  587. testutils.post_poll(other_thread, self.user)
  588. response = self.client.post(
  589. self.api_link, {
  590. 'other_thread': other_thread.get_absolute_url(),
  591. 'poll': Poll.objects.all()[0].pk + 10,
  592. }
  593. )
  594. self.assertEqual(response.status_code, 400)
  595. self.assertEqual(response.json(), {'detail': "Invalid choice."})
  596. # polls and votes were untouched
  597. self.assertEqual(Poll.objects.count(), 2)
  598. self.assertEqual(PollVote.objects.count(), 8)
  599. def test_threads_merge_conflict_delete_all_polls(self):
  600. """api deletes all polls when delete all choice is selected"""
  601. self.override_acl({'can_merge_threads': 1})
  602. self.override_other_acl({'can_merge_threads': 1})
  603. other_thread = testutils.post_thread(self.category_b)
  604. testutils.post_poll(self.thread, self.user)
  605. testutils.post_poll(other_thread, self.user)
  606. response = self.client.post(
  607. self.api_link, {
  608. 'other_thread': other_thread.get_absolute_url(),
  609. 'poll': 0,
  610. }
  611. )
  612. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  613. # other thread has two posts and an event now
  614. self.assertEqual(other_thread.post_set.count(), 3)
  615. # first thread is gone
  616. with self.assertRaises(Thread.DoesNotExist):
  617. Thread.objects.get(pk=self.thread.pk)
  618. # polls and votes are gone
  619. self.assertEqual(Poll.objects.count(), 0)
  620. self.assertEqual(PollVote.objects.count(), 0)
  621. def test_threads_merge_conflict_keep_first_poll(self):
  622. """api deletes other poll on merge"""
  623. self.override_acl({'can_merge_threads': 1})
  624. self.override_other_acl({'can_merge_threads': 1})
  625. other_thread = testutils.post_thread(self.category_b)
  626. poll = testutils.post_poll(self.thread, self.user)
  627. other_poll = testutils.post_poll(other_thread, self.user)
  628. response = self.client.post(
  629. self.api_link, {
  630. 'other_thread': other_thread.get_absolute_url(),
  631. 'poll': poll.pk,
  632. }
  633. )
  634. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  635. # other thread has two posts and an event now
  636. self.assertEqual(other_thread.post_set.count(), 3)
  637. # first thread is gone
  638. with self.assertRaises(Thread.DoesNotExist):
  639. Thread.objects.get(pk=self.thread.pk)
  640. # other poll and its votes are gone
  641. self.assertEqual(Poll.objects.filter(thread=self.thread).count(), 0)
  642. self.assertEqual(PollVote.objects.filter(thread=self.thread).count(), 0)
  643. self.assertEqual(Poll.objects.filter(thread=other_thread).count(), 1)
  644. self.assertEqual(PollVote.objects.filter(thread=other_thread).count(), 4)
  645. Poll.objects.get(pk=poll.pk)
  646. with self.assertRaises(Poll.DoesNotExist):
  647. Poll.objects.get(pk=other_poll.pk)
  648. def test_threads_merge_conflict_keep_other_poll(self):
  649. """api deletes first poll on merge"""
  650. self.override_acl({'can_merge_threads': 1})
  651. self.override_other_acl({'can_merge_threads': 1})
  652. other_thread = testutils.post_thread(self.category_b)
  653. poll = testutils.post_poll(self.thread, self.user)
  654. other_poll = testutils.post_poll(other_thread, self.user)
  655. response = self.client.post(
  656. self.api_link, {
  657. 'other_thread': other_thread.get_absolute_url(),
  658. 'poll': other_poll.pk,
  659. }
  660. )
  661. self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
  662. # other thread has two posts and an event now
  663. self.assertEqual(other_thread.post_set.count(), 3)
  664. # first thread is gone
  665. with self.assertRaises(Thread.DoesNotExist):
  666. Thread.objects.get(pk=self.thread.pk)
  667. # other poll and its votes are gone
  668. self.assertEqual(Poll.objects.filter(thread=self.thread).count(), 0)
  669. self.assertEqual(PollVote.objects.filter(thread=self.thread).count(), 0)
  670. self.assertEqual(Poll.objects.filter(thread=other_thread).count(), 1)
  671. self.assertEqual(PollVote.objects.filter(thread=other_thread).count(), 4)
  672. Poll.objects.get(pk=other_poll.pk)
  673. with self.assertRaises(Poll.DoesNotExist):
  674. Poll.objects.get(pk=poll.pk)