test_thread_postpatch_api.py 41 KB

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