test_thread_postpatch_api.py 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import json
  4. from datetime import timedelta
  5. from django.urls import reverse
  6. from django.utils import timezone
  7. from misago.acl.testutils import override_acl
  8. from misago.categories.models import Category
  9. from misago.threads import testutils
  10. from misago.threads.models import Thread, Post
  11. from misago.users.testutils import AuthenticatedUserTestCase
  12. class ThreadPostPatchApiTestCase(AuthenticatedUserTestCase):
  13. def setUp(self):
  14. super(ThreadPostPatchApiTestCase, self).setUp()
  15. self.category = Category.objects.get(slug='first-category')
  16. self.thread = testutils.post_thread(category=self.category)
  17. self.post = testutils.reply_thread(self.thread, poster=self.user)
  18. self.api_link = reverse(
  19. 'misago:api:thread-post-detail',
  20. kwargs={
  21. 'thread_pk': self.thread.pk,
  22. 'pk': self.post.pk,
  23. }
  24. )
  25. def patch(self, api_link, ops):
  26. return self.client.patch(api_link, json.dumps(ops), content_type="application/json")
  27. def refresh_post(self):
  28. self.post = self.thread.post_set.get(pk=self.post.pk)
  29. def refresh_thread(self):
  30. self.thread = Thread.objects.get(pk=self.thread.pk)
  31. def override_acl(self, extra_acl=None):
  32. new_acl = self.user.acl_cache
  33. new_acl['categories'][self.category.pk].update({
  34. 'can_see': 1,
  35. 'can_browse': 1,
  36. 'can_start_threads': 0,
  37. 'can_reply_threads': 0,
  38. 'can_edit_posts': 1,
  39. })
  40. if extra_acl:
  41. new_acl['categories'][self.category.pk].update(extra_acl)
  42. override_acl(self.user, new_acl)
  43. class PostAddAclApiTests(ThreadPostPatchApiTestCase):
  44. def test_add_acl_true(self):
  45. """api adds current event's acl to response"""
  46. response = self.patch(self.api_link, [
  47. {
  48. 'op': 'add',
  49. 'path': 'acl',
  50. 'value': True,
  51. },
  52. ])
  53. self.assertEqual(response.status_code, 200)
  54. response_json = response.json()
  55. self.assertTrue(response_json['acl'])
  56. def test_add_acl_false(self):
  57. """if value is false, api won't add acl to the response, but will set empty key"""
  58. response = self.patch(self.api_link, [
  59. {
  60. 'op': 'add',
  61. 'path': 'acl',
  62. 'value': False,
  63. },
  64. ])
  65. self.assertEqual(response.status_code, 200)
  66. response_json = response.json()
  67. self.assertIsNone(response_json['acl'])
  68. class PostProtectApiTests(ThreadPostPatchApiTestCase):
  69. def test_protect_post(self):
  70. """api makes it possible to protect post"""
  71. self.override_acl({'can_protect_posts': 1})
  72. response = self.patch(
  73. self.api_link, [
  74. {
  75. 'op': 'replace',
  76. 'path': 'is-protected',
  77. 'value': True,
  78. },
  79. ]
  80. )
  81. self.assertEqual(response.status_code, 200)
  82. reponse_json = response.json()
  83. self.assertTrue(reponse_json['is_protected'])
  84. self.refresh_post()
  85. self.assertTrue(self.post.is_protected)
  86. def test_unprotect_post(self):
  87. """api makes it possible to unprotect protected post"""
  88. self.post.is_protected = True
  89. self.post.save()
  90. self.override_acl({'can_protect_posts': 1})
  91. response = self.patch(
  92. self.api_link, [
  93. {
  94. 'op': 'replace',
  95. 'path': 'is-protected',
  96. 'value': False,
  97. },
  98. ]
  99. )
  100. self.assertEqual(response.status_code, 200)
  101. reponse_json = response.json()
  102. self.assertFalse(reponse_json['is_protected'])
  103. self.refresh_post()
  104. self.assertFalse(self.post.is_protected)
  105. def test_protect_best_answer(self):
  106. """api makes it possible to protect post"""
  107. self.thread.set_best_answer(self.user, self.post)
  108. self.thread.save()
  109. self.assertFalse(self.thread.best_answer_is_protected)
  110. self.override_acl({'can_protect_posts': 1})
  111. response = self.patch(
  112. self.api_link, [
  113. {
  114. 'op': 'replace',
  115. 'path': 'is-protected',
  116. 'value': True,
  117. },
  118. ]
  119. )
  120. self.assertEqual(response.status_code, 200)
  121. reponse_json = response.json()
  122. self.assertTrue(reponse_json['is_protected'])
  123. self.refresh_post()
  124. self.assertTrue(self.post.is_protected)
  125. self.refresh_thread()
  126. self.assertTrue(self.thread.best_answer_is_protected)
  127. def test_unprotect_best_answer(self):
  128. """api makes it possible to unprotect protected post"""
  129. self.post.is_protected = True
  130. self.post.save()
  131. self.thread.set_best_answer(self.user, self.post)
  132. self.thread.save()
  133. self.assertTrue(self.thread.best_answer_is_protected)
  134. self.override_acl({'can_protect_posts': 1})
  135. response = self.patch(
  136. self.api_link, [
  137. {
  138. 'op': 'replace',
  139. 'path': 'is-protected',
  140. 'value': False,
  141. },
  142. ]
  143. )
  144. self.assertEqual(response.status_code, 200)
  145. reponse_json = response.json()
  146. self.assertFalse(reponse_json['is_protected'])
  147. self.refresh_post()
  148. self.assertFalse(self.post.is_protected)
  149. self.refresh_thread()
  150. self.assertFalse(self.thread.best_answer_is_protected)
  151. def test_protect_post_no_permission(self):
  152. """api validates permission to protect post"""
  153. self.override_acl({'can_protect_posts': 0})
  154. response = self.patch(
  155. self.api_link, [
  156. {
  157. 'op': 'replace',
  158. 'path': 'is-protected',
  159. 'value': True,
  160. },
  161. ]
  162. )
  163. self.assertEqual(response.status_code, 400)
  164. response_json = response.json()
  165. self.assertEqual(response_json['detail'][0], "You can't protect posts in this category.")
  166. self.refresh_post()
  167. self.assertFalse(self.post.is_protected)
  168. def test_unprotect_post_no_permission(self):
  169. """api validates permission to unprotect post"""
  170. self.post.is_protected = True
  171. self.post.save()
  172. self.override_acl({'can_protect_posts': 0})
  173. response = self.patch(
  174. self.api_link, [
  175. {
  176. 'op': 'replace',
  177. 'path': 'is-protected',
  178. 'value': False,
  179. },
  180. ]
  181. )
  182. self.assertEqual(response.status_code, 400)
  183. response_json = response.json()
  184. self.assertEqual(response_json['detail'][0], "You can't protect posts in this category.")
  185. self.refresh_post()
  186. self.assertTrue(self.post.is_protected)
  187. def test_protect_post_not_editable(self):
  188. """api validates if we can edit post we want to protect"""
  189. self.override_acl({'can_edit_posts': 0, 'can_protect_posts': 1})
  190. response = self.patch(
  191. self.api_link, [
  192. {
  193. 'op': 'replace',
  194. 'path': 'is-protected',
  195. 'value': True,
  196. },
  197. ]
  198. )
  199. self.assertEqual(response.status_code, 400)
  200. response_json = response.json()
  201. self.assertEqual(response_json['detail'][0], "You can't protect posts you can't edit.")
  202. self.refresh_post()
  203. self.assertFalse(self.post.is_protected)
  204. def test_unprotect_post_not_editable(self):
  205. """api validates if we can edit post we want to protect"""
  206. self.post.is_protected = True
  207. self.post.save()
  208. self.override_acl({'can_edit_posts': 0, 'can_protect_posts': 1})
  209. response = self.patch(
  210. self.api_link, [
  211. {
  212. 'op': 'replace',
  213. 'path': 'is-protected',
  214. 'value': False,
  215. },
  216. ]
  217. )
  218. self.assertEqual(response.status_code, 400)
  219. response_json = response.json()
  220. self.assertEqual(response_json['detail'][0], "You can't protect posts you can't edit.")
  221. self.refresh_post()
  222. self.assertTrue(self.post.is_protected)
  223. class PostApproveApiTests(ThreadPostPatchApiTestCase):
  224. def test_approve_post(self):
  225. """api makes it possible to approve post"""
  226. self.post.is_unapproved = True
  227. self.post.save()
  228. self.override_acl({'can_approve_content': 1})
  229. response = self.patch(
  230. self.api_link, [
  231. {
  232. 'op': 'replace',
  233. 'path': 'is-unapproved',
  234. 'value': False,
  235. },
  236. ]
  237. )
  238. self.assertEqual(response.status_code, 200)
  239. reponse_json = response.json()
  240. self.assertFalse(reponse_json['is_unapproved'])
  241. self.refresh_post()
  242. self.assertFalse(self.post.is_unapproved)
  243. def test_unapprove_post(self):
  244. """unapproving posts is not supported by api"""
  245. self.override_acl({'can_approve_content': 1})
  246. response = self.patch(
  247. self.api_link, [
  248. {
  249. 'op': 'replace',
  250. 'path': 'is-unapproved',
  251. 'value': True,
  252. },
  253. ]
  254. )
  255. self.assertEqual(response.status_code, 400)
  256. response_json = response.json()
  257. self.assertEqual(response_json['detail'][0], "Content approval can't be reversed.")
  258. self.refresh_post()
  259. self.assertFalse(self.post.is_unapproved)
  260. def test_approve_post_no_permission(self):
  261. """api validates approval permission"""
  262. self.post.is_unapproved = True
  263. self.post.save()
  264. self.override_acl({'can_approve_content': 0})
  265. response = self.patch(
  266. self.api_link, [
  267. {
  268. 'op': 'replace',
  269. 'path': 'is-unapproved',
  270. 'value': False,
  271. },
  272. ]
  273. )
  274. self.assertEqual(response.status_code, 400)
  275. response_json = response.json()
  276. self.assertEqual(response_json['detail'][0], "You can't approve posts in this category.")
  277. self.refresh_post()
  278. self.assertTrue(self.post.is_unapproved)
  279. def test_approve_post_closed_thread_no_permission(self):
  280. """api validates approval permission in closed threads"""
  281. self.post.is_unapproved = True
  282. self.post.save()
  283. self.thread.is_closed = True
  284. self.thread.save()
  285. self.override_acl({
  286. 'can_approve_content': 1,
  287. 'can_close_threads': 0,
  288. })
  289. response = self.patch(
  290. self.api_link, [
  291. {
  292. 'op': 'replace',
  293. 'path': 'is-unapproved',
  294. 'value': False,
  295. },
  296. ]
  297. )
  298. self.assertEqual(response.status_code, 400)
  299. response_json = response.json()
  300. self.assertEqual(
  301. response_json['detail'][0],
  302. "This thread is closed. You can't approve posts in it.",
  303. )
  304. self.refresh_post()
  305. self.assertTrue(self.post.is_unapproved)
  306. def test_approve_post_closed_category_no_permission(self):
  307. """api validates approval permission in closed categories"""
  308. self.post.is_unapproved = True
  309. self.post.save()
  310. self.category.is_closed = True
  311. self.category.save()
  312. self.override_acl({
  313. 'can_approve_content': 1,
  314. 'can_close_threads': 0,
  315. })
  316. response = self.patch(
  317. self.api_link, [
  318. {
  319. 'op': 'replace',
  320. 'path': 'is-unapproved',
  321. 'value': False,
  322. },
  323. ]
  324. )
  325. self.assertEqual(response.status_code, 400)
  326. response_json = response.json()
  327. self.assertEqual(
  328. response_json['detail'][0],
  329. "This category is closed. You can't approve posts in it.",
  330. )
  331. self.refresh_post()
  332. self.assertTrue(self.post.is_unapproved)
  333. def test_approve_first_post(self):
  334. """api approve first post fails"""
  335. self.post.is_unapproved = True
  336. self.post.save()
  337. self.thread.set_first_post(self.post)
  338. self.thread.save()
  339. self.override_acl({'can_approve_content': 1})
  340. response = self.patch(
  341. self.api_link, [
  342. {
  343. 'op': 'replace',
  344. 'path': 'is-unapproved',
  345. 'value': False,
  346. },
  347. ]
  348. )
  349. self.assertEqual(response.status_code, 400)
  350. response_json = response.json()
  351. self.assertEqual(response_json['detail'][0], "You can't approve thread's first post.")
  352. self.refresh_post()
  353. self.assertTrue(self.post.is_unapproved)
  354. def test_approve_hidden_post(self):
  355. """api approve hidden post fails"""
  356. self.post.is_unapproved = True
  357. self.post.is_hidden = True
  358. self.post.save()
  359. self.override_acl({'can_approve_content': 1})
  360. response = self.patch(
  361. self.api_link, [
  362. {
  363. 'op': 'replace',
  364. 'path': 'is-unapproved',
  365. 'value': False,
  366. },
  367. ]
  368. )
  369. self.assertEqual(response.status_code, 400)
  370. response_json = response.json()
  371. self.assertEqual(
  372. response_json['detail'][0], "You can't approve posts the content you can't see."
  373. )
  374. self.refresh_post()
  375. self.assertTrue(self.post.is_unapproved)
  376. class PostHideApiTests(ThreadPostPatchApiTestCase):
  377. def test_hide_post(self):
  378. """api makes it possible to hide post"""
  379. self.override_acl({'can_hide_posts': 1})
  380. response = self.patch(
  381. self.api_link, [
  382. {
  383. 'op': 'replace',
  384. 'path': 'is-hidden',
  385. 'value': True,
  386. },
  387. ]
  388. )
  389. self.assertEqual(response.status_code, 200)
  390. reponse_json = response.json()
  391. self.assertTrue(reponse_json['is_hidden'])
  392. self.refresh_post()
  393. self.assertTrue(self.post.is_hidden)
  394. def test_hide_own_post(self):
  395. """api makes it possible to hide owned post"""
  396. self.override_acl({'can_hide_own_posts': 1})
  397. response = self.patch(
  398. self.api_link, [
  399. {
  400. 'op': 'replace',
  401. 'path': 'is-hidden',
  402. 'value': True,
  403. },
  404. ]
  405. )
  406. self.assertEqual(response.status_code, 200)
  407. reponse_json = response.json()
  408. self.assertTrue(reponse_json['is_hidden'])
  409. self.refresh_post()
  410. self.assertTrue(self.post.is_hidden)
  411. def test_hide_post_no_permission(self):
  412. """api hide post with no permission fails"""
  413. self.override_acl({'can_hide_posts': 0})
  414. response = self.patch(
  415. self.api_link, [
  416. {
  417. 'op': 'replace',
  418. 'path': 'is-hidden',
  419. 'value': True,
  420. },
  421. ]
  422. )
  423. self.assertEqual(response.status_code, 400)
  424. response_json = response.json()
  425. self.assertEqual(response_json['detail'][0], "You can't hide posts in this category.")
  426. self.refresh_post()
  427. self.assertFalse(self.post.is_hidden)
  428. def test_hide_own_protected_post(self):
  429. """api validates if we are trying to hide protected post"""
  430. self.post.is_protected = True
  431. self.post.save()
  432. self.override_acl({'can_protect_posts': 0, 'can_hide_own_posts': 1})
  433. response = self.patch(
  434. self.api_link, [
  435. {
  436. 'op': 'replace',
  437. 'path': 'is-hidden',
  438. 'value': True,
  439. },
  440. ]
  441. )
  442. self.assertEqual(response.status_code, 400)
  443. response_json = response.json()
  444. self.assertEqual(response_json['detail'][0], "This post is protected. You can't hide it.")
  445. self.refresh_post()
  446. self.assertFalse(self.post.is_hidden)
  447. def test_hide_other_user_post(self):
  448. """api validates post ownership when hiding"""
  449. self.post.poster = None
  450. self.post.save()
  451. self.override_acl({'can_hide_own_posts': 1})
  452. response = self.patch(
  453. self.api_link, [
  454. {
  455. 'op': 'replace',
  456. 'path': 'is-hidden',
  457. 'value': True,
  458. },
  459. ]
  460. )
  461. self.assertEqual(response.status_code, 400)
  462. response_json = response.json()
  463. self.assertEqual(
  464. response_json['detail'][0], "You can't hide other users posts in this category."
  465. )
  466. self.refresh_post()
  467. self.assertFalse(self.post.is_hidden)
  468. def test_hide_own_post_after_edit_time(self):
  469. """api validates if we are trying to hide post after edit time"""
  470. self.post.posted_on = timezone.now() - timedelta(minutes=10)
  471. self.post.save()
  472. self.override_acl({'post_edit_time': 1, 'can_hide_own_posts': 1})
  473. response = self.patch(
  474. self.api_link, [
  475. {
  476. 'op': 'replace',
  477. 'path': 'is-hidden',
  478. 'value': True,
  479. },
  480. ]
  481. )
  482. self.assertEqual(response.status_code, 400)
  483. response_json = response.json()
  484. self.assertEqual(
  485. response_json['detail'][0], "You can't hide posts that are older than 1 minute."
  486. )
  487. self.refresh_post()
  488. self.assertFalse(self.post.is_hidden)
  489. def test_hide_post_in_closed_thread(self):
  490. """api validates if we are trying to hide post in closed thread"""
  491. self.thread.is_closed = True
  492. self.thread.save()
  493. self.override_acl({'can_hide_own_posts': 1})
  494. response = self.patch(
  495. self.api_link, [
  496. {
  497. 'op': 'replace',
  498. 'path': 'is-hidden',
  499. 'value': True,
  500. },
  501. ]
  502. )
  503. self.assertEqual(response.status_code, 400)
  504. response_json = response.json()
  505. self.assertEqual(
  506. response_json['detail'][0], "This thread is closed. You can't hide posts in it."
  507. )
  508. self.refresh_post()
  509. self.assertFalse(self.post.is_hidden)
  510. def test_hide_post_in_closed_category(self):
  511. """api validates if we are trying to hide post in closed category"""
  512. self.category.is_closed = True
  513. self.category.save()
  514. self.override_acl({'can_hide_own_posts': 1})
  515. response = self.patch(
  516. self.api_link, [
  517. {
  518. 'op': 'replace',
  519. 'path': 'is-hidden',
  520. 'value': True,
  521. },
  522. ]
  523. )
  524. self.assertEqual(response.status_code, 400)
  525. response_json = response.json()
  526. self.assertEqual(
  527. response_json['detail'][0], "This category is closed. You can't hide posts in it."
  528. )
  529. self.refresh_post()
  530. self.assertFalse(self.post.is_hidden)
  531. def test_hide_first_post(self):
  532. """api hide first post fails"""
  533. self.thread.set_first_post(self.post)
  534. self.thread.save()
  535. self.override_acl({'can_hide_posts': 1})
  536. response = self.patch(
  537. self.api_link, [
  538. {
  539. 'op': 'replace',
  540. 'path': 'is-hidden',
  541. 'value': True,
  542. },
  543. ]
  544. )
  545. self.assertEqual(response.status_code, 400)
  546. response_json = response.json()
  547. self.assertEqual(response_json['detail'][0], "You can't hide thread's first post.")
  548. def test_hide_best_answer(self):
  549. """api hide first post fails"""
  550. self.thread.set_best_answer(self.user, self.post)
  551. self.thread.save()
  552. self.override_acl({'can_hide_posts': 2})
  553. response = self.patch(
  554. self.api_link, [
  555. {
  556. 'op': 'replace',
  557. 'path': 'is-hidden',
  558. 'value': True,
  559. },
  560. ]
  561. )
  562. self.assertEqual(response.status_code, 400)
  563. self.assertEqual(response.json(), {
  564. 'id': self.post.id,
  565. 'detail': ["You can't hide this post because its marked as best answer."],
  566. })
  567. class PostUnhideApiTests(ThreadPostPatchApiTestCase):
  568. def test_show_post(self):
  569. """api makes it possible to unhide post"""
  570. self.post.is_hidden = True
  571. self.post.save()
  572. self.refresh_post()
  573. self.assertTrue(self.post.is_hidden)
  574. self.override_acl({'can_hide_posts': 1})
  575. response = self.patch(
  576. self.api_link, [
  577. {
  578. 'op': 'replace',
  579. 'path': 'is-hidden',
  580. 'value': False,
  581. },
  582. ]
  583. )
  584. self.assertEqual(response.status_code, 200)
  585. reponse_json = response.json()
  586. self.assertFalse(reponse_json['is_hidden'])
  587. self.refresh_post()
  588. self.assertFalse(self.post.is_hidden)
  589. def test_show_own_post(self):
  590. """api makes it possible to unhide owned post"""
  591. self.post.is_hidden = True
  592. self.post.save()
  593. self.refresh_post()
  594. self.assertTrue(self.post.is_hidden)
  595. self.override_acl({'can_hide_own_posts': 1})
  596. response = self.patch(
  597. self.api_link, [
  598. {
  599. 'op': 'replace',
  600. 'path': 'is-hidden',
  601. 'value': False,
  602. },
  603. ]
  604. )
  605. self.assertEqual(response.status_code, 200)
  606. reponse_json = response.json()
  607. self.assertFalse(reponse_json['is_hidden'])
  608. self.refresh_post()
  609. self.assertFalse(self.post.is_hidden)
  610. def test_show_post_no_permission(self):
  611. """api unhide post with no permission fails"""
  612. self.post.is_hidden = True
  613. self.post.save()
  614. self.refresh_post()
  615. self.assertTrue(self.post.is_hidden)
  616. self.override_acl({'can_hide_posts': 0})
  617. response = self.patch(
  618. self.api_link, [
  619. {
  620. 'op': 'replace',
  621. 'path': 'is-hidden',
  622. 'value': False,
  623. },
  624. ]
  625. )
  626. self.assertEqual(response.status_code, 400)
  627. response_json = response.json()
  628. self.assertEqual(response_json['detail'][0], "You can't reveal posts in this category.")
  629. self.refresh_post()
  630. self.assertTrue(self.post.is_hidden)
  631. def test_show_own_protected_post(self):
  632. """api validates if we are trying to reveal protected post"""
  633. self.post.is_hidden = True
  634. self.post.save()
  635. self.override_acl({'can_protect_posts': 0, 'can_hide_own_posts': 1})
  636. self.post.is_protected = True
  637. self.post.save()
  638. response = self.patch(
  639. self.api_link, [
  640. {
  641. 'op': 'replace',
  642. 'path': 'is-hidden',
  643. 'value': False,
  644. },
  645. ]
  646. )
  647. self.assertEqual(response.status_code, 400)
  648. response_json = response.json()
  649. self.assertEqual(
  650. response_json['detail'][0], "This post is protected. You can't reveal it."
  651. )
  652. self.refresh_post()
  653. self.assertTrue(self.post.is_hidden)
  654. def test_show_other_user_post(self):
  655. """api validates post ownership when revealing"""
  656. self.post.is_hidden = True
  657. self.post.poster = None
  658. self.post.save()
  659. self.override_acl({'can_hide_own_posts': 1})
  660. response = self.patch(
  661. self.api_link, [
  662. {
  663. 'op': 'replace',
  664. 'path': 'is-hidden',
  665. 'value': False,
  666. },
  667. ]
  668. )
  669. self.assertEqual(response.status_code, 400)
  670. response_json = response.json()
  671. self.assertEqual(
  672. response_json['detail'][0], "You can't reveal other users posts in this category."
  673. )
  674. self.refresh_post()
  675. self.assertTrue(self.post.is_hidden)
  676. def test_show_own_post_after_edit_time(self):
  677. """api validates if we are trying to reveal post after edit time"""
  678. self.post.is_hidden = True
  679. self.post.posted_on = timezone.now() - timedelta(minutes=10)
  680. self.post.save()
  681. self.override_acl({'post_edit_time': 1, 'can_hide_own_posts': 1})
  682. response = self.patch(
  683. self.api_link, [
  684. {
  685. 'op': 'replace',
  686. 'path': 'is-hidden',
  687. 'value': False,
  688. },
  689. ]
  690. )
  691. self.assertEqual(response.status_code, 400)
  692. response_json = response.json()
  693. self.assertEqual(
  694. response_json['detail'][0], "You can't reveal posts that are older than 1 minute."
  695. )
  696. self.refresh_post()
  697. self.assertTrue(self.post.is_hidden)
  698. def test_show_post_in_closed_thread(self):
  699. """api validates if we are trying to reveal post in closed thread"""
  700. self.thread.is_closed = True
  701. self.thread.save()
  702. self.post.is_hidden = True
  703. self.post.save()
  704. self.override_acl({'can_hide_own_posts': 1})
  705. response = self.patch(
  706. self.api_link, [
  707. {
  708. 'op': 'replace',
  709. 'path': 'is-hidden',
  710. 'value': False,
  711. },
  712. ]
  713. )
  714. self.assertEqual(response.status_code, 400)
  715. response_json = response.json()
  716. self.assertEqual(
  717. response_json['detail'][0], "This thread is closed. You can't reveal posts in it."
  718. )
  719. self.refresh_post()
  720. self.assertTrue(self.post.is_hidden)
  721. def test_show_post_in_closed_category(self):
  722. """api validates if we are trying to reveal post in closed category"""
  723. self.category.is_closed = True
  724. self.category.save()
  725. self.post.is_hidden = True
  726. self.post.save()
  727. self.override_acl({'can_hide_own_posts': 1})
  728. response = self.patch(
  729. self.api_link, [
  730. {
  731. 'op': 'replace',
  732. 'path': 'is-hidden',
  733. 'value': False,
  734. },
  735. ]
  736. )
  737. self.assertEqual(response.status_code, 400)
  738. response_json = response.json()
  739. self.assertEqual(
  740. response_json['detail'][0], "This category is closed. You can't reveal posts in it."
  741. )
  742. self.refresh_post()
  743. self.assertTrue(self.post.is_hidden)
  744. def test_show_first_post(self):
  745. """api unhide first post fails"""
  746. self.thread.set_first_post(self.post)
  747. self.thread.save()
  748. self.override_acl({'can_hide_posts': 1})
  749. response = self.patch(
  750. self.api_link, [
  751. {
  752. 'op': 'replace',
  753. 'path': 'is-hidden',
  754. 'value': False,
  755. },
  756. ]
  757. )
  758. self.assertEqual(response.status_code, 400)
  759. response_json = response.json()
  760. self.assertEqual(response_json['detail'][0], "You can't reveal thread's first post.")
  761. class PostLikeApiTests(ThreadPostPatchApiTestCase):
  762. def test_like_no_see_permission(self):
  763. """api validates user's permission to see posts likes"""
  764. self.override_acl({'can_see_posts_likes': 0})
  765. response = self.patch(
  766. self.api_link, [
  767. {
  768. 'op': 'replace',
  769. 'path': 'is-liked',
  770. 'value': True,
  771. },
  772. ]
  773. )
  774. self.assertContains(response, "You can't like posts in this category.", status_code=400)
  775. def test_like_no_like_permission(self):
  776. """api validates user's permission to see posts likes"""
  777. self.override_acl({'can_like_posts': False})
  778. response = self.patch(
  779. self.api_link, [
  780. {
  781. 'op': 'replace',
  782. 'path': 'is-liked',
  783. 'value': True,
  784. },
  785. ]
  786. )
  787. self.assertContains(response, "You can't like posts in this category.", status_code=400)
  788. def test_like_post(self):
  789. """api adds user like to post"""
  790. response = self.patch(
  791. self.api_link, [
  792. {
  793. 'op': 'replace',
  794. 'path': 'is-liked',
  795. 'value': True,
  796. },
  797. ]
  798. )
  799. self.assertEqual(response.status_code, 200)
  800. response_json = response.json()
  801. self.assertEqual(response_json['likes'], 1)
  802. self.assertEqual(response_json['is_liked'], True)
  803. self.assertEqual(
  804. response_json['last_likes'], [
  805. {
  806. 'id': self.user.id,
  807. 'username': self.user.username,
  808. },
  809. ]
  810. )
  811. post = Post.objects.get(pk=self.post.pk)
  812. self.assertEqual(post.likes, response_json['likes'])
  813. self.assertEqual(post.last_likes, response_json['last_likes'])
  814. def test_like_liked_post(self):
  815. """api adds user like to post"""
  816. testutils.like_post(self.post, username='Myo')
  817. testutils.like_post(self.post, username='Mugi')
  818. testutils.like_post(self.post, username='Bob')
  819. testutils.like_post(self.post, username='Miku')
  820. response = self.patch(
  821. self.api_link, [
  822. {
  823. 'op': 'replace',
  824. 'path': 'is-liked',
  825. 'value': True,
  826. },
  827. ]
  828. )
  829. self.assertEqual(response.status_code, 200)
  830. response_json = response.json()
  831. self.assertEqual(response_json['likes'], 5)
  832. self.assertEqual(response_json['is_liked'], True)
  833. self.assertEqual(
  834. response_json['last_likes'], [
  835. {
  836. 'id': self.user.id,
  837. 'username': self.user.username
  838. },
  839. {
  840. 'id': None,
  841. 'username': 'Miku',
  842. },
  843. {
  844. 'id': None,
  845. 'username': 'Bob',
  846. },
  847. {
  848. 'id': None,
  849. 'username': 'Mugi',
  850. },
  851. ]
  852. )
  853. post = Post.objects.get(pk=self.post.pk)
  854. self.assertEqual(post.likes, response_json['likes'])
  855. self.assertEqual(post.last_likes, response_json['last_likes'])
  856. def test_unlike_post(self):
  857. """api removes user like from post"""
  858. testutils.like_post(self.post, self.user)
  859. response = self.patch(
  860. self.api_link, [
  861. {
  862. 'op': 'replace',
  863. 'path': 'is-liked',
  864. 'value': False,
  865. },
  866. ]
  867. )
  868. self.assertEqual(response.status_code, 200)
  869. response_json = response.json()
  870. self.assertEqual(response_json['likes'], 0)
  871. self.assertEqual(response_json['is_liked'], False)
  872. self.assertEqual(response_json['last_likes'], [])
  873. post = Post.objects.get(pk=self.post.pk)
  874. self.assertEqual(post.likes, response_json['likes'])
  875. self.assertEqual(post.last_likes, response_json['last_likes'])
  876. def test_like_post_no_change(self):
  877. """api does no state change if we are linking liked post"""
  878. testutils.like_post(self.post, self.user)
  879. response = self.patch(
  880. self.api_link, [
  881. {
  882. 'op': 'replace',
  883. 'path': 'is-liked',
  884. 'value': True,
  885. },
  886. ]
  887. )
  888. self.assertEqual(response.status_code, 200)
  889. response_json = response.json()
  890. self.assertEqual(response_json['likes'], 1)
  891. self.assertEqual(response_json['is_liked'], True)
  892. self.assertEqual(
  893. response_json['last_likes'], [
  894. {
  895. 'id': self.user.id,
  896. 'username': self.user.username,
  897. },
  898. ]
  899. )
  900. post = Post.objects.get(pk=self.post.pk)
  901. self.assertEqual(post.likes, response_json['likes'])
  902. self.assertEqual(post.last_likes, response_json['last_likes'])
  903. def test_unlike_post_no_change(self):
  904. """api does no state change if we are unlinking unliked post"""
  905. response = self.patch(
  906. self.api_link, [
  907. {
  908. 'op': 'replace',
  909. 'path': 'is-liked',
  910. 'value': False,
  911. },
  912. ]
  913. )
  914. self.assertEqual(response.status_code, 200)
  915. response_json = response.json()
  916. self.assertEqual(response_json['likes'], 0)
  917. self.assertEqual(response_json['is_liked'], False)
  918. self.assertEqual(response_json['last_likes'], [])
  919. class ThreadEventPatchApiTestCase(ThreadPostPatchApiTestCase):
  920. def setUp(self):
  921. super(ThreadEventPatchApiTestCase, self).setUp()
  922. self.event = testutils.reply_thread(self.thread, poster=self.user, is_event=True)
  923. self.api_link = reverse(
  924. 'misago:api:thread-post-detail',
  925. kwargs={
  926. 'thread_pk': self.thread.pk,
  927. 'pk': self.event.pk,
  928. }
  929. )
  930. def refresh_event(self):
  931. self.event = self.thread.post_set.get(pk=self.event.pk)
  932. class EventAnonPatchApiTests(ThreadEventPatchApiTestCase):
  933. def test_anonymous_user(self):
  934. """anonymous users can't change event state"""
  935. self.logout_user()
  936. response = self.patch(self.api_link, [
  937. {
  938. 'op': 'add',
  939. 'path': 'acl',
  940. 'value': True,
  941. },
  942. ])
  943. self.assertEqual(response.status_code, 403)
  944. class EventAddAclApiTests(ThreadEventPatchApiTestCase):
  945. def test_add_acl_true(self):
  946. """api adds current event's acl to response"""
  947. response = self.patch(self.api_link, [
  948. {
  949. 'op': 'add',
  950. 'path': 'acl',
  951. 'value': True,
  952. },
  953. ])
  954. self.assertEqual(response.status_code, 200)
  955. response_json = response.json()
  956. self.assertTrue(response_json['acl'])
  957. def test_add_acl_false(self):
  958. """if value is false, api won't add acl to the response, but will set empty key"""
  959. response = self.patch(self.api_link, [
  960. {
  961. 'op': 'add',
  962. 'path': 'acl',
  963. 'value': False,
  964. },
  965. ])
  966. self.assertEqual(response.status_code, 200)
  967. response_json = response.json()
  968. self.assertIsNone(response_json['acl'])
  969. response = self.patch(self.api_link, [
  970. {
  971. 'op': 'add',
  972. 'path': 'acl',
  973. 'value': True,
  974. },
  975. ])
  976. self.assertEqual(response.status_code, 200)
  977. class EventHideApiTests(ThreadEventPatchApiTestCase):
  978. def test_hide_event(self):
  979. """api makes it possible to hide event"""
  980. self.override_acl({'can_hide_events': 1})
  981. response = self.patch(
  982. self.api_link, [
  983. {
  984. 'op': 'replace',
  985. 'path': 'is-hidden',
  986. 'value': True,
  987. },
  988. ]
  989. )
  990. self.assertEqual(response.status_code, 200)
  991. self.refresh_event()
  992. self.assertTrue(self.event.is_hidden)
  993. def test_show_event(self):
  994. """api makes it possible to unhide event"""
  995. self.event.is_hidden = True
  996. self.event.save()
  997. self.refresh_event()
  998. self.assertTrue(self.event.is_hidden)
  999. self.override_acl({'can_hide_events': 1})
  1000. response = self.patch(
  1001. self.api_link, [
  1002. {
  1003. 'op': 'replace',
  1004. 'path': 'is-hidden',
  1005. 'value': False,
  1006. },
  1007. ]
  1008. )
  1009. self.assertEqual(response.status_code, 200)
  1010. self.refresh_event()
  1011. self.assertFalse(self.event.is_hidden)
  1012. def test_hide_event_no_permission(self):
  1013. """api hide event with no permission fails"""
  1014. self.override_acl({'can_hide_events': 0})
  1015. response = self.patch(
  1016. self.api_link, [
  1017. {
  1018. 'op': 'replace',
  1019. 'path': 'is-hidden',
  1020. 'value': True,
  1021. },
  1022. ]
  1023. )
  1024. self.assertEqual(response.status_code, 400)
  1025. response_json = response.json()
  1026. self.assertEqual(
  1027. response_json['detail'][0], "You can't hide events in this category."
  1028. )
  1029. self.refresh_event()
  1030. self.assertFalse(self.event.is_hidden)
  1031. def test_hide_event_closed_thread_no_permission(self):
  1032. """api hide event in closed thread with no permission fails"""
  1033. self.override_acl({
  1034. 'can_hide_events': 1,
  1035. 'can_close_threads': 0,
  1036. })
  1037. self.thread.is_closed = True
  1038. self.thread.save()
  1039. response = self.patch(
  1040. self.api_link, [
  1041. {
  1042. 'op': 'replace',
  1043. 'path': 'is-hidden',
  1044. 'value': True,
  1045. },
  1046. ]
  1047. )
  1048. self.assertEqual(response.status_code, 400)
  1049. response_json = response.json()
  1050. self.assertEqual(
  1051. response_json['detail'][0], "This thread is closed. You can't hide events in it."
  1052. )
  1053. self.refresh_event()
  1054. self.assertFalse(self.event.is_hidden)
  1055. def test_hide_event_closed_category_no_permission(self):
  1056. """api hide event in closed category with no permission fails"""
  1057. self.override_acl({
  1058. 'can_hide_events': 1,
  1059. 'can_close_threads': 0,
  1060. })
  1061. self.category.is_closed = True
  1062. self.category.save()
  1063. response = self.patch(
  1064. self.api_link, [
  1065. {
  1066. 'op': 'replace',
  1067. 'path': 'is-hidden',
  1068. 'value': True,
  1069. },
  1070. ]
  1071. )
  1072. self.assertEqual(response.status_code, 400)
  1073. response_json = response.json()
  1074. self.assertEqual(
  1075. response_json['detail'][0], "This category is closed. You can't hide events in it."
  1076. )
  1077. self.refresh_event()
  1078. self.assertFalse(self.event.is_hidden)
  1079. def test_show_event_no_permission(self):
  1080. """api unhide event with no permission fails"""
  1081. self.event.is_hidden = True
  1082. self.event.save()
  1083. self.refresh_event()
  1084. self.assertTrue(self.event.is_hidden)
  1085. self.override_acl({'can_hide_events': 0})
  1086. response = self.patch(
  1087. self.api_link, [
  1088. {
  1089. 'op': 'replace',
  1090. 'path': 'is-hidden',
  1091. 'value': False,
  1092. },
  1093. ]
  1094. )
  1095. self.assertEqual(response.status_code, 404)
  1096. def test_show_event_closed_thread_no_permission(self):
  1097. """api show event in closed thread with no permission fails"""
  1098. self.event.is_hidden = True
  1099. self.event.save()
  1100. self.override_acl({
  1101. 'can_hide_events': 1,
  1102. 'can_close_threads': 0,
  1103. })
  1104. self.thread.is_closed = True
  1105. self.thread.save()
  1106. response = self.patch(
  1107. self.api_link, [
  1108. {
  1109. 'op': 'replace',
  1110. 'path': 'is-hidden',
  1111. 'value': False,
  1112. },
  1113. ]
  1114. )
  1115. self.assertEqual(response.status_code, 400)
  1116. response_json = response.json()
  1117. self.assertEqual(
  1118. response_json['detail'][0], "This thread is closed. You can't reveal events in it."
  1119. )
  1120. self.refresh_event()
  1121. self.assertTrue(self.event.is_hidden)
  1122. def test_show_event_closed_category_no_permission(self):
  1123. """api show event in closed category with no permission fails"""
  1124. self.event.is_hidden = True
  1125. self.event.save()
  1126. self.override_acl({
  1127. 'can_hide_events': 1,
  1128. 'can_close_threads': 0,
  1129. })
  1130. self.category.is_closed = True
  1131. self.category.save()
  1132. response = self.patch(
  1133. self.api_link, [
  1134. {
  1135. 'op': 'replace',
  1136. 'path': 'is-hidden',
  1137. 'value': False,
  1138. },
  1139. ]
  1140. )
  1141. self.assertEqual(response.status_code, 400)
  1142. response_json = response.json()
  1143. self.assertEqual(
  1144. response_json['detail'][0], "This category is closed. You can't reveal events in it."
  1145. )
  1146. self.refresh_event()
  1147. self.assertTrue(self.event.is_hidden)