test_thread_patch_api.py 21 KB

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