test_thread_patch_api.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. import json
  2. from django.utils import six
  3. from django.utils.encoding import smart_str
  4. from misago.acl.testutils import override_acl
  5. from misago.categories.models import Category
  6. from .test_threads_api import ThreadsApiTestCase
  7. class ThreadPinGloballyApiTests(ThreadsApiTestCase):
  8. def test_pin_thread(self):
  9. """api makes it possible to pin globally thread"""
  10. self.override_acl({
  11. 'can_pin_threads': 2
  12. })
  13. response = self.client.patch(self.api_link, json.dumps([
  14. {'op': 'replace', 'path': 'weight', 'value': 2}
  15. ]),
  16. content_type="application/json")
  17. self.assertEqual(response.status_code, 200)
  18. thread_json = self.get_thread_json()
  19. self.assertEqual(thread_json['weight'], 2)
  20. def test_unpin_thread(self):
  21. """api makes it possible to unpin thread"""
  22. self.thread.weight = 2
  23. self.thread.save()
  24. thread_json = self.get_thread_json()
  25. self.assertEqual(thread_json['weight'], 2)
  26. self.override_acl({
  27. 'can_pin_threads': 2
  28. })
  29. response = self.client.patch(self.api_link, json.dumps([
  30. {'op': 'replace', 'path': 'weight', 'value': 0}
  31. ]),
  32. content_type="application/json")
  33. self.assertEqual(response.status_code, 200)
  34. thread_json = self.get_thread_json()
  35. self.assertEqual(thread_json['weight'], 0)
  36. def test_pin_thread_no_permission(self):
  37. """api pin thread globally with no permission fails"""
  38. self.override_acl({
  39. 'can_pin_threads': 1
  40. })
  41. response = self.client.patch(self.api_link, json.dumps([
  42. {'op': 'replace', 'path': 'weight', 'value': 2}
  43. ]),
  44. content_type="application/json")
  45. self.assertEqual(response.status_code, 400)
  46. response_json = json.loads(smart_str(response.content))
  47. self.assertEqual(response_json['detail'][0],
  48. "You don't have permission to pin this thread globally.")
  49. thread_json = self.get_thread_json()
  50. self.assertEqual(thread_json['weight'], 0)
  51. def test_unpin_thread_no_permission(self):
  52. """api unpin thread with no permission fails"""
  53. self.thread.weight = 2
  54. self.thread.save()
  55. thread_json = self.get_thread_json()
  56. self.assertEqual(thread_json['weight'], 2)
  57. self.override_acl({
  58. 'can_pin_threads': 1
  59. })
  60. response = self.client.patch(self.api_link, json.dumps([
  61. {'op': 'replace', 'path': 'weight', 'value': 1}
  62. ]),
  63. content_type="application/json")
  64. self.assertEqual(response.status_code, 400)
  65. response_json = json.loads(smart_str(response.content))
  66. self.assertEqual(response_json['detail'][0],
  67. "You don't have permission to change this thread's weight.")
  68. thread_json = self.get_thread_json()
  69. self.assertEqual(thread_json['weight'], 2)
  70. class ThreadPinLocallyApiTests(ThreadsApiTestCase):
  71. def test_pin_thread(self):
  72. """api makes it possible to pin locally thread"""
  73. self.override_acl({
  74. 'can_pin_threads': 1
  75. })
  76. response = self.client.patch(self.api_link, json.dumps([
  77. {'op': 'replace', 'path': 'weight', 'value': 1}
  78. ]),
  79. content_type="application/json")
  80. self.assertEqual(response.status_code, 200)
  81. thread_json = self.get_thread_json()
  82. self.assertEqual(thread_json['weight'], 1)
  83. def test_unpin_thread(self):
  84. """api makes it possible to unpin thread"""
  85. self.thread.weight = 1
  86. self.thread.save()
  87. thread_json = self.get_thread_json()
  88. self.assertEqual(thread_json['weight'], 1)
  89. self.override_acl({
  90. 'can_pin_threads': 1
  91. })
  92. response = self.client.patch(self.api_link, json.dumps([
  93. {'op': 'replace', 'path': 'weight', 'value': 0}
  94. ]),
  95. content_type="application/json")
  96. self.assertEqual(response.status_code, 200)
  97. thread_json = self.get_thread_json()
  98. self.assertEqual(thread_json['weight'], 0)
  99. def test_pin_thread_no_permission(self):
  100. """api pin thread locally with no permission fails"""
  101. self.override_acl({
  102. 'can_pin_threads': 0
  103. })
  104. response = self.client.patch(self.api_link, json.dumps([
  105. {'op': 'replace', 'path': 'weight', 'value': 1}
  106. ]),
  107. content_type="application/json")
  108. self.assertEqual(response.status_code, 400)
  109. response_json = json.loads(smart_str(response.content))
  110. self.assertEqual(response_json['detail'][0],
  111. "You don't have permission to change this thread's weight.")
  112. thread_json = self.get_thread_json()
  113. self.assertEqual(thread_json['weight'], 0)
  114. def test_unpin_thread_no_permission(self):
  115. """api unpin thread with no permission fails"""
  116. self.thread.weight = 1
  117. self.thread.save()
  118. thread_json = self.get_thread_json()
  119. self.assertEqual(thread_json['weight'], 1)
  120. self.override_acl({
  121. 'can_pin_threads': 0
  122. })
  123. response = self.client.patch(self.api_link, json.dumps([
  124. {'op': 'replace', 'path': 'weight', 'value': 0}
  125. ]),
  126. content_type="application/json")
  127. self.assertEqual(response.status_code, 400)
  128. response_json = json.loads(smart_str(response.content))
  129. self.assertEqual(response_json['detail'][0],
  130. "You don't have permission to change this thread's weight.")
  131. thread_json = self.get_thread_json()
  132. self.assertEqual(thread_json['weight'], 1)
  133. class ThreadMoveApiTests(ThreadsApiTestCase):
  134. def setUp(self):
  135. super(ThreadMoveApiTests, self).setUp()
  136. Category(
  137. name='Category B',
  138. slug='category-b',
  139. ).insert_at(self.category, position='last-child', save=True)
  140. self.category_b = Category.objects.get(slug='category-b')
  141. def override_other_acl(self, acl):
  142. final_acl = {
  143. 'can_see': 1,
  144. 'can_browse': 1,
  145. 'can_see_all_threads': 1,
  146. 'can_see_own_threads': 0,
  147. 'can_hide_threads': 0,
  148. 'can_approve_content': 0,
  149. }
  150. final_acl.update(acl)
  151. categories_acl = self.user.acl['categories']
  152. categories_acl[self.category_b.pk] = final_acl
  153. visible_categories = [self.category.pk]
  154. if final_acl['can_see']:
  155. visible_categories.append(self.category_b.pk)
  156. override_acl(self.user, {
  157. 'visible_categories': visible_categories,
  158. 'categories': categories_acl,
  159. })
  160. def test_move_thread_no_top(self):
  161. """api moves thread to other category, sets no top category"""
  162. self.override_acl({
  163. 'can_move_threads': True
  164. })
  165. self.override_other_acl({})
  166. response = self.client.patch(self.api_link, json.dumps([
  167. {'op': 'replace', 'path': 'category', 'value': self.category_b.pk},
  168. {'op': 'add', 'path': 'top-category', 'value': self.category_b.pk},
  169. {'op': 'replace', 'path': 'flatten-categories', 'value': None},
  170. ]),
  171. content_type="application/json")
  172. self.assertEqual(response.status_code, 200)
  173. self.override_other_acl({})
  174. thread_json = self.get_thread_json()
  175. self.assertEqual(thread_json['category']['id'], self.category_b.pk)
  176. reponse_json = json.loads(smart_str(response.content))
  177. self.assertEqual(reponse_json['category'], self.category_b.pk)
  178. self.assertEqual(reponse_json['top_category'], None)
  179. def test_move_thread_with_top(self):
  180. """api moves thread to other category, sets top"""
  181. self.override_acl({
  182. 'can_move_threads': True
  183. })
  184. self.override_other_acl({})
  185. response = self.client.patch(self.api_link, json.dumps([
  186. {'op': 'replace', 'path': 'category', 'value': self.category_b.pk},
  187. {
  188. 'op': 'add',
  189. 'path': 'top-category',
  190. 'value': Category.objects.root_category().pk,
  191. },
  192. {'op': 'replace', 'path': 'flatten-categories', 'value': None},
  193. ]),
  194. content_type="application/json")
  195. self.assertEqual(response.status_code, 200)
  196. self.override_other_acl({})
  197. thread_json = self.get_thread_json()
  198. self.assertEqual(thread_json['category']['id'], self.category_b.pk)
  199. reponse_json = json.loads(smart_str(response.content))
  200. self.assertEqual(reponse_json['category'], self.category_b.pk)
  201. self.assertEqual(reponse_json['top_category'], self.category.pk)
  202. def test_move_thread_no_permission(self):
  203. """api move thread to other category with no permission fails"""
  204. self.override_acl({
  205. 'can_move_threads': False
  206. })
  207. self.override_other_acl({})
  208. response = self.client.patch(self.api_link, json.dumps([
  209. {'op': 'replace', 'path': 'category', 'value': self.category_b.pk}
  210. ]),
  211. content_type="application/json")
  212. self.assertEqual(response.status_code, 400)
  213. response_json = json.loads(smart_str(response.content))
  214. self.assertEqual(response_json['detail'][0],
  215. "You don't have permission to move this thread.")
  216. self.override_other_acl({})
  217. thread_json = self.get_thread_json()
  218. self.assertEqual(thread_json['category']['id'], self.category.pk)
  219. def test_move_thread_no_category_access(self):
  220. """api move thread to category with no access fails"""
  221. self.override_acl({
  222. 'can_move_threads': True
  223. })
  224. self.override_other_acl({
  225. 'can_see': False
  226. })
  227. response = self.client.patch(self.api_link, json.dumps([
  228. {'op': 'replace', 'path': 'category', 'value': self.category_b.pk}
  229. ]),
  230. content_type="application/json")
  231. self.assertEqual(response.status_code, 400)
  232. response_json = json.loads(smart_str(response.content))
  233. self.assertEqual(response_json['detail'][0], 'NOT FOUND')
  234. self.override_other_acl({})
  235. thread_json = self.get_thread_json()
  236. self.assertEqual(thread_json['category']['id'], self.category.pk)
  237. def test_move_thread_no_category_browse(self):
  238. """api move thread to category with no browsing access fails"""
  239. self.override_acl({
  240. 'can_move_threads': True
  241. })
  242. self.override_other_acl({
  243. 'can_browse': False
  244. })
  245. response = self.client.patch(self.api_link, json.dumps([
  246. {'op': 'replace', 'path': 'category', 'value': self.category_b.pk}
  247. ]),
  248. content_type="application/json")
  249. self.assertEqual(response.status_code, 400)
  250. response_json = json.loads(smart_str(response.content))
  251. self.assertEqual(response_json['detail'][0],
  252. 'You don\'t have permission to browse "Category B" contents.')
  253. self.override_other_acl({})
  254. thread_json = self.get_thread_json()
  255. self.assertEqual(thread_json['category']['id'], self.category.pk)
  256. def test_thread_flatten_categories(self):
  257. """api flatten thread categories"""
  258. response = self.client.patch(self.api_link, json.dumps([
  259. {'op': 'replace', 'path': 'flatten-categories', 'value': None}
  260. ]),
  261. content_type="application/json")
  262. self.assertEqual(response.status_code, 200)
  263. response_json = json.loads(smart_str(response.content))
  264. self.assertEqual(response_json['category'], self.category.pk)
  265. def test_thread_top_flatten_categories(self):
  266. """api flatten thread with top category"""
  267. self.thread.category = self.category_b
  268. self.thread.save()
  269. self.override_other_acl({})
  270. response = self.client.patch(self.api_link, json.dumps([
  271. {
  272. 'op': 'add',
  273. 'path': 'top-category',
  274. 'value': Category.objects.root_category().pk,
  275. },
  276. {'op': 'replace', 'path': 'flatten-categories', 'value': None},
  277. ]),
  278. content_type="application/json")
  279. self.assertEqual(response.status_code, 200)
  280. response_json = json.loads(smart_str(response.content))
  281. self.assertEqual(response_json['top_category'], self.category.pk)
  282. self.assertEqual(response_json['category'], self.category_b.pk)
  283. class ThreadCloseApiTests(ThreadsApiTestCase):
  284. def test_close_thread(self):
  285. """api makes it possible to close thread"""
  286. self.override_acl({
  287. 'can_close_threads': True
  288. })
  289. response = self.client.patch(self.api_link, json.dumps([
  290. {'op': 'replace', 'path': 'is-closed', 'value': True}
  291. ]),
  292. content_type="application/json")
  293. self.assertEqual(response.status_code, 200)
  294. thread_json = self.get_thread_json()
  295. self.assertTrue(thread_json['is_closed'])
  296. def test_open_thread(self):
  297. """api makes it possible to open thread"""
  298. self.thread.is_closed = True
  299. self.thread.save()
  300. thread_json = self.get_thread_json()
  301. self.assertTrue(thread_json['is_closed'])
  302. self.override_acl({
  303. 'can_close_threads': True
  304. })
  305. response = self.client.patch(self.api_link, json.dumps([
  306. {'op': 'replace', 'path': 'is-closed', 'value': False}
  307. ]),
  308. content_type="application/json")
  309. self.assertEqual(response.status_code, 200)
  310. thread_json = self.get_thread_json()
  311. self.assertFalse(thread_json['is_closed'])
  312. def test_close_thread_no_permission(self):
  313. """api close thread with no permission fails"""
  314. self.override_acl({
  315. 'can_close_threads': False
  316. })
  317. response = self.client.patch(self.api_link, json.dumps([
  318. {'op': 'replace', 'path': 'is-closed', 'value': True}
  319. ]),
  320. content_type="application/json")
  321. self.assertEqual(response.status_code, 400)
  322. response_json = json.loads(smart_str(response.content))
  323. self.assertEqual(response_json['detail'][0],
  324. "You don't have permission to close this thread.")
  325. thread_json = self.get_thread_json()
  326. self.assertFalse(thread_json['is_closed'])
  327. def test_open_thread_no_permission(self):
  328. """api open thread with no permission fails"""
  329. self.thread.is_closed = True
  330. self.thread.save()
  331. thread_json = self.get_thread_json()
  332. self.assertTrue(thread_json['is_closed'])
  333. self.override_acl({
  334. 'can_close_threads': False
  335. })
  336. response = self.client.patch(self.api_link, json.dumps([
  337. {'op': 'replace', 'path': 'is-closed', 'value': False}
  338. ]),
  339. content_type="application/json")
  340. self.assertEqual(response.status_code, 400)
  341. response_json = json.loads(smart_str(response.content))
  342. self.assertEqual(response_json['detail'][0],
  343. "You don't have permission to open this thread.")
  344. thread_json = self.get_thread_json()
  345. self.assertTrue(thread_json['is_closed'])
  346. class ThreadApproveApiTests(ThreadsApiTestCase):
  347. def test_approve_thread(self):
  348. """api makes it possible to approve thread"""
  349. self.thread.is_unapproved = True
  350. self.thread.save()
  351. self.override_acl({
  352. 'can_approve_content': 1
  353. })
  354. response = self.client.patch(self.api_link, json.dumps([
  355. {'op': 'replace', 'path': 'is-unapproved', 'value': False}
  356. ]),
  357. content_type="application/json")
  358. self.assertEqual(response.status_code, 200)
  359. thread_json = self.get_thread_json()
  360. self.assertFalse(thread_json['is_unapproved'])
  361. def test_unapprove_thread(self):
  362. """api returns permission error on approval removal"""
  363. self.override_acl({
  364. 'can_approve_content': 1
  365. })
  366. response = self.client.patch(self.api_link, json.dumps([
  367. {'op': 'replace', 'path': 'is-unapproved', 'value': True}
  368. ]),
  369. content_type="application/json")
  370. self.assertEqual(response.status_code, 400)
  371. response_json = json.loads(smart_str(response.content))
  372. self.assertEqual(response_json['detail'][0],
  373. "Content approval can't be reversed.")
  374. class ThreadHideApiTests(ThreadsApiTestCase):
  375. def test_hide_thread(self):
  376. """api makes it possible to hide thread"""
  377. self.override_acl({
  378. 'can_hide_threads': 1
  379. })
  380. response = self.client.patch(self.api_link, json.dumps([
  381. {'op': 'replace', 'path': 'is-hidden', 'value': True}
  382. ]),
  383. content_type="application/json")
  384. self.assertEqual(response.status_code, 200)
  385. self.override_acl({
  386. 'can_hide_threads': 1
  387. })
  388. thread_json = self.get_thread_json()
  389. self.assertTrue(thread_json['is_hidden'])
  390. def test_show_thread(self):
  391. """api makes it possible to unhide thread"""
  392. self.thread.is_hidden = True
  393. self.thread.save()
  394. self.override_acl({
  395. 'can_hide_threads': 1
  396. })
  397. thread_json = self.get_thread_json()
  398. self.assertTrue(thread_json['is_hidden'])
  399. self.override_acl({
  400. 'can_hide_threads': 1
  401. })
  402. response = self.client.patch(self.api_link, json.dumps([
  403. {'op': 'replace', 'path': 'is-hidden', 'value': False}
  404. ]),
  405. content_type="application/json")
  406. self.assertEqual(response.status_code, 200)
  407. self.override_acl({
  408. 'can_hide_threads': 1
  409. })
  410. thread_json = self.get_thread_json()
  411. self.assertFalse(thread_json['is_hidden'])
  412. def test_hide_thread_no_permission(self):
  413. """api hide thread with no permission fails"""
  414. self.override_acl({
  415. 'can_hide_threads': 0
  416. })
  417. response = self.client.patch(self.api_link, json.dumps([
  418. {'op': 'replace', 'path': 'is-hidden', 'value': True}
  419. ]),
  420. content_type="application/json")
  421. self.assertEqual(response.status_code, 400)
  422. response_json = json.loads(smart_str(response.content))
  423. self.assertEqual(response_json['detail'][0],
  424. "You don't have permission to hide this thread.")
  425. thread_json = self.get_thread_json()
  426. self.assertFalse(thread_json['is_hidden'])
  427. def test_show_thread_no_permission(self):
  428. """api unhide thread with no permission fails"""
  429. self.thread.is_hidden = True
  430. self.thread.save()
  431. self.override_acl({
  432. 'can_hide_threads': 1
  433. })
  434. thread_json = self.get_thread_json()
  435. self.assertTrue(thread_json['is_hidden'])
  436. self.override_acl({
  437. 'can_hide_threads': 0
  438. })
  439. response = self.client.patch(self.api_link, json.dumps([
  440. {'op': 'replace', 'path': 'is-hidden', 'value': False}
  441. ]),
  442. content_type="application/json")
  443. self.assertEqual(response.status_code, 404)
  444. class ThreadSubscribeApiTests(ThreadsApiTestCase):
  445. def test_subscribe_thread(self):
  446. """api makes it possible to subscribe thread"""
  447. response = self.client.patch(self.api_link, json.dumps([
  448. {'op': 'replace', 'path': 'subscription', 'value': 'notify'}
  449. ]),
  450. content_type="application/json")
  451. self.assertEqual(response.status_code, 200)
  452. thread_json = self.get_thread_json()
  453. self.assertFalse(thread_json['subscription'])
  454. subscription = self.user.subscription_set.get(thread=self.thread)
  455. self.assertFalse(subscription.send_email)
  456. def test_subscribe_thread_with_email(self):
  457. """api makes it possible to subscribe thread with emails"""
  458. response = self.client.patch(self.api_link, json.dumps([
  459. {'op': 'replace', 'path': 'subscription', 'value': 'email'}
  460. ]),
  461. content_type="application/json")
  462. self.assertEqual(response.status_code, 200)
  463. thread_json = self.get_thread_json()
  464. self.assertTrue(thread_json['subscription'])
  465. subscription = self.user.subscription_set.get(thread=self.thread)
  466. self.assertTrue(subscription.send_email)
  467. def test_unsubscribe_thread(self):
  468. """api makes it possible to unsubscribe thread"""
  469. response = self.client.patch(self.api_link, json.dumps([
  470. {'op': 'replace', 'path': 'subscription', 'value': 'remove'}
  471. ]),
  472. content_type="application/json")
  473. self.assertEqual(response.status_code, 200)
  474. thread_json = self.get_thread_json()
  475. self.assertIsNone(thread_json['subscription'])
  476. self.assertEqual(self.user.subscription_set.count(), 0)
  477. def test_subscribe_as_guest(self):
  478. """api makes it impossible to subscribe thread"""
  479. self.logout_user()
  480. response = self.client.patch(self.api_link, json.dumps([
  481. {'op': 'replace', 'path': 'subscription', 'value': 'email'}
  482. ]),
  483. content_type="application/json")
  484. self.assertEqual(response.status_code, 403)
  485. def test_subscribe_nonexistant_thread(self):
  486. """api makes it impossible to subscribe nonexistant thread"""
  487. bad_api_link = self.api_link.replace(
  488. six.text_type(self.thread.pk), six.text_type(self.thread.pk + 9))
  489. response = self.client.patch(bad_api_link, json.dumps([
  490. {'op': 'replace', 'path': 'subscription', 'value': 'email'}
  491. ]),
  492. content_type="application/json")
  493. self.assertEqual(response.status_code, 404)