test_thread_postpatch_api.py 39 KB

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