test_thread_postpatch_api.py 41 KB

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