test_threads_merge_api.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939
  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. new_thread.top_category = None
  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_kitchensink(self):
  555. """api performs merge"""
  556. posts_ids = [p.id for p in Post.objects.all()]
  557. self.override_acl({
  558. 'can_merge_threads': True,
  559. 'can_close_threads': True,
  560. 'can_hide_threads': 1,
  561. 'can_pin_threads': 2,
  562. })
  563. thread = testutils.post_thread(category=self.category)
  564. response = self.client.post(
  565. self.api_link,
  566. json.dumps({
  567. 'threads': [self.thread.id, thread.id],
  568. 'title': 'Merged thread!',
  569. 'category': self.category.id,
  570. 'is_closed': 1,
  571. 'is_hidden': 1,
  572. 'weight': 2,
  573. }),
  574. content_type="application/json",
  575. )
  576. self.assertEqual(response.status_code, 200)
  577. # is response json with new thread?
  578. response_json = response.json()
  579. new_thread = Thread.objects.get(pk=response_json['id'])
  580. new_thread.is_read = False
  581. new_thread.subscription = None
  582. new_thread.top_category = None
  583. self.assertEqual(new_thread.weight, 2)
  584. self.assertTrue(new_thread.is_closed)
  585. self.assertTrue(new_thread.is_hidden)
  586. add_acl(self.user, new_thread.category)
  587. add_acl(self.user, new_thread)
  588. self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
  589. # did posts move to new thread?
  590. for post in Post.objects.filter(id__in=posts_ids):
  591. self.assertEqual(post.thread_id, new_thread.id)
  592. # are old threads gone?
  593. self.assertEqual([t.pk for t in Thread.objects.all()], [new_thread.pk])
  594. def test_merge_with_top_category(self):
  595. """api performs merge with top category"""
  596. posts_ids = [p.id for p in Post.objects.all()]
  597. self.override_acl({
  598. 'can_merge_threads': True,
  599. 'can_close_threads': False,
  600. 'can_edit_threads': False,
  601. 'can_reply_threads': False,
  602. })
  603. thread = testutils.post_thread(category=self.category)
  604. response = self.client.post(
  605. self.api_link,
  606. json.dumps({
  607. 'top_category': self.root.id,
  608. 'threads': [self.thread.id, thread.id],
  609. 'title': 'Merged thread!',
  610. 'category': self.category.id,
  611. }),
  612. content_type="application/json",
  613. )
  614. self.assertEqual(response.status_code, 200)
  615. # is response json with new thread?
  616. response_json = response.json()
  617. new_thread = Thread.objects.get(pk=response_json['id'])
  618. new_thread.is_read = False
  619. new_thread.subscription = None
  620. new_thread.top_category = self.category
  621. add_acl(self.user, new_thread.category)
  622. add_acl(self.user, new_thread)
  623. self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
  624. # did posts move to new thread?
  625. for post in Post.objects.filter(id__in=posts_ids):
  626. self.assertEqual(post.thread_id, new_thread.id)
  627. # are old threads gone?
  628. self.assertEqual([t.pk for t in Thread.objects.all()], [new_thread.pk])
  629. def test_merge_threads_kept_poll(self):
  630. """api merges two threads successfully, keeping poll from old thread"""
  631. self.override_acl({'can_merge_threads': True})
  632. other_thread = testutils.post_thread(self.category)
  633. poll = testutils.post_poll(other_thread, self.user)
  634. response = self.client.post(
  635. self.api_link,
  636. json.dumps({
  637. 'threads': [self.thread.id, other_thread.id],
  638. 'title': 'Merged thread!',
  639. 'category': self.category.id,
  640. }),
  641. content_type="application/json",
  642. )
  643. self.assertEqual(response.status_code, 200)
  644. response_json = response.json()
  645. new_thread = Thread.objects.get(pk=response_json['id'])
  646. # poll and its votes were kept
  647. self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=new_thread).count(), 1)
  648. self.assertEqual(PollVote.objects.filter(poll=poll, thread=new_thread).count(), 4)
  649. self.assertEqual(Poll.objects.count(), 1)
  650. self.assertEqual(PollVote.objects.count(), 4)
  651. def test_merge_threads_moved_poll(self):
  652. """api merges two threads successfully, moving poll from other thread"""
  653. self.override_acl({'can_merge_threads': True})
  654. other_thread = testutils.post_thread(self.category)
  655. poll = testutils.post_poll(self.thread, self.user)
  656. response = self.client.post(
  657. self.api_link,
  658. json.dumps({
  659. 'threads': [self.thread.id, other_thread.id],
  660. 'title': 'Merged thread!',
  661. 'category': self.category.id,
  662. }),
  663. content_type="application/json",
  664. )
  665. self.assertEqual(response.status_code, 200)
  666. response_json = response.json()
  667. new_thread = Thread.objects.get(pk=response_json['id'])
  668. # poll and its votes were kept
  669. self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=new_thread).count(), 1)
  670. self.assertEqual(PollVote.objects.filter(poll=poll, thread=new_thread).count(), 4)
  671. self.assertEqual(Poll.objects.count(), 1)
  672. self.assertEqual(PollVote.objects.count(), 4)
  673. def test_threads_merge_conflict(self):
  674. """api errors on merge conflict, returning list of available polls"""
  675. self.override_acl({'can_merge_threads': True})
  676. other_thread = testutils.post_thread(self.category)
  677. poll = testutils.post_poll(self.thread, self.user)
  678. other_poll = testutils.post_poll(other_thread, self.user)
  679. response = self.client.post(
  680. self.api_link,
  681. json.dumps({
  682. 'threads': [self.thread.id, other_thread.id],
  683. 'title': 'Merged thread!',
  684. 'category': self.category.id,
  685. }),
  686. content_type="application/json",
  687. )
  688. self.assertEqual(response.status_code, 400)
  689. self.assertEqual(
  690. response.json(), {
  691. 'polls': [
  692. [0, "Delete all polls"],
  693. [poll.pk, poll.question],
  694. [other_poll.pk, other_poll.question],
  695. ],
  696. }
  697. )
  698. # polls and votes were untouched
  699. self.assertEqual(Poll.objects.count(), 2)
  700. self.assertEqual(PollVote.objects.count(), 8)
  701. def test_threads_merge_conflict_invalid_resolution(self):
  702. """api errors on invalid merge conflict resolution"""
  703. self.override_acl({'can_merge_threads': True})
  704. other_thread = testutils.post_thread(self.category)
  705. testutils.post_poll(self.thread, self.user)
  706. testutils.post_poll(other_thread, self.user)
  707. response = self.client.post(
  708. self.api_link,
  709. json.dumps({
  710. 'threads': [self.thread.id, other_thread.id],
  711. 'title': 'Merged thread!',
  712. 'category': self.category.id,
  713. 'poll': 'dsa7dsadsa9789',
  714. }),
  715. content_type="application/json",
  716. )
  717. self.assertEqual(response.status_code, 400)
  718. self.assertEqual(response.json(), {
  719. 'detail': "Invalid choice.",
  720. })
  721. # polls and votes were untouched
  722. self.assertEqual(Poll.objects.count(), 2)
  723. self.assertEqual(PollVote.objects.count(), 8)
  724. def test_threads_merge_conflict_delete_all(self):
  725. """api deletes all polls when delete all choice is selected"""
  726. self.override_acl({'can_merge_threads': True})
  727. other_thread = testutils.post_thread(self.category)
  728. testutils.post_poll(self.thread, self.user)
  729. testutils.post_poll(other_thread, self.user)
  730. response = self.client.post(
  731. self.api_link,
  732. json.dumps({
  733. 'threads': [self.thread.id, other_thread.id],
  734. 'title': 'Merged thread!',
  735. 'category': self.category.id,
  736. 'poll': 0,
  737. }),
  738. content_type="application/json",
  739. )
  740. self.assertEqual(response.status_code, 200)
  741. # polls and votes are gone
  742. self.assertEqual(Poll.objects.count(), 0)
  743. self.assertEqual(PollVote.objects.count(), 0)
  744. def test_threads_merge_conflict_keep_first_poll(self):
  745. """api deletes other poll on merge"""
  746. self.override_acl({'can_merge_threads': True})
  747. other_thread = testutils.post_thread(self.category)
  748. poll = testutils.post_poll(self.thread, self.user)
  749. other_poll = testutils.post_poll(other_thread, self.user)
  750. response = self.client.post(
  751. self.api_link,
  752. json.dumps({
  753. 'threads': [self.thread.id, other_thread.id],
  754. 'title': 'Merged thread!',
  755. 'category': self.category.id,
  756. 'poll': poll.pk,
  757. }),
  758. content_type="application/json",
  759. )
  760. self.assertEqual(response.status_code, 200)
  761. # other poll and its votes are gone
  762. self.assertEqual(Poll.objects.count(), 1)
  763. self.assertEqual(PollVote.objects.count(), 4)
  764. Poll.objects.get(pk=poll.pk)
  765. with self.assertRaises(Poll.DoesNotExist):
  766. Poll.objects.get(pk=other_poll.pk)
  767. def test_threads_merge_conflict_keep_other_poll(self):
  768. """api deletes first poll on merge"""
  769. self.override_acl({'can_merge_threads': True})
  770. other_thread = testutils.post_thread(self.category)
  771. poll = testutils.post_poll(self.thread, self.user)
  772. other_poll = testutils.post_poll(other_thread, self.user)
  773. response = self.client.post(
  774. self.api_link,
  775. json.dumps({
  776. 'threads': [self.thread.id, other_thread.id],
  777. 'title': 'Merged thread!',
  778. 'category': self.category.id,
  779. 'poll': other_poll.pk,
  780. }),
  781. content_type="application/json",
  782. )
  783. self.assertEqual(response.status_code, 200)
  784. # other poll and its votes are gone
  785. self.assertEqual(Poll.objects.count(), 1)
  786. self.assertEqual(PollVote.objects.count(), 4)
  787. Poll.objects.get(pk=other_poll.pk)
  788. with self.assertRaises(Poll.DoesNotExist):
  789. Poll.objects.get(pk=poll.pk)