test_thread_postpatch_api.py 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040
  1. import json
  2. from datetime import timedelta
  3. from django.urls import reverse
  4. from django.utils import timezone
  5. from misago.categories.models import Category
  6. from misago.threads import test
  7. from misago.threads.models import Post, Thread
  8. from misago.threads.test import patch_category_acl
  9. from misago.users.test 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 = test.post_thread(category=self.category)
  15. self.post = test.reply_thread(self.thread, poster=self.user)
  16. self.api_link = reverse(
  17. "misago:api:thread-post-detail",
  18. kwargs={"thread_pk": self.thread.pk, "pk": self.post.pk},
  19. )
  20. def patch(self, api_link, ops):
  21. return self.client.patch(
  22. api_link, json.dumps(ops), content_type="application/json"
  23. )
  24. class PostAddAclApiTests(ThreadPostPatchApiTestCase):
  25. def test_add_acl_true(self):
  26. """api adds current event's acl to response"""
  27. response = self.patch(
  28. self.api_link, [{"op": "add", "path": "acl", "value": True}]
  29. )
  30. self.assertEqual(response.status_code, 200)
  31. response_json = response.json()
  32. self.assertTrue(response_json["acl"])
  33. def test_add_acl_false(self):
  34. """if value is false, api won't add acl to the response, but will set empty key"""
  35. response = self.patch(
  36. self.api_link, [{"op": "add", "path": "acl", "value": False}]
  37. )
  38. self.assertEqual(response.status_code, 200)
  39. response_json = response.json()
  40. self.assertIsNone(response_json["acl"])
  41. class PostProtectApiTests(ThreadPostPatchApiTestCase):
  42. @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": True})
  43. def test_protect_post(self):
  44. """api makes it possible to protect post"""
  45. response = self.patch(
  46. self.api_link, [{"op": "replace", "path": "is-protected", "value": True}]
  47. )
  48. self.assertEqual(response.status_code, 200)
  49. reponse_json = response.json()
  50. self.assertTrue(reponse_json["is_protected"])
  51. self.post.refresh_from_db()
  52. self.assertTrue(self.post.is_protected)
  53. @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": True})
  54. def test_unprotect_post(self):
  55. """api makes it possible to unprotect protected post"""
  56. self.post.is_protected = True
  57. self.post.save()
  58. response = self.patch(
  59. self.api_link, [{"op": "replace", "path": "is-protected", "value": False}]
  60. )
  61. self.assertEqual(response.status_code, 200)
  62. reponse_json = response.json()
  63. self.assertFalse(reponse_json["is_protected"])
  64. self.post.refresh_from_db()
  65. self.assertFalse(self.post.is_protected)
  66. @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": True})
  67. def test_protect_best_answer(self):
  68. """api makes it possible to protect post"""
  69. self.thread.set_best_answer(self.user, self.post)
  70. self.thread.save()
  71. self.assertFalse(self.thread.best_answer_is_protected)
  72. response = self.patch(
  73. self.api_link, [{"op": "replace", "path": "is-protected", "value": True}]
  74. )
  75. self.assertEqual(response.status_code, 200)
  76. reponse_json = response.json()
  77. self.assertTrue(reponse_json["is_protected"])
  78. self.post.refresh_from_db()
  79. self.assertTrue(self.post.is_protected)
  80. self.thread.refresh_from_db()
  81. self.assertTrue(self.thread.best_answer_is_protected)
  82. @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": True})
  83. def test_unprotect_best_answer(self):
  84. """api makes it possible to unprotect protected post"""
  85. self.post.is_protected = True
  86. self.post.save()
  87. self.thread.set_best_answer(self.user, self.post)
  88. self.thread.save()
  89. self.assertTrue(self.thread.best_answer_is_protected)
  90. response = self.patch(
  91. self.api_link, [{"op": "replace", "path": "is-protected", "value": False}]
  92. )
  93. self.assertEqual(response.status_code, 200)
  94. reponse_json = response.json()
  95. self.assertFalse(reponse_json["is_protected"])
  96. self.post.refresh_from_db()
  97. self.assertFalse(self.post.is_protected)
  98. self.thread.refresh_from_db()
  99. self.assertFalse(self.thread.best_answer_is_protected)
  100. @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": False})
  101. def test_protect_post_no_permission(self):
  102. """api validates permission to protect post"""
  103. response = self.patch(
  104. self.api_link, [{"op": "replace", "path": "is-protected", "value": True}]
  105. )
  106. self.assertEqual(response.status_code, 400)
  107. response_json = response.json()
  108. self.assertEqual(
  109. response_json["detail"][0], "You can't protect posts in this category."
  110. )
  111. self.post.refresh_from_db()
  112. self.assertFalse(self.post.is_protected)
  113. @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": False})
  114. def test_unprotect_post_no_permission(self):
  115. """api validates permission to unprotect post"""
  116. self.post.is_protected = True
  117. self.post.save()
  118. response = self.patch(
  119. self.api_link, [{"op": "replace", "path": "is-protected", "value": False}]
  120. )
  121. self.assertEqual(response.status_code, 400)
  122. response_json = response.json()
  123. self.assertEqual(
  124. response_json["detail"][0], "You can't protect posts in this category."
  125. )
  126. self.post.refresh_from_db()
  127. self.assertTrue(self.post.is_protected)
  128. @patch_category_acl({"can_edit_posts": 0, "can_protect_posts": True})
  129. def test_protect_post_not_editable(self):
  130. """api validates if we can edit post we want to protect"""
  131. response = self.patch(
  132. self.api_link, [{"op": "replace", "path": "is-protected", "value": True}]
  133. )
  134. self.assertEqual(response.status_code, 400)
  135. response_json = response.json()
  136. self.assertEqual(
  137. response_json["detail"][0], "You can't protect posts you can't edit."
  138. )
  139. self.post.refresh_from_db()
  140. self.assertFalse(self.post.is_protected)
  141. @patch_category_acl({"can_edit_posts": 0, "can_protect_posts": True})
  142. def test_unprotect_post_not_editable(self):
  143. """api validates if we can edit post we want to protect"""
  144. self.post.is_protected = True
  145. self.post.save()
  146. response = self.patch(
  147. self.api_link, [{"op": "replace", "path": "is-protected", "value": False}]
  148. )
  149. self.assertEqual(response.status_code, 400)
  150. response_json = response.json()
  151. self.assertEqual(
  152. response_json["detail"][0], "You can't protect posts you can't edit."
  153. )
  154. self.post.refresh_from_db()
  155. self.assertTrue(self.post.is_protected)
  156. class PostApproveApiTests(ThreadPostPatchApiTestCase):
  157. @patch_category_acl({"can_approve_content": True})
  158. def test_approve_post(self):
  159. """api makes it possible to approve post"""
  160. self.post.is_unapproved = True
  161. self.post.save()
  162. response = self.patch(
  163. self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
  164. )
  165. self.assertEqual(response.status_code, 200)
  166. reponse_json = response.json()
  167. self.assertFalse(reponse_json["is_unapproved"])
  168. self.post.refresh_from_db()
  169. self.assertFalse(self.post.is_unapproved)
  170. @patch_category_acl({"can_approve_content": True})
  171. def test_unapprove_post(self):
  172. """unapproving posts is not supported by api"""
  173. response = self.patch(
  174. self.api_link, [{"op": "replace", "path": "is-unapproved", "value": True}]
  175. )
  176. self.assertEqual(response.status_code, 400)
  177. response_json = response.json()
  178. self.assertEqual(
  179. response_json["detail"][0], "Content approval can't be reversed."
  180. )
  181. self.post.refresh_from_db()
  182. self.assertFalse(self.post.is_unapproved)
  183. @patch_category_acl({"can_approve_content": False})
  184. def test_approve_post_no_permission(self):
  185. """api validates approval permission"""
  186. self.post.is_unapproved = True
  187. self.post.save()
  188. response = self.patch(
  189. self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
  190. )
  191. self.assertEqual(response.status_code, 400)
  192. response_json = response.json()
  193. self.assertEqual(
  194. response_json["detail"][0], "You can't approve posts in this category."
  195. )
  196. self.post.refresh_from_db()
  197. self.assertTrue(self.post.is_unapproved)
  198. @patch_category_acl({"can_approve_content": True, "can_close_threads": False})
  199. def test_approve_post_closed_thread_no_permission(self):
  200. """api validates approval permission in closed threads"""
  201. self.post.is_unapproved = True
  202. self.post.save()
  203. self.thread.is_closed = True
  204. self.thread.save()
  205. response = self.patch(
  206. self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
  207. )
  208. self.assertEqual(response.status_code, 400)
  209. response_json = response.json()
  210. self.assertEqual(
  211. response_json["detail"][0],
  212. "This thread is closed. You can't approve posts in it.",
  213. )
  214. self.post.refresh_from_db()
  215. self.assertTrue(self.post.is_unapproved)
  216. @patch_category_acl({"can_approve_content": True, "can_close_threads": False})
  217. def test_approve_post_closed_category_no_permission(self):
  218. """api validates approval permission in closed categories"""
  219. self.post.is_unapproved = True
  220. self.post.save()
  221. self.category.is_closed = True
  222. self.category.save()
  223. response = self.patch(
  224. self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
  225. )
  226. self.assertEqual(response.status_code, 400)
  227. response_json = response.json()
  228. self.assertEqual(
  229. response_json["detail"][0],
  230. "This category is closed. You can't approve posts in it.",
  231. )
  232. self.post.refresh_from_db()
  233. self.assertTrue(self.post.is_unapproved)
  234. @patch_category_acl({"can_approve_content": True})
  235. def test_approve_first_post(self):
  236. """api approve first post fails"""
  237. self.post.is_unapproved = True
  238. self.post.save()
  239. self.thread.set_first_post(self.post)
  240. self.thread.save()
  241. response = self.patch(
  242. self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
  243. )
  244. self.assertEqual(response.status_code, 400)
  245. response_json = response.json()
  246. self.assertEqual(
  247. response_json["detail"][0], "You can't approve thread's first post."
  248. )
  249. self.post.refresh_from_db()
  250. self.assertTrue(self.post.is_unapproved)
  251. @patch_category_acl({"can_approve_content": True})
  252. def test_approve_hidden_post(self):
  253. """api approve hidden post fails"""
  254. self.post.is_unapproved = True
  255. self.post.is_hidden = True
  256. self.post.save()
  257. response = self.patch(
  258. self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
  259. )
  260. self.assertEqual(response.status_code, 400)
  261. response_json = response.json()
  262. self.assertEqual(
  263. response_json["detail"][0],
  264. "You can't approve posts the content you can't see.",
  265. )
  266. self.post.refresh_from_db()
  267. self.assertTrue(self.post.is_unapproved)
  268. class PostHideApiTests(ThreadPostPatchApiTestCase):
  269. @patch_category_acl({"can_hide_posts": 1})
  270. def test_hide_post(self):
  271. """api makes it possible to hide post"""
  272. response = self.patch(
  273. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  274. )
  275. self.assertEqual(response.status_code, 200)
  276. reponse_json = response.json()
  277. self.assertTrue(reponse_json["is_hidden"])
  278. self.post.refresh_from_db()
  279. self.assertTrue(self.post.is_hidden)
  280. @patch_category_acl({"can_hide_posts": 1})
  281. def test_hide_own_post(self):
  282. """api makes it possible to hide owned post"""
  283. response = self.patch(
  284. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  285. )
  286. self.assertEqual(response.status_code, 200)
  287. reponse_json = response.json()
  288. self.assertTrue(reponse_json["is_hidden"])
  289. self.post.refresh_from_db()
  290. self.assertTrue(self.post.is_hidden)
  291. @patch_category_acl({"can_hide_posts": 0})
  292. def test_hide_post_no_permission(self):
  293. """api hide post with no permission fails"""
  294. response = self.patch(
  295. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  296. )
  297. self.assertEqual(response.status_code, 400)
  298. response_json = response.json()
  299. self.assertEqual(
  300. response_json["detail"][0], "You can't hide posts in this category."
  301. )
  302. self.post.refresh_from_db()
  303. self.assertFalse(self.post.is_hidden)
  304. @patch_category_acl({"can_hide_own_posts": 1, "can_protect_posts": False})
  305. def test_hide_own_protected_post(self):
  306. """api validates if we are trying to hide protected post"""
  307. self.post.is_protected = True
  308. self.post.save()
  309. response = self.patch(
  310. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  311. )
  312. self.assertEqual(response.status_code, 400)
  313. response_json = response.json()
  314. self.assertEqual(
  315. response_json["detail"][0], "This post is protected. You can't hide it."
  316. )
  317. self.post.refresh_from_db()
  318. self.assertFalse(self.post.is_hidden)
  319. @patch_category_acl({"can_hide_own_posts": True})
  320. def test_hide_other_user_post(self):
  321. """api validates post ownership when hiding"""
  322. self.post.poster = None
  323. self.post.save()
  324. response = self.patch(
  325. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  326. )
  327. self.assertEqual(response.status_code, 400)
  328. response_json = response.json()
  329. self.assertEqual(
  330. response_json["detail"][0],
  331. "You can't hide other users posts in this category.",
  332. )
  333. self.post.refresh_from_db()
  334. self.assertFalse(self.post.is_hidden)
  335. @patch_category_acl({"post_edit_time": 1, "can_hide_own_posts": True})
  336. def test_hide_own_post_after_edit_time(self):
  337. """api validates if we are trying to hide post after edit time"""
  338. self.post.posted_on = timezone.now() - timedelta(minutes=10)
  339. self.post.save()
  340. response = self.patch(
  341. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  342. )
  343. self.assertEqual(response.status_code, 400)
  344. response_json = response.json()
  345. self.assertEqual(
  346. response_json["detail"][0],
  347. "You can't hide posts that are older than 1 minute.",
  348. )
  349. self.post.refresh_from_db()
  350. self.assertFalse(self.post.is_hidden)
  351. @patch_category_acl({"can_close_threads": False, "can_hide_own_posts": True})
  352. def test_hide_post_in_closed_thread(self):
  353. """api validates if we are trying to hide post in closed thread"""
  354. self.thread.is_closed = True
  355. self.thread.save()
  356. response = self.patch(
  357. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  358. )
  359. self.assertEqual(response.status_code, 400)
  360. response_json = response.json()
  361. self.assertEqual(
  362. response_json["detail"][0],
  363. "This thread is closed. You can't hide posts in it.",
  364. )
  365. self.post.refresh_from_db()
  366. self.assertFalse(self.post.is_hidden)
  367. @patch_category_acl({"can_close_threads": False, "can_hide_own_posts": True})
  368. def test_hide_post_in_closed_category(self):
  369. """api validates if we are trying to hide post in closed category"""
  370. self.category.is_closed = True
  371. self.category.save()
  372. response = self.patch(
  373. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  374. )
  375. self.assertEqual(response.status_code, 400)
  376. response_json = response.json()
  377. self.assertEqual(
  378. response_json["detail"][0],
  379. "This category is closed. You can't hide posts in it.",
  380. )
  381. self.post.refresh_from_db()
  382. self.assertFalse(self.post.is_hidden)
  383. @patch_category_acl({"can_hide_posts": 1})
  384. def test_hide_first_post(self):
  385. """api hide first post fails"""
  386. self.thread.set_first_post(self.post)
  387. self.thread.save()
  388. response = self.patch(
  389. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  390. )
  391. self.assertEqual(response.status_code, 400)
  392. response_json = response.json()
  393. self.assertEqual(
  394. response_json["detail"][0], "You can't hide thread's first post."
  395. )
  396. @patch_category_acl({"can_hide_posts": 1})
  397. def test_hide_best_answer(self):
  398. """api hide first post fails"""
  399. self.thread.set_best_answer(self.user, self.post)
  400. self.thread.save()
  401. response = self.patch(
  402. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  403. )
  404. self.assertEqual(response.status_code, 400)
  405. self.assertEqual(
  406. response.json(),
  407. {
  408. "id": self.post.id,
  409. "detail": [
  410. "You can't hide this post because its marked as best answer."
  411. ],
  412. },
  413. )
  414. class PostUnhideApiTests(ThreadPostPatchApiTestCase):
  415. @patch_category_acl({"can_hide_posts": 1})
  416. def test_show_post(self):
  417. """api makes it possible to unhide post"""
  418. self.post.is_hidden = True
  419. self.post.save()
  420. self.post.refresh_from_db()
  421. self.assertTrue(self.post.is_hidden)
  422. response = self.patch(
  423. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  424. )
  425. self.assertEqual(response.status_code, 200)
  426. reponse_json = response.json()
  427. self.assertFalse(reponse_json["is_hidden"])
  428. self.post.refresh_from_db()
  429. self.assertFalse(self.post.is_hidden)
  430. @patch_category_acl({"can_hide_own_posts": 1})
  431. def test_show_own_post(self):
  432. """api makes it possible to unhide owned post"""
  433. self.post.is_hidden = True
  434. self.post.save()
  435. self.post.refresh_from_db()
  436. self.assertTrue(self.post.is_hidden)
  437. response = self.patch(
  438. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  439. )
  440. self.assertEqual(response.status_code, 200)
  441. reponse_json = response.json()
  442. self.assertFalse(reponse_json["is_hidden"])
  443. self.post.refresh_from_db()
  444. self.assertFalse(self.post.is_hidden)
  445. @patch_category_acl({"can_hide_posts": 0})
  446. def test_show_post_no_permission(self):
  447. """api unhide post with no permission fails"""
  448. self.post.is_hidden = True
  449. self.post.save()
  450. self.post.refresh_from_db()
  451. self.assertTrue(self.post.is_hidden)
  452. response = self.patch(
  453. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  454. )
  455. self.assertEqual(response.status_code, 400)
  456. response_json = response.json()
  457. self.assertEqual(
  458. response_json["detail"][0], "You can't reveal posts in this category."
  459. )
  460. self.post.refresh_from_db()
  461. self.assertTrue(self.post.is_hidden)
  462. @patch_category_acl({"can_protect_posts": 0, "can_hide_own_posts": 1})
  463. def test_show_own_protected_post(self):
  464. """api validates if we are trying to reveal protected post"""
  465. self.post.is_hidden = True
  466. self.post.save()
  467. self.post.is_protected = True
  468. self.post.save()
  469. response = self.patch(
  470. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  471. )
  472. self.assertEqual(response.status_code, 400)
  473. response_json = response.json()
  474. self.assertEqual(
  475. response_json["detail"][0], "This post is protected. You can't reveal it."
  476. )
  477. self.post.refresh_from_db()
  478. self.assertTrue(self.post.is_hidden)
  479. @patch_category_acl({"can_hide_own_posts": 1})
  480. def test_show_other_user_post(self):
  481. """api validates post ownership when revealing"""
  482. self.post.is_hidden = True
  483. self.post.poster = None
  484. self.post.save()
  485. response = self.patch(
  486. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  487. )
  488. self.assertEqual(response.status_code, 400)
  489. response_json = response.json()
  490. self.assertEqual(
  491. response_json["detail"][0],
  492. "You can't reveal other users posts in this category.",
  493. )
  494. self.post.refresh_from_db()
  495. self.assertTrue(self.post.is_hidden)
  496. @patch_category_acl({"post_edit_time": 1, "can_hide_own_posts": 1})
  497. def test_show_own_post_after_edit_time(self):
  498. """api validates if we are trying to reveal post after edit time"""
  499. self.post.is_hidden = True
  500. self.post.posted_on = timezone.now() - timedelta(minutes=10)
  501. self.post.save()
  502. response = self.patch(
  503. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  504. )
  505. self.assertEqual(response.status_code, 400)
  506. response_json = response.json()
  507. self.assertEqual(
  508. response_json["detail"][0],
  509. "You can't reveal posts that are older than 1 minute.",
  510. )
  511. self.post.refresh_from_db()
  512. self.assertTrue(self.post.is_hidden)
  513. @patch_category_acl({"can_close_threads": False, "can_hide_own_posts": 1})
  514. def test_show_post_in_closed_thread(self):
  515. """api validates if we are trying to reveal post in closed thread"""
  516. self.thread.is_closed = True
  517. self.thread.save()
  518. self.post.is_hidden = True
  519. self.post.save()
  520. response = self.patch(
  521. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  522. )
  523. self.assertEqual(response.status_code, 400)
  524. response_json = response.json()
  525. self.assertEqual(
  526. response_json["detail"][0],
  527. "This thread is closed. You can't reveal posts in it.",
  528. )
  529. self.post.refresh_from_db()
  530. self.assertTrue(self.post.is_hidden)
  531. @patch_category_acl({"can_close_threads": False, "can_hide_own_posts": 1})
  532. def test_show_post_in_closed_category(self):
  533. """api validates if we are trying to reveal post in closed category"""
  534. self.category.is_closed = True
  535. self.category.save()
  536. self.post.is_hidden = True
  537. self.post.save()
  538. response = self.patch(
  539. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  540. )
  541. self.assertEqual(response.status_code, 400)
  542. response_json = response.json()
  543. self.assertEqual(
  544. response_json["detail"][0],
  545. "This category is closed. You can't reveal posts in it.",
  546. )
  547. self.post.refresh_from_db()
  548. self.assertTrue(self.post.is_hidden)
  549. @patch_category_acl({"can_hide_posts": 1})
  550. def test_show_first_post(self):
  551. """api unhide first post fails"""
  552. self.thread.set_first_post(self.post)
  553. self.thread.save()
  554. response = self.patch(
  555. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  556. )
  557. self.assertEqual(response.status_code, 400)
  558. response_json = response.json()
  559. self.assertEqual(
  560. response_json["detail"][0], "You can't reveal thread's first post."
  561. )
  562. class PostLikeApiTests(ThreadPostPatchApiTestCase):
  563. @patch_category_acl({"can_see_posts_likes": 0})
  564. def test_like_no_see_permission(self):
  565. """api validates user's permission to see posts likes"""
  566. response = self.patch(
  567. self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
  568. )
  569. self.assertEqual(response.status_code, 400)
  570. self.assertEqual(
  571. response.json(),
  572. {"id": self.post.id, "detail": ["You can't like posts in this category."]},
  573. )
  574. @patch_category_acl({"can_like_posts": False})
  575. def test_like_no_like_permission(self):
  576. """api validates user's permission to see posts likes"""
  577. response = self.patch(
  578. self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
  579. )
  580. self.assertEqual(response.status_code, 400)
  581. self.assertEqual(
  582. response.json(),
  583. {"id": self.post.id, "detail": ["You can't like posts in this category."]},
  584. )
  585. def test_like_post(self):
  586. """api adds user like to post"""
  587. response = self.patch(
  588. self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
  589. )
  590. self.assertEqual(response.status_code, 200)
  591. response_json = response.json()
  592. self.assertEqual(response_json["likes"], 1)
  593. self.assertEqual(response_json["is_liked"], True)
  594. self.assertEqual(
  595. response_json["last_likes"],
  596. [{"id": self.user.id, "username": self.user.username}],
  597. )
  598. post = Post.objects.get(pk=self.post.pk)
  599. self.assertEqual(post.likes, response_json["likes"])
  600. self.assertEqual(post.last_likes, response_json["last_likes"])
  601. def test_like_liked_post(self):
  602. """api adds user like to post"""
  603. test.like_post(self.post, username="Myo")
  604. test.like_post(self.post, username="Mugi")
  605. test.like_post(self.post, username="Bob")
  606. test.like_post(self.post, username="Miku")
  607. response = self.patch(
  608. self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
  609. )
  610. self.assertEqual(response.status_code, 200)
  611. response_json = response.json()
  612. self.assertEqual(response_json["likes"], 5)
  613. self.assertEqual(response_json["is_liked"], True)
  614. self.assertEqual(
  615. response_json["last_likes"],
  616. [
  617. {"id": self.user.id, "username": self.user.username},
  618. {"id": None, "username": "Miku"},
  619. {"id": None, "username": "Bob"},
  620. {"id": None, "username": "Mugi"},
  621. ],
  622. )
  623. post = Post.objects.get(pk=self.post.pk)
  624. self.assertEqual(post.likes, response_json["likes"])
  625. self.assertEqual(post.last_likes, response_json["last_likes"])
  626. def test_unlike_post(self):
  627. """api removes user like from post"""
  628. test.like_post(self.post, self.user)
  629. response = self.patch(
  630. self.api_link, [{"op": "replace", "path": "is-liked", "value": False}]
  631. )
  632. self.assertEqual(response.status_code, 200)
  633. response_json = response.json()
  634. self.assertEqual(response_json["likes"], 0)
  635. self.assertEqual(response_json["is_liked"], False)
  636. self.assertEqual(response_json["last_likes"], [])
  637. post = Post.objects.get(pk=self.post.pk)
  638. self.assertEqual(post.likes, response_json["likes"])
  639. self.assertEqual(post.last_likes, response_json["last_likes"])
  640. def test_like_post_no_change(self):
  641. """api does no state change if we are linking liked post"""
  642. test.like_post(self.post, self.user)
  643. response = self.patch(
  644. self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
  645. )
  646. self.assertEqual(response.status_code, 200)
  647. response_json = response.json()
  648. self.assertEqual(response_json["likes"], 1)
  649. self.assertEqual(response_json["is_liked"], True)
  650. self.assertEqual(
  651. response_json["last_likes"],
  652. [{"id": self.user.id, "username": self.user.username}],
  653. )
  654. post = Post.objects.get(pk=self.post.pk)
  655. self.assertEqual(post.likes, response_json["likes"])
  656. self.assertEqual(post.last_likes, response_json["last_likes"])
  657. def test_unlike_post_no_change(self):
  658. """api does no state change if we are unlinking unliked post"""
  659. response = self.patch(
  660. self.api_link, [{"op": "replace", "path": "is-liked", "value": False}]
  661. )
  662. self.assertEqual(response.status_code, 200)
  663. response_json = response.json()
  664. self.assertEqual(response_json["likes"], 0)
  665. self.assertEqual(response_json["is_liked"], False)
  666. self.assertEqual(response_json["last_likes"], [])
  667. class ThreadEventPatchApiTestCase(ThreadPostPatchApiTestCase):
  668. def setUp(self):
  669. super().setUp()
  670. self.event = test.reply_thread(self.thread, poster=self.user, is_event=True)
  671. self.api_link = reverse(
  672. "misago:api:thread-post-detail",
  673. kwargs={"thread_pk": self.thread.pk, "pk": self.event.pk},
  674. )
  675. def refresh_event(self):
  676. self.event = self.thread.post_set.get(pk=self.event.pk)
  677. class EventAnonPatchApiTests(ThreadEventPatchApiTestCase):
  678. def test_anonymous_user(self):
  679. """anonymous users can't change event state"""
  680. self.logout_user()
  681. response = self.patch(
  682. self.api_link, [{"op": "add", "path": "acl", "value": True}]
  683. )
  684. self.assertEqual(response.status_code, 403)
  685. class EventAddAclApiTests(ThreadEventPatchApiTestCase):
  686. def test_add_acl_true(self):
  687. """api adds current event's acl to response"""
  688. response = self.patch(
  689. self.api_link, [{"op": "add", "path": "acl", "value": True}]
  690. )
  691. self.assertEqual(response.status_code, 200)
  692. response_json = response.json()
  693. self.assertTrue(response_json["acl"])
  694. def test_add_acl_false(self):
  695. """if value is false, api won't add acl to the response, but will set empty key"""
  696. response = self.patch(
  697. self.api_link, [{"op": "add", "path": "acl", "value": False}]
  698. )
  699. self.assertEqual(response.status_code, 200)
  700. response_json = response.json()
  701. self.assertIsNone(response_json["acl"])
  702. response = self.patch(
  703. self.api_link, [{"op": "add", "path": "acl", "value": True}]
  704. )
  705. self.assertEqual(response.status_code, 200)
  706. class EventHideApiTests(ThreadEventPatchApiTestCase):
  707. @patch_category_acl({"can_hide_events": 1})
  708. def test_hide_event(self):
  709. """api makes it possible to hide event"""
  710. response = self.patch(
  711. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  712. )
  713. self.assertEqual(response.status_code, 200)
  714. self.event.refresh_from_db()
  715. self.assertTrue(self.event.is_hidden)
  716. @patch_category_acl({"can_hide_events": 1})
  717. def test_show_event(self):
  718. """api makes it possible to unhide event"""
  719. self.event.is_hidden = True
  720. self.event.save()
  721. self.event.refresh_from_db()
  722. self.assertTrue(self.event.is_hidden)
  723. response = self.patch(
  724. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  725. )
  726. self.assertEqual(response.status_code, 200)
  727. self.event.refresh_from_db()
  728. self.assertFalse(self.event.is_hidden)
  729. @patch_category_acl({"can_hide_events": 0})
  730. def test_hide_event_no_permission(self):
  731. """api hide event with no permission fails"""
  732. response = self.patch(
  733. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  734. )
  735. self.assertEqual(response.status_code, 400)
  736. response_json = response.json()
  737. self.assertEqual(
  738. response_json["detail"][0], "You can't hide events in this category."
  739. )
  740. self.event.refresh_from_db()
  741. self.assertFalse(self.event.is_hidden)
  742. @patch_category_acl({"can_close_threads": False, "can_hide_events": 1})
  743. def test_hide_event_closed_thread_no_permission(self):
  744. """api hide event in closed thread with no permission fails"""
  745. self.thread.is_closed = True
  746. self.thread.save()
  747. response = self.patch(
  748. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  749. )
  750. self.assertEqual(response.status_code, 400)
  751. response_json = response.json()
  752. self.assertEqual(
  753. response_json["detail"][0],
  754. "This thread is closed. You can't hide events in it.",
  755. )
  756. self.event.refresh_from_db()
  757. self.assertFalse(self.event.is_hidden)
  758. @patch_category_acl({"can_close_threads": False, "can_hide_events": 1})
  759. def test_hide_event_closed_category_no_permission(self):
  760. """api hide event in closed category with no permission fails"""
  761. self.category.is_closed = True
  762. self.category.save()
  763. response = self.patch(
  764. self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
  765. )
  766. self.assertEqual(response.status_code, 400)
  767. response_json = response.json()
  768. self.assertEqual(
  769. response_json["detail"][0],
  770. "This category is closed. You can't hide events in it.",
  771. )
  772. self.event.refresh_from_db()
  773. self.assertFalse(self.event.is_hidden)
  774. @patch_category_acl({"can_hide_events": 0})
  775. def test_show_event_no_permission(self):
  776. """api unhide event with no permission fails"""
  777. self.event.is_hidden = True
  778. self.event.save()
  779. self.event.refresh_from_db()
  780. self.assertTrue(self.event.is_hidden)
  781. response = self.patch(
  782. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  783. )
  784. self.assertEqual(response.status_code, 404)
  785. @patch_category_acl({"can_close_threads": False, "can_hide_events": 1})
  786. def test_show_event_closed_thread_no_permission(self):
  787. """api show event in closed thread with no permission fails"""
  788. self.event.is_hidden = True
  789. self.event.save()
  790. self.thread.is_closed = True
  791. self.thread.save()
  792. response = self.patch(
  793. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  794. )
  795. self.assertEqual(response.status_code, 400)
  796. response_json = response.json()
  797. self.assertEqual(
  798. response_json["detail"][0],
  799. "This thread is closed. You can't reveal events in it.",
  800. )
  801. self.event.refresh_from_db()
  802. self.assertTrue(self.event.is_hidden)
  803. @patch_category_acl({"can_close_threads": False, "can_hide_events": 1})
  804. def test_show_event_closed_category_no_permission(self):
  805. """api show event in closed category with no permission fails"""
  806. self.event.is_hidden = True
  807. self.event.save()
  808. self.category.is_closed = True
  809. self.category.save()
  810. response = self.patch(
  811. self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
  812. )
  813. self.assertEqual(response.status_code, 400)
  814. response_json = response.json()
  815. self.assertEqual(
  816. response_json["detail"][0],
  817. "This category is closed. You can't reveal events in it.",
  818. )
  819. self.event.refresh_from_db()
  820. self.assertTrue(self.event.is_hidden)