test_thread_postpatch_api.py 41 KB

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