test_thread_postpatch_api.py 41 KB

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