test_thread_postpatch_api.py 40 KB

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