test_thread_patch_api.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. import json
  2. from misago.acl.testutils import override_acl
  3. from misago.categories.models import Category
  4. from misago.threads.tests.test_thread_api import ThreadApiTestCase
  5. class ThreadPinGloballyApiTests(ThreadApiTestCase):
  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(ThreadApiTestCase):
  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(ThreadApiTestCase):
  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(self):
  159. """api moves thread to other 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. def test_move_thread_no_permission(self):
  175. """api move thread to other category with no permission fails"""
  176. self.override_acl({
  177. 'can_move_threads': False
  178. })
  179. self.override_other_acl({})
  180. response = self.client.patch(self.api_link, json.dumps([
  181. {'op': 'replace', 'path': 'category', 'value': self.category_b.pk}
  182. ]),
  183. content_type="application/json")
  184. self.assertEqual(response.status_code, 400)
  185. response_json = json.loads(response.content)
  186. self.assertEqual(response_json['detail'][0],
  187. "You don't have permission to move this thread.")
  188. self.override_other_acl({})
  189. thread_json = self.get_thread_json()
  190. self.assertEqual(thread_json['category']['id'], self.category.pk)
  191. def test_move_thread_no_category_access(self):
  192. """api move thread to category with no access fails"""
  193. self.override_acl({
  194. 'can_move_threads': True
  195. })
  196. self.override_other_acl({
  197. 'can_see': False
  198. })
  199. response = self.client.patch(self.api_link, json.dumps([
  200. {'op': 'replace', 'path': 'category', 'value': self.category_b.pk}
  201. ]),
  202. content_type="application/json")
  203. self.assertEqual(response.status_code, 400)
  204. response_json = json.loads(response.content)
  205. self.assertEqual(response_json['detail'][0], 'NOT FOUND')
  206. self.override_other_acl({})
  207. thread_json = self.get_thread_json()
  208. self.assertEqual(thread_json['category']['id'], self.category.pk)
  209. def test_move_thread_no_category_browse(self):
  210. """api move thread to category with no browsing access fails"""
  211. self.override_acl({
  212. 'can_move_threads': True
  213. })
  214. self.override_other_acl({
  215. 'can_browse': False
  216. })
  217. response = self.client.patch(self.api_link, json.dumps([
  218. {'op': 'replace', 'path': 'category', 'value': self.category_b.pk}
  219. ]),
  220. content_type="application/json")
  221. self.assertEqual(response.status_code, 400)
  222. response_json = json.loads(response.content)
  223. self.assertEqual(response_json['detail'][0],
  224. 'You don\'t have permission to browse "Category B" contents.')
  225. self.override_other_acl({})
  226. thread_json = self.get_thread_json()
  227. self.assertEqual(thread_json['category']['id'], self.category.pk)
  228. def test_thread_flatten_categories(self):
  229. """api flatten thread categories"""
  230. response = self.client.patch(self.api_link, json.dumps([
  231. {'op': 'replace', 'path': 'flatten-categories', 'value': None}
  232. ]),
  233. content_type="application/json")
  234. self.assertEqual(response.status_code, 200)
  235. response_json = json.loads(response.content)
  236. self.assertEqual(response_json['category'], self.category.pk)
  237. def test_thread_top_flatten_categories(self):
  238. """api flatten thread with top category"""
  239. self.thread.category = self.category_b
  240. self.thread.save()
  241. self.override_other_acl({})
  242. response = self.client.patch(self.api_link, json.dumps([
  243. {
  244. 'op': 'add',
  245. 'path': 'top-category',
  246. 'value': Category.objects.root_category().pk,
  247. },
  248. {'op': 'replace', 'path': 'flatten-categories', 'value': None},
  249. ]),
  250. content_type="application/json")
  251. self.assertEqual(response.status_code, 200)
  252. response_json = json.loads(response.content)
  253. self.assertEqual(response_json['top_category'], self.category.pk)
  254. self.assertEqual(response_json['category'], self.category_b.pk)
  255. class ThreadCloseApiTests(ThreadApiTestCase):
  256. def test_close_thread(self):
  257. """api makes it possible to close thread"""
  258. self.override_acl({
  259. 'can_close_threads': True
  260. })
  261. response = self.client.patch(self.api_link, json.dumps([
  262. {'op': 'replace', 'path': 'is-closed', 'value': True}
  263. ]),
  264. content_type="application/json")
  265. self.assertEqual(response.status_code, 200)
  266. thread_json = self.get_thread_json()
  267. self.assertTrue(thread_json['is_closed'])
  268. def test_open_thread(self):
  269. """api makes it possible to open thread"""
  270. self.thread.is_closed = True
  271. self.thread.save()
  272. thread_json = self.get_thread_json()
  273. self.assertTrue(thread_json['is_closed'])
  274. self.override_acl({
  275. 'can_close_threads': True
  276. })
  277. response = self.client.patch(self.api_link, json.dumps([
  278. {'op': 'replace', 'path': 'is-closed', 'value': False}
  279. ]),
  280. content_type="application/json")
  281. self.assertEqual(response.status_code, 200)
  282. thread_json = self.get_thread_json()
  283. self.assertFalse(thread_json['is_closed'])
  284. def test_close_thread_no_permission(self):
  285. """api close thread with no permission fails"""
  286. self.override_acl({
  287. 'can_close_threads': False
  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, 400)
  294. response_json = json.loads(response.content)
  295. self.assertEqual(response_json['detail'][0],
  296. "You don't have permission to close this thread.")
  297. thread_json = self.get_thread_json()
  298. self.assertFalse(thread_json['is_closed'])
  299. def test_open_thread_no_permission(self):
  300. """api open thread with no permission fails"""
  301. self.thread.is_closed = True
  302. self.thread.save()
  303. thread_json = self.get_thread_json()
  304. self.assertTrue(thread_json['is_closed'])
  305. self.override_acl({
  306. 'can_close_threads': False
  307. })
  308. response = self.client.patch(self.api_link, json.dumps([
  309. {'op': 'replace', 'path': 'is-closed', 'value': False}
  310. ]),
  311. content_type="application/json")
  312. self.assertEqual(response.status_code, 400)
  313. response_json = json.loads(response.content)
  314. self.assertEqual(response_json['detail'][0],
  315. "You don't have permission to open this thread.")
  316. thread_json = self.get_thread_json()
  317. self.assertTrue(thread_json['is_closed'])
  318. class ThreadApproveApiTests(ThreadApiTestCase):
  319. def test_approve_thread(self):
  320. """api makes it possible to approve thread"""
  321. self.thread.is_unapproved = True
  322. self.thread.save()
  323. self.override_acl({
  324. 'can_approve_content': 1
  325. })
  326. response = self.client.patch(self.api_link, json.dumps([
  327. {'op': 'replace', 'path': 'is-unapproved', 'value': False}
  328. ]),
  329. content_type="application/json")
  330. self.assertEqual(response.status_code, 200)
  331. thread_json = self.get_thread_json()
  332. self.assertFalse(thread_json['is_unapproved'])
  333. def test_unapprove_thread(self):
  334. """api returns permission error on approval removal"""
  335. self.override_acl({
  336. 'can_approve_content': 1
  337. })
  338. response = self.client.patch(self.api_link, json.dumps([
  339. {'op': 'replace', 'path': 'is-unapproved', 'value': True}
  340. ]),
  341. content_type="application/json")
  342. self.assertEqual(response.status_code, 400)
  343. response_json = json.loads(response.content)
  344. self.assertEqual(response_json['detail'][0],
  345. "Content approval can't be reversed.")
  346. class ThreadHideApiTests(ThreadApiTestCase):
  347. def test_hide_thread(self):
  348. """api makes it possible to hide thread"""
  349. self.override_acl({
  350. 'can_hide_threads': 1
  351. })
  352. response = self.client.patch(self.api_link, json.dumps([
  353. {'op': 'replace', 'path': 'is-hidden', 'value': True}
  354. ]),
  355. content_type="application/json")
  356. self.assertEqual(response.status_code, 200)
  357. self.override_acl({
  358. 'can_hide_threads': 1
  359. })
  360. thread_json = self.get_thread_json()
  361. self.assertTrue(thread_json['is_hidden'])
  362. def test_show_thread(self):
  363. """api makes it possible to unhide thread"""
  364. self.thread.is_hidden = True
  365. self.thread.save()
  366. self.override_acl({
  367. 'can_hide_threads': 1
  368. })
  369. thread_json = self.get_thread_json()
  370. self.assertTrue(thread_json['is_hidden'])
  371. self.override_acl({
  372. 'can_hide_threads': 1
  373. })
  374. response = self.client.patch(self.api_link, json.dumps([
  375. {'op': 'replace', 'path': 'is-hidden', 'value': False}
  376. ]),
  377. content_type="application/json")
  378. self.assertEqual(response.status_code, 200)
  379. self.override_acl({
  380. 'can_hide_threads': 1
  381. })
  382. thread_json = self.get_thread_json()
  383. self.assertFalse(thread_json['is_hidden'])
  384. def test_hide_thread_no_permission(self):
  385. """api hide thread with no permission fails"""
  386. self.override_acl({
  387. 'can_hide_threads': 0
  388. })
  389. response = self.client.patch(self.api_link, json.dumps([
  390. {'op': 'replace', 'path': 'is-hidden', 'value': True}
  391. ]),
  392. content_type="application/json")
  393. self.assertEqual(response.status_code, 400)
  394. response_json = json.loads(response.content)
  395. self.assertEqual(response_json['detail'][0],
  396. "You don't have permission to hide this thread.")
  397. thread_json = self.get_thread_json()
  398. self.assertFalse(thread_json['is_hidden'])
  399. def test_show_thread_no_permission(self):
  400. """api unhide thread with no permission fails"""
  401. self.thread.is_hidden = True
  402. self.thread.save()
  403. self.override_acl({
  404. 'can_hide_threads': 1
  405. })
  406. thread_json = self.get_thread_json()
  407. self.assertTrue(thread_json['is_hidden'])
  408. self.override_acl({
  409. 'can_hide_threads': 0
  410. })
  411. response = self.client.patch(self.api_link, json.dumps([
  412. {'op': 'replace', 'path': 'is-hidden', 'value': False}
  413. ]),
  414. content_type="application/json")
  415. self.assertEqual(response.status_code, 404)
  416. class ThreadSubscribeApiTests(ThreadApiTestCase):
  417. def test_subscribe_thread(self):
  418. """api makes it possible to subscribe thread"""
  419. response = self.client.patch(self.api_link, json.dumps([
  420. {'op': 'replace', 'path': 'subscription', 'value': 'notify'}
  421. ]),
  422. content_type="application/json")
  423. self.assertEqual(response.status_code, 200)
  424. thread_json = self.get_thread_json()
  425. self.assertFalse(thread_json['subscription'])
  426. subscription = self.user.subscription_set.get(thread=self.thread)
  427. self.assertFalse(subscription.send_email)
  428. def test_subscribe_thread_with_email(self):
  429. """api makes it possible to subscribe thread with emails"""
  430. response = self.client.patch(self.api_link, json.dumps([
  431. {'op': 'replace', 'path': 'subscription', 'value': 'email'}
  432. ]),
  433. content_type="application/json")
  434. self.assertEqual(response.status_code, 200)
  435. thread_json = self.get_thread_json()
  436. self.assertTrue(thread_json['subscription'])
  437. subscription = self.user.subscription_set.get(thread=self.thread)
  438. self.assertTrue(subscription.send_email)
  439. def test_unsubscribe_thread(self):
  440. """api makes it possible to unsubscribe thread"""
  441. response = self.client.patch(self.api_link, json.dumps([
  442. {'op': 'replace', 'path': 'subscription', 'value': 'remove'}
  443. ]),
  444. content_type="application/json")
  445. self.assertEqual(response.status_code, 200)
  446. thread_json = self.get_thread_json()
  447. self.assertIsNone(thread_json['subscription'])
  448. self.assertEqual(self.user.subscription_set.count(), 0)
  449. def test_subscribe_as_guest(self):
  450. """api makes it impossible to subscribe thread"""
  451. self.logout_user()
  452. response = self.client.patch(self.api_link, json.dumps([
  453. {'op': 'replace', 'path': 'subscription', 'value': 'email'}
  454. ]),
  455. content_type="application/json")
  456. self.assertEqual(response.status_code, 403)
  457. def test_subscribe_nonexistant_thread(self):
  458. """api makes it impossible to subscribe nonexistant thread"""
  459. bad_api_link = self.api_link.replace(
  460. unicode(self.thread.pk), unicode(self.thread.pk + 9))
  461. response = self.client.patch(bad_api_link, json.dumps([
  462. {'op': 'replace', 'path': 'subscription', 'value': 'email'}
  463. ]),
  464. content_type="application/json")
  465. self.assertEqual(response.status_code, 404)