test_threads_merge_api.py 30 KB

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