test_threads_merge_api.py 29 KB

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