test_thread_postpatch_api.py 38 KB

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