test_thread_patch_api.py 20 KB

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