test_threads_merge_api.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  1. import json
  2. from django.urls import reverse
  3. from misago.acl import add_acl
  4. from misago.categories.models import Category
  5. from misago.threads import testutils
  6. from misago.threads.api.threadendpoints.merge import MERGE_LIMIT
  7. from misago.threads.models import Poll, PollVote, Post, Thread
  8. from misago.threads.serializers import ThreadsListSerializer
  9. from .test_threads_api import ThreadsApiTestCase
  10. class ThreadsMergeApiTests(ThreadsApiTestCase):
  11. def setUp(self):
  12. super(ThreadsMergeApiTests, self).setUp()
  13. self.api_link = reverse('misago:api:thread-merge')
  14. Category(
  15. name='Category B',
  16. slug='category-b',
  17. ).insert_at(
  18. self.category, position='last-child', save=True
  19. )
  20. self.category_b = Category.objects.get(slug='category-b')
  21. def test_merge_no_threads(self):
  22. """api validates if we are trying to merge no threads"""
  23. response = self.client.post(self.api_link, content_type="application/json")
  24. self.assertEqual(response.status_code, 403)
  25. response_json = response.json()
  26. self.assertEqual(
  27. response_json, {'detail': "You have to select at least two threads to merge."}
  28. )
  29. def test_merge_empty_threads(self):
  30. """api validates if we are trying to empty threads list"""
  31. response = self.client.post(
  32. self.api_link, json.dumps({
  33. 'threads': []
  34. }), content_type="application/json"
  35. )
  36. self.assertEqual(response.status_code, 403)
  37. response_json = response.json()
  38. self.assertEqual(
  39. response_json, {'detail': "You have to select at least two threads to merge."}
  40. )
  41. def test_merge_invalid_threads(self):
  42. """api validates if we are trying to merge invalid thread ids"""
  43. response = self.client.post(
  44. self.api_link, json.dumps({
  45. 'threads': 'abcd'
  46. }), content_type="application/json"
  47. )
  48. self.assertEqual(response.status_code, 403)
  49. response_json = response.json()
  50. self.assertEqual(
  51. response_json, {'detail': "One or more thread ids received were invalid."}
  52. )
  53. response = self.client.post(
  54. self.api_link,
  55. json.dumps({
  56. 'threads': ['a', '-', 'c']
  57. }),
  58. content_type="application/json"
  59. )
  60. self.assertEqual(response.status_code, 403)
  61. response_json = response.json()
  62. self.assertEqual(
  63. response_json, {'detail': "One or more thread ids received were invalid."}
  64. )
  65. def test_merge_single_thread(self):
  66. """api validates if we are trying to merge single thread"""
  67. response = self.client.post(
  68. self.api_link,
  69. json.dumps({
  70. 'threads': [self.thread.id]
  71. }),
  72. content_type="application/json"
  73. )
  74. self.assertEqual(response.status_code, 403)
  75. response_json = response.json()
  76. self.assertEqual(
  77. response_json, {'detail': "You have to select at least two threads to merge."}
  78. )
  79. def test_merge_with_nonexisting_thread(self):
  80. """api validates if we are trying to merge with invalid thread"""
  81. testutils.post_thread(category=self.category_b)
  82. response = self.client.post(
  83. self.api_link,
  84. json.dumps({
  85. 'threads': [self.thread.id, self.thread.id + 1000]
  86. }),
  87. content_type="application/json"
  88. )
  89. self.assertEqual(response.status_code, 403)
  90. response_json = response.json()
  91. self.assertEqual(
  92. response_json, {'detail': "One or more threads to merge could not be found."}
  93. )
  94. def test_merge_with_invisible_thread(self):
  95. """api validates if we are trying to merge with inaccesible thread"""
  96. unaccesible_thread = testutils.post_thread(category=self.category_b)
  97. response = self.client.post(
  98. self.api_link,
  99. json.dumps({
  100. 'threads': [self.thread.id, unaccesible_thread.id]
  101. }),
  102. content_type="application/json"
  103. )
  104. self.assertEqual(response.status_code, 403)
  105. response_json = response.json()
  106. self.assertEqual(
  107. response_json, {'detail': "One or more threads to merge could not be found."}
  108. )
  109. def test_merge_no_permission(self):
  110. """api validates permission to merge threads"""
  111. thread = testutils.post_thread(category=self.category)
  112. response = self.client.post(
  113. self.api_link,
  114. json.dumps({
  115. 'threads': [self.thread.id, thread.id]
  116. }),
  117. content_type="application/json"
  118. )
  119. self.assertEqual(response.status_code, 403)
  120. response_json = response.json()
  121. self.assertEqual(
  122. response_json, [
  123. {
  124. 'id': thread.pk,
  125. 'title': thread.title,
  126. 'errors': ["You don't have permission to merge this thread with others."]
  127. },
  128. {
  129. 'id': self.thread.pk,
  130. 'title': self.thread.title,
  131. 'errors': ["You don't have permission to merge this thread with others."]
  132. },
  133. ]
  134. )
  135. def test_merge_too_many_threads(self):
  136. """api rejects too many threads to merge"""
  137. threads = []
  138. for _ in range(MERGE_LIMIT + 1):
  139. threads.append(testutils.post_thread(category=self.category).pk)
  140. self.override_acl({
  141. 'can_merge_threads': True,
  142. 'can_close_threads': False,
  143. 'can_edit_threads': False,
  144. 'can_reply_threads': False,
  145. })
  146. response = self.client.post(
  147. self.api_link, json.dumps({
  148. 'threads': threads
  149. }), content_type="application/json"
  150. )
  151. self.assertEqual(response.status_code, 403)
  152. response_json = response.json()
  153. self.assertEqual(
  154. response_json,
  155. {'detail': "No more than %s threads can be merged at single time." % MERGE_LIMIT}
  156. )
  157. def test_merge_no_final_thread(self):
  158. """api rejects merge because no data to merge threads was specified"""
  159. self.override_acl({
  160. 'can_merge_threads': True,
  161. 'can_close_threads': False,
  162. 'can_edit_threads': False,
  163. 'can_reply_threads': False,
  164. })
  165. thread = testutils.post_thread(category=self.category)
  166. response = self.client.post(
  167. self.api_link,
  168. json.dumps({
  169. 'threads': [self.thread.id, thread.id]
  170. }),
  171. content_type="application/json"
  172. )
  173. self.assertEqual(response.status_code, 400)
  174. response_json = response.json()
  175. self.assertEqual(
  176. response_json, {
  177. 'title': ['This field is required.'],
  178. 'category': ['This field is required.'],
  179. }
  180. )
  181. def test_merge_invalid_final_title(self):
  182. """api rejects merge because final thread title was invalid"""
  183. self.override_acl({
  184. 'can_merge_threads': True,
  185. 'can_close_threads': False,
  186. 'can_edit_threads': False,
  187. 'can_reply_threads': False,
  188. })
  189. thread = testutils.post_thread(category=self.category)
  190. response = self.client.post(
  191. self.api_link,
  192. json.dumps({
  193. 'threads': [self.thread.id, thread.id],
  194. 'title': '$$$',
  195. 'category': self.category.id,
  196. }),
  197. content_type="application/json"
  198. )
  199. self.assertEqual(response.status_code, 400)
  200. response_json = response.json()
  201. self.assertEqual(
  202. response_json,
  203. {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
  204. )
  205. def test_merge_invalid_category(self):
  206. """api rejects merge because final category was invalid"""
  207. self.override_acl({
  208. 'can_merge_threads': True,
  209. 'can_close_threads': False,
  210. 'can_edit_threads': False,
  211. 'can_reply_threads': False,
  212. })
  213. thread = testutils.post_thread(category=self.category)
  214. response = self.client.post(
  215. self.api_link,
  216. json.dumps({
  217. 'threads': [self.thread.id, thread.id],
  218. 'title': 'Valid thread title',
  219. 'category': self.category_b.id,
  220. }),
  221. content_type="application/json"
  222. )
  223. self.assertEqual(response.status_code, 400)
  224. response_json = response.json()
  225. self.assertEqual(response_json, {'category': ["Requested category could not be found."]})
  226. def test_merge_unallowed_start_thread(self):
  227. """api rejects merge because category isn't allowing starting threads"""
  228. self.override_acl({
  229. 'can_merge_threads': True,
  230. 'can_close_threads': False,
  231. 'can_edit_threads': False,
  232. 'can_reply_threads': False,
  233. 'can_start_threads': 0
  234. })
  235. thread = testutils.post_thread(category=self.category)
  236. response = self.client.post(
  237. self.api_link,
  238. json.dumps({
  239. 'threads': [self.thread.id, thread.id],
  240. 'title': 'Valid thread title',
  241. 'category': self.category.id
  242. }),
  243. content_type="application/json"
  244. )
  245. self.assertEqual(response.status_code, 400)
  246. response_json = response.json()
  247. self.assertEqual(
  248. response_json, {'category': ["You can't create new threads in selected category."]}
  249. )
  250. def test_merge_invalid_weight(self):
  251. """api rejects merge because final weight was invalid"""
  252. self.override_acl({
  253. 'can_merge_threads': True,
  254. 'can_close_threads': False,
  255. 'can_edit_threads': False,
  256. 'can_reply_threads': False,
  257. })
  258. thread = testutils.post_thread(category=self.category)
  259. response = self.client.post(
  260. self.api_link,
  261. json.dumps({
  262. 'threads': [self.thread.id, thread.id],
  263. 'title': 'Valid thread title',
  264. 'category': self.category.id,
  265. 'weight': 4,
  266. }),
  267. content_type="application/json"
  268. )
  269. self.assertEqual(response.status_code, 400)
  270. response_json = response.json()
  271. self.assertEqual(
  272. response_json, {'weight': ["Ensure this value is less than or equal to 2."]}
  273. )
  274. def test_merge_unallowed_global_weight(self):
  275. """api rejects merge because global weight was unallowed"""
  276. self.override_acl({
  277. 'can_merge_threads': True,
  278. 'can_close_threads': False,
  279. 'can_edit_threads': False,
  280. 'can_reply_threads': False,
  281. })
  282. thread = testutils.post_thread(category=self.category)
  283. response = self.client.post(
  284. self.api_link,
  285. json.dumps({
  286. 'threads': [self.thread.id, thread.id],
  287. 'title': 'Valid thread title',
  288. 'category': self.category.id,
  289. 'weight': 2,
  290. }),
  291. content_type="application/json"
  292. )
  293. self.assertEqual(response.status_code, 400)
  294. response_json = response.json()
  295. self.assertEqual(
  296. response_json,
  297. {'weight': ["You don't have permission to pin threads globally in this category."]}
  298. )
  299. def test_merge_unallowed_local_weight(self):
  300. """api rejects merge because local weight was unallowed"""
  301. self.override_acl({
  302. 'can_merge_threads': True,
  303. 'can_close_threads': False,
  304. 'can_edit_threads': False,
  305. 'can_reply_threads': False,
  306. })
  307. thread = testutils.post_thread(category=self.category)
  308. response = self.client.post(
  309. self.api_link,
  310. json.dumps({
  311. 'threads': [self.thread.id, thread.id],
  312. 'title': 'Valid thread title',
  313. 'category': self.category.id,
  314. 'weight': 1,
  315. }),
  316. content_type="application/json"
  317. )
  318. self.assertEqual(response.status_code, 400)
  319. response_json = response.json()
  320. self.assertEqual(
  321. response_json,
  322. {'weight': ["You don't have permission to pin threads in this category."]}
  323. )
  324. def test_merge_allowed_local_weight(self):
  325. """api allows local weight"""
  326. self.override_acl({
  327. 'can_merge_threads': True,
  328. 'can_close_threads': False,
  329. 'can_edit_threads': False,
  330. 'can_reply_threads': False,
  331. 'can_pin_threads': 1,
  332. })
  333. thread = testutils.post_thread(category=self.category)
  334. response = self.client.post(
  335. self.api_link,
  336. json.dumps({
  337. 'threads': [self.thread.id, thread.id],
  338. 'title': '$$$',
  339. 'category': self.category.id,
  340. 'weight': 1,
  341. }),
  342. content_type="application/json"
  343. )
  344. self.assertEqual(response.status_code, 400)
  345. response_json = response.json()
  346. self.assertEqual(
  347. response_json,
  348. {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
  349. )
  350. def test_merge_allowed_global_weight(self):
  351. """api allows global weight"""
  352. self.override_acl({
  353. 'can_merge_threads': True,
  354. 'can_close_threads': False,
  355. 'can_edit_threads': False,
  356. 'can_reply_threads': False,
  357. 'can_pin_threads': 2,
  358. })
  359. thread = testutils.post_thread(category=self.category)
  360. response = self.client.post(
  361. self.api_link,
  362. json.dumps({
  363. 'threads': [self.thread.id, thread.id],
  364. 'title': '$$$',
  365. 'category': self.category.id,
  366. 'weight': 2,
  367. }),
  368. content_type="application/json"
  369. )
  370. self.assertEqual(response.status_code, 400)
  371. response_json = response.json()
  372. self.assertEqual(
  373. response_json,
  374. {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
  375. )
  376. def test_merge_unallowed_close(self):
  377. """api rejects merge because closing thread was unallowed"""
  378. self.override_acl({
  379. 'can_merge_threads': True,
  380. 'can_close_threads': False,
  381. 'can_edit_threads': False,
  382. 'can_reply_threads': False,
  383. })
  384. thread = testutils.post_thread(category=self.category)
  385. response = self.client.post(
  386. self.api_link,
  387. json.dumps({
  388. 'threads': [self.thread.id, thread.id],
  389. 'title': 'Valid thread title',
  390. 'category': self.category.id,
  391. 'is_closed': True,
  392. }),
  393. content_type="application/json"
  394. )
  395. self.assertEqual(response.status_code, 400)
  396. response_json = response.json()
  397. self.assertEqual(
  398. response_json,
  399. {'is_closed': ["You don't have permission to close threads in this category."]}
  400. )
  401. def test_merge_with_close(self):
  402. """api allows for closing thread"""
  403. self.override_acl({
  404. 'can_merge_threads': True,
  405. 'can_edit_threads': False,
  406. 'can_reply_threads': False,
  407. 'can_close_threads': True,
  408. })
  409. thread = testutils.post_thread(category=self.category)
  410. response = self.client.post(
  411. self.api_link,
  412. json.dumps({
  413. 'threads': [self.thread.id, thread.id],
  414. 'title': '$$$',
  415. 'category': self.category.id,
  416. 'weight': 0,
  417. 'is_closed': True,
  418. }),
  419. content_type="application/json"
  420. )
  421. self.assertEqual(response.status_code, 400)
  422. response_json = response.json()
  423. self.assertEqual(
  424. response_json,
  425. {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
  426. )
  427. def test_merge_unallowed_hidden(self):
  428. """api rejects merge because hidden thread was unallowed"""
  429. self.override_acl({
  430. 'can_merge_threads': True,
  431. 'can_close_threads': False,
  432. 'can_edit_threads': False,
  433. 'can_reply_threads': False,
  434. 'can_hide_threads': 0,
  435. })
  436. thread = testutils.post_thread(category=self.category)
  437. response = self.client.post(
  438. self.api_link,
  439. json.dumps({
  440. 'threads': [self.thread.id, thread.id],
  441. 'title': 'Valid thread title',
  442. 'category': self.category.id,
  443. 'is_hidden': True,
  444. }),
  445. content_type="application/json"
  446. )
  447. self.assertEqual(response.status_code, 400)
  448. response_json = response.json()
  449. self.assertEqual(
  450. response_json,
  451. {'is_hidden': ["You don't have permission to hide threads in this category."]}
  452. )
  453. def test_merge_with_hide(self):
  454. """api allows for hiding thread"""
  455. self.override_acl({
  456. 'can_merge_threads': True,
  457. 'can_close_threads': False,
  458. 'can_edit_threads': False,
  459. 'can_reply_threads': False,
  460. 'can_hide_threads': 1,
  461. })
  462. thread = testutils.post_thread(category=self.category)
  463. response = self.client.post(
  464. self.api_link,
  465. json.dumps({
  466. 'threads': [self.thread.id, thread.id],
  467. 'title': '$$$',
  468. 'category': self.category.id,
  469. 'weight': 0,
  470. 'is_hidden': True,
  471. }),
  472. content_type="application/json"
  473. )
  474. self.assertEqual(response.status_code, 400)
  475. response_json = response.json()
  476. self.assertEqual(
  477. response_json,
  478. {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
  479. )
  480. def test_merge(self):
  481. """api performs basic merge"""
  482. posts_ids = [p.id for p in Post.objects.all()]
  483. self.override_acl({
  484. 'can_merge_threads': True,
  485. 'can_close_threads': False,
  486. 'can_edit_threads': False,
  487. 'can_reply_threads': False,
  488. })
  489. thread = testutils.post_thread(category=self.category)
  490. response = self.client.post(
  491. self.api_link,
  492. json.dumps({
  493. 'threads': [self.thread.id, thread.id],
  494. 'title': 'Merged thread!',
  495. 'category': self.category.id,
  496. }),
  497. content_type="application/json"
  498. )
  499. self.assertEqual(response.status_code, 200)
  500. # is response json with new thread?
  501. response_json = response.json()
  502. new_thread = Thread.objects.get(pk=response_json['id'])
  503. new_thread.is_read = False
  504. new_thread.subscription = None
  505. new_thread.top_category = None
  506. add_acl(self.user, new_thread.category)
  507. add_acl(self.user, new_thread)
  508. self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
  509. # did posts move to new thread?
  510. for post in Post.objects.filter(id__in=posts_ids):
  511. self.assertEqual(post.thread_id, new_thread.id)
  512. # are old threads gone?
  513. self.assertEqual([t.pk for t in Thread.objects.all()], [new_thread.pk])
  514. def test_merge_kitchensink(self):
  515. """api performs merge"""
  516. posts_ids = [p.id for p in Post.objects.all()]
  517. self.override_acl({
  518. 'can_merge_threads': True,
  519. 'can_close_threads': True,
  520. 'can_hide_threads': 1,
  521. 'can_pin_threads': 2
  522. })
  523. thread = testutils.post_thread(category=self.category)
  524. response = self.client.post(
  525. self.api_link,
  526. json.dumps({
  527. 'threads': [self.thread.id, thread.id],
  528. 'title': 'Merged thread!',
  529. 'category': self.category.id,
  530. 'is_closed': 1,
  531. 'is_hidden': 1,
  532. 'weight': 2
  533. }),
  534. content_type="application/json"
  535. )
  536. self.assertEqual(response.status_code, 200)
  537. # is response json with new thread?
  538. response_json = response.json()
  539. new_thread = Thread.objects.get(pk=response_json['id'])
  540. new_thread.is_read = False
  541. new_thread.subscription = None
  542. new_thread.top_category = None
  543. self.assertEqual(new_thread.weight, 2)
  544. self.assertTrue(new_thread.is_closed)
  545. self.assertTrue(new_thread.is_hidden)
  546. add_acl(self.user, new_thread.category)
  547. add_acl(self.user, new_thread)
  548. self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
  549. # did posts move to new thread?
  550. for post in Post.objects.filter(id__in=posts_ids):
  551. self.assertEqual(post.thread_id, new_thread.id)
  552. # are old threads gone?
  553. self.assertEqual([t.pk for t in Thread.objects.all()], [new_thread.pk])
  554. def test_merge_with_top_category(self):
  555. """api performs merge with top category"""
  556. posts_ids = [p.id for p in Post.objects.all()]
  557. self.override_acl({
  558. 'can_merge_threads': True,
  559. 'can_close_threads': False,
  560. 'can_edit_threads': False,
  561. 'can_reply_threads': False,
  562. })
  563. thread = testutils.post_thread(category=self.category)
  564. response = self.client.post(
  565. self.api_link,
  566. json.dumps({
  567. 'top_category': self.root.id,
  568. 'threads': [self.thread.id, thread.id],
  569. 'title': 'Merged thread!',
  570. 'category': self.category.id,
  571. }),
  572. content_type="application/json"
  573. )
  574. self.assertEqual(response.status_code, 200)
  575. # is response json with new thread?
  576. response_json = response.json()
  577. new_thread = Thread.objects.get(pk=response_json['id'])
  578. new_thread.is_read = False
  579. new_thread.subscription = None
  580. new_thread.top_category = self.category
  581. add_acl(self.user, new_thread.category)
  582. add_acl(self.user, new_thread)
  583. self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
  584. # did posts move to new thread?
  585. for post in Post.objects.filter(id__in=posts_ids):
  586. self.assertEqual(post.thread_id, new_thread.id)
  587. # are old threads gone?
  588. self.assertEqual([t.pk for t in Thread.objects.all()], [new_thread.pk])
  589. def test_merge_threads_kept_poll(self):
  590. """api merges two threads successfully, keeping poll from old thread"""
  591. self.override_acl({
  592. 'can_merge_threads': True,
  593. })
  594. other_thread = testutils.post_thread(self.category)
  595. poll = testutils.post_poll(other_thread, self.user)
  596. response = self.client.post(
  597. self.api_link,
  598. json.dumps({
  599. 'threads': [self.thread.id, other_thread.id],
  600. 'title': 'Merged thread!',
  601. 'category': self.category.id,
  602. }),
  603. content_type="application/json"
  604. )
  605. self.assertEqual(response.status_code, 200)
  606. response_json = response.json()
  607. new_thread = Thread.objects.get(pk=response_json['id'])
  608. # poll and its votes were kept
  609. self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=new_thread).count(), 1)
  610. self.assertEqual(PollVote.objects.filter(poll=poll, thread=new_thread).count(), 4)
  611. self.assertEqual(Poll.objects.count(), 1)
  612. self.assertEqual(PollVote.objects.count(), 4)
  613. def test_merge_threads_moved_poll(self):
  614. """api merges two threads successfully, moving poll from other thread"""
  615. self.override_acl({
  616. 'can_merge_threads': True,
  617. })
  618. other_thread = testutils.post_thread(self.category)
  619. poll = testutils.post_poll(self.thread, self.user)
  620. response = self.client.post(
  621. self.api_link,
  622. json.dumps({
  623. 'threads': [self.thread.id, other_thread.id],
  624. 'title': 'Merged thread!',
  625. 'category': self.category.id,
  626. }),
  627. content_type="application/json"
  628. )
  629. self.assertEqual(response.status_code, 200)
  630. response_json = response.json()
  631. new_thread = Thread.objects.get(pk=response_json['id'])
  632. # poll and its votes were kept
  633. self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=new_thread).count(), 1)
  634. self.assertEqual(PollVote.objects.filter(poll=poll, thread=new_thread).count(), 4)
  635. self.assertEqual(Poll.objects.count(), 1)
  636. self.assertEqual(PollVote.objects.count(), 4)
  637. def test_threads_merge_conflict(self):
  638. """api errors on merge conflict, returning list of available polls"""
  639. self.override_acl({
  640. 'can_merge_threads': True,
  641. })
  642. other_thread = testutils.post_thread(self.category)
  643. poll = testutils.post_poll(self.thread, self.user)
  644. other_poll = testutils.post_poll(other_thread, self.user)
  645. response = self.client.post(
  646. self.api_link,
  647. json.dumps({
  648. 'threads': [self.thread.id, other_thread.id],
  649. 'title': 'Merged thread!',
  650. 'category': self.category.id,
  651. }),
  652. content_type="application/json"
  653. )
  654. self.assertEqual(response.status_code, 400)
  655. self.assertEqual(
  656. response.json(), {
  657. 'polls': [[0, "Delete all polls"],
  658. [poll.pk, poll.question],
  659. [other_poll.pk, other_poll.question]]
  660. }
  661. )
  662. # polls and votes were untouched
  663. self.assertEqual(Poll.objects.count(), 2)
  664. self.assertEqual(PollVote.objects.count(), 8)
  665. def test_threads_merge_conflict_invalid_resolution(self):
  666. """api errors on invalid merge conflict resolution"""
  667. self.override_acl({
  668. 'can_merge_threads': True,
  669. })
  670. other_thread = testutils.post_thread(self.category)
  671. testutils.post_poll(self.thread, self.user)
  672. testutils.post_poll(other_thread, self.user)
  673. response = self.client.post(
  674. self.api_link,
  675. json.dumps({
  676. 'threads': [self.thread.id, other_thread.id],
  677. 'title': 'Merged thread!',
  678. 'category': self.category.id,
  679. 'poll': 'dsa7dsadsa9789'
  680. }),
  681. content_type="application/json"
  682. )
  683. self.assertEqual(response.status_code, 400)
  684. self.assertEqual(response.json(), {'detail': "Invalid choice."})
  685. # polls and votes were untouched
  686. self.assertEqual(Poll.objects.count(), 2)
  687. self.assertEqual(PollVote.objects.count(), 8)
  688. def test_threads_merge_conflict_delete_all(self):
  689. """api deletes all polls when delete all choice is selected"""
  690. self.override_acl({
  691. 'can_merge_threads': True,
  692. })
  693. other_thread = testutils.post_thread(self.category)
  694. testutils.post_poll(self.thread, self.user)
  695. testutils.post_poll(other_thread, self.user)
  696. response = self.client.post(
  697. self.api_link,
  698. json.dumps({
  699. 'threads': [self.thread.id, other_thread.id],
  700. 'title': 'Merged thread!',
  701. 'category': self.category.id,
  702. 'poll': 0
  703. }),
  704. content_type="application/json"
  705. )
  706. self.assertEqual(response.status_code, 200)
  707. # polls and votes are gone
  708. self.assertEqual(Poll.objects.count(), 0)
  709. self.assertEqual(PollVote.objects.count(), 0)
  710. def test_threads_merge_conflict_keep_first_poll(self):
  711. """api deletes other poll on merge"""
  712. self.override_acl({
  713. 'can_merge_threads': True,
  714. })
  715. other_thread = testutils.post_thread(self.category)
  716. poll = testutils.post_poll(self.thread, self.user)
  717. other_poll = testutils.post_poll(other_thread, self.user)
  718. response = self.client.post(
  719. self.api_link,
  720. json.dumps({
  721. 'threads': [self.thread.id, other_thread.id],
  722. 'title': 'Merged thread!',
  723. 'category': self.category.id,
  724. 'poll': poll.pk
  725. }),
  726. content_type="application/json"
  727. )
  728. self.assertEqual(response.status_code, 200)
  729. # other poll and its votes are gone
  730. self.assertEqual(Poll.objects.count(), 1)
  731. self.assertEqual(PollVote.objects.count(), 4)
  732. Poll.objects.get(pk=poll.pk)
  733. with self.assertRaises(Poll.DoesNotExist):
  734. Poll.objects.get(pk=other_poll.pk)
  735. def test_threads_merge_conflict_keep_other_poll(self):
  736. """api deletes first poll on merge"""
  737. self.override_acl({
  738. 'can_merge_threads': True,
  739. })
  740. other_thread = testutils.post_thread(self.category)
  741. poll = testutils.post_poll(self.thread, self.user)
  742. other_poll = testutils.post_poll(other_thread, self.user)
  743. response = self.client.post(
  744. self.api_link,
  745. json.dumps({
  746. 'threads': [self.thread.id, other_thread.id],
  747. 'title': 'Merged thread!',
  748. 'category': self.category.id,
  749. 'poll': other_poll.pk
  750. }),
  751. content_type="application/json"
  752. )
  753. self.assertEqual(response.status_code, 200)
  754. # other poll and its votes are gone
  755. self.assertEqual(Poll.objects.count(), 1)
  756. self.assertEqual(PollVote.objects.count(), 4)
  757. Poll.objects.get(pk=other_poll.pk)
  758. with self.assertRaises(Poll.DoesNotExist):
  759. Poll.objects.get(pk=poll.pk)