test_thread_postpatch_api.py 40 KB

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