test_thread_patch_api.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. import json
  2. from datetime import timedelta
  3. from django.utils import six, timezone
  4. from misago.acl.testutils import override_acl
  5. from misago.categories.models import Category
  6. from misago.threads.models import Thread
  7. from .test_threads_api import ThreadsApiTestCase
  8. class ThreadPatchApiTestCase(ThreadsApiTestCase):
  9. def patch(self, api_link, ops):
  10. return self.client.patch(api_link, json.dumps(ops), content_type="application/json")
  11. class ThreadAddAclApiTests(ThreadPatchApiTestCase):
  12. def test_add_acl_true(self):
  13. """api adds current thread's acl to response"""
  14. response = self.patch(self.api_link, [
  15. {
  16. 'op': 'add',
  17. 'path': 'acl',
  18. 'value': True,
  19. },
  20. ])
  21. self.assertEqual(response.status_code, 200)
  22. response_json = response.json()
  23. self.assertTrue(response_json['acl'])
  24. def test_add_acl_false(self):
  25. """if value is false, api won't add acl to the response, but will set empty key"""
  26. response = self.patch(self.api_link, [
  27. {
  28. 'op': 'add',
  29. 'path': 'acl',
  30. 'value': False,
  31. },
  32. ])
  33. self.assertEqual(response.status_code, 200)
  34. response_json = response.json()
  35. self.assertIsNone(response_json['acl'])
  36. class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
  37. def test_change_thread_title(self):
  38. """api makes it possible to change thread title"""
  39. self.override_acl({'can_edit_threads': 2})
  40. response = self.patch(
  41. self.api_link, [
  42. {
  43. 'op': 'replace',
  44. 'path': 'title',
  45. 'value': "Lorem ipsum change!",
  46. },
  47. ]
  48. )
  49. self.assertEqual(response.status_code, 200)
  50. response_json = response.json()
  51. self.assertEqual(response_json['title'], "Lorem ipsum change!")
  52. thread_json = self.get_thread_json()
  53. self.assertEqual(thread_json['title'], "Lorem ipsum change!")
  54. def test_change_thread_title_no_permission(self):
  55. """api validates permission to change title"""
  56. self.override_acl({'can_edit_threads': 0})
  57. response = self.patch(
  58. self.api_link, [
  59. {
  60. 'op': 'replace',
  61. 'path': 'title',
  62. 'value': "Lorem ipsum change!",
  63. },
  64. ]
  65. )
  66. self.assertEqual(response.status_code, 400)
  67. response_json = response.json()
  68. self.assertEqual(response_json['detail'][0], "You can't edit threads in this category.")
  69. def test_change_thread_title_after_edit_time(self):
  70. """api cleans, validates and rejects too short title"""
  71. self.override_acl({'thread_edit_time': 1, 'can_edit_threads': 1})
  72. self.thread.starter = self.user
  73. self.thread.started_on = timezone.now() - timedelta(minutes=10)
  74. self.thread.save()
  75. response = self.patch(
  76. self.api_link, [
  77. {
  78. 'op': 'replace',
  79. 'path': 'title',
  80. 'value': "Lorem ipsum change!",
  81. },
  82. ]
  83. )
  84. self.assertEqual(response.status_code, 400)
  85. response_json = response.json()
  86. self.assertEqual(
  87. response_json['detail'][0], "You can't edit threads that are older than 1 minute."
  88. )
  89. def test_change_thread_title_invalid(self):
  90. """api cleans, validates and rejects too short title"""
  91. self.override_acl({'can_edit_threads': 2})
  92. response = self.patch(
  93. self.api_link, [
  94. {
  95. 'op': 'replace',
  96. 'path': 'title',
  97. 'value': 12,
  98. },
  99. ]
  100. )
  101. self.assertEqual(response.status_code, 400)
  102. response_json = response.json()
  103. self.assertEqual(
  104. response_json['detail'][0],
  105. "Thread title should be at least 5 characters long (it has 2)."
  106. )
  107. class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
  108. def test_pin_thread(self):
  109. """api makes it possible to pin globally thread"""
  110. self.override_acl({'can_pin_threads': 2})
  111. response = self.patch(
  112. self.api_link, [
  113. {
  114. 'op': 'replace',
  115. 'path': 'weight',
  116. 'value': 2,
  117. },
  118. ]
  119. )
  120. self.assertEqual(response.status_code, 200)
  121. response_json = response.json()
  122. self.assertEqual(response_json['weight'], 2)
  123. thread_json = self.get_thread_json()
  124. self.assertEqual(thread_json['weight'], 2)
  125. def test_unpin_thread(self):
  126. """api makes it possible to unpin thread"""
  127. self.thread.weight = 2
  128. self.thread.save()
  129. thread_json = self.get_thread_json()
  130. self.assertEqual(thread_json['weight'], 2)
  131. self.override_acl({'can_pin_threads': 2})
  132. response = self.patch(
  133. self.api_link, [
  134. {
  135. 'op': 'replace',
  136. 'path': 'weight',
  137. 'value': 0,
  138. },
  139. ]
  140. )
  141. self.assertEqual(response.status_code, 200)
  142. response_json = response.json()
  143. self.assertEqual(response_json['weight'], 0)
  144. thread_json = self.get_thread_json()
  145. self.assertEqual(thread_json['weight'], 0)
  146. def test_pin_thread_no_permission(self):
  147. """api pin thread globally with no permission fails"""
  148. self.override_acl({'can_pin_threads': 1})
  149. response = self.patch(
  150. self.api_link, [
  151. {
  152. 'op': 'replace',
  153. 'path': 'weight',
  154. 'value': 2,
  155. },
  156. ]
  157. )
  158. self.assertEqual(response.status_code, 400)
  159. response_json = response.json()
  160. self.assertEqual(
  161. response_json['detail'][0], "You don't have permission to pin this thread globally."
  162. )
  163. thread_json = self.get_thread_json()
  164. self.assertEqual(thread_json['weight'], 0)
  165. def test_unpin_thread_no_permission(self):
  166. """api unpin thread with no permission fails"""
  167. self.thread.weight = 2
  168. self.thread.save()
  169. thread_json = self.get_thread_json()
  170. self.assertEqual(thread_json['weight'], 2)
  171. self.override_acl({'can_pin_threads': 1})
  172. response = self.patch(
  173. self.api_link, [
  174. {
  175. 'op': 'replace',
  176. 'path': 'weight',
  177. 'value': 1,
  178. },
  179. ]
  180. )
  181. self.assertEqual(response.status_code, 400)
  182. response_json = response.json()
  183. self.assertEqual(
  184. response_json['detail'][0], "You don't have permission to change this thread's weight."
  185. )
  186. thread_json = self.get_thread_json()
  187. self.assertEqual(thread_json['weight'], 2)
  188. class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
  189. def test_pin_thread(self):
  190. """api makes it possible to pin locally thread"""
  191. self.override_acl({'can_pin_threads': 1})
  192. response = self.patch(
  193. self.api_link, [
  194. {
  195. 'op': 'replace',
  196. 'path': 'weight',
  197. 'value': 1,
  198. },
  199. ]
  200. )
  201. self.assertEqual(response.status_code, 200)
  202. response_json = response.json()
  203. self.assertEqual(response_json['weight'], 1)
  204. thread_json = self.get_thread_json()
  205. self.assertEqual(thread_json['weight'], 1)
  206. def test_unpin_thread(self):
  207. """api makes it possible to unpin thread"""
  208. self.thread.weight = 1
  209. self.thread.save()
  210. thread_json = self.get_thread_json()
  211. self.assertEqual(thread_json['weight'], 1)
  212. self.override_acl({'can_pin_threads': 1})
  213. response = self.patch(
  214. self.api_link, [
  215. {
  216. 'op': 'replace',
  217. 'path': 'weight',
  218. 'value': 0,
  219. },
  220. ]
  221. )
  222. self.assertEqual(response.status_code, 200)
  223. response_json = response.json()
  224. self.assertEqual(response_json['weight'], 0)
  225. thread_json = self.get_thread_json()
  226. self.assertEqual(thread_json['weight'], 0)
  227. def test_pin_thread_no_permission(self):
  228. """api pin thread locally with no permission fails"""
  229. self.override_acl({'can_pin_threads': 0})
  230. response = self.patch(
  231. self.api_link, [
  232. {
  233. 'op': 'replace',
  234. 'path': 'weight',
  235. 'value': 1,
  236. },
  237. ]
  238. )
  239. self.assertEqual(response.status_code, 400)
  240. response_json = response.json()
  241. self.assertEqual(
  242. response_json['detail'][0], "You don't have permission to change this thread's weight."
  243. )
  244. thread_json = self.get_thread_json()
  245. self.assertEqual(thread_json['weight'], 0)
  246. def test_unpin_thread_no_permission(self):
  247. """api unpin thread with no permission fails"""
  248. self.thread.weight = 1
  249. self.thread.save()
  250. thread_json = self.get_thread_json()
  251. self.assertEqual(thread_json['weight'], 1)
  252. self.override_acl({'can_pin_threads': 0})
  253. response = self.patch(
  254. self.api_link, [
  255. {
  256. 'op': 'replace',
  257. 'path': 'weight',
  258. 'value': 0,
  259. },
  260. ]
  261. )
  262. self.assertEqual(response.status_code, 400)
  263. response_json = response.json()
  264. self.assertEqual(
  265. response_json['detail'][0], "You don't have permission to change this thread's weight."
  266. )
  267. thread_json = self.get_thread_json()
  268. self.assertEqual(thread_json['weight'], 1)
  269. class ThreadMoveApiTests(ThreadPatchApiTestCase):
  270. def setUp(self):
  271. super(ThreadMoveApiTests, self).setUp()
  272. Category(
  273. name='Category B',
  274. slug='category-b',
  275. ).insert_at(
  276. self.category,
  277. position='last-child',
  278. save=True,
  279. )
  280. self.category_b = Category.objects.get(slug='category-b')
  281. def override_other_acl(self, acl):
  282. other_category_acl = self.user.acl_cache['categories'][self.category.pk].copy()
  283. other_category_acl.update({
  284. 'can_see': 1,
  285. 'can_browse': 1,
  286. 'can_see_all_threads': 1,
  287. 'can_see_own_threads': 0,
  288. 'can_hide_threads': 0,
  289. 'can_approve_content': 0,
  290. })
  291. other_category_acl.update(acl)
  292. categories_acl = self.user.acl_cache['categories']
  293. categories_acl[self.category_b.pk] = other_category_acl
  294. visible_categories = [self.category.pk]
  295. if other_category_acl['can_see']:
  296. visible_categories.append(self.category_b.pk)
  297. override_acl(
  298. self.user, {
  299. 'visible_categories': visible_categories,
  300. 'categories': categories_acl,
  301. }
  302. )
  303. def test_move_thread_no_top(self):
  304. """api moves thread to other category, sets no top category"""
  305. self.override_acl({'can_move_threads': True})
  306. self.override_other_acl({'can_start_threads': 2})
  307. response = self.patch(
  308. self.api_link, [
  309. {
  310. 'op': 'replace',
  311. 'path': 'category',
  312. 'value': self.category_b.pk,
  313. },
  314. {
  315. 'op': 'add',
  316. 'path': 'top-category',
  317. 'value': self.category_b.pk,
  318. },
  319. {
  320. 'op': 'replace',
  321. 'path': 'flatten-categories',
  322. 'value': None,
  323. },
  324. ]
  325. )
  326. self.assertEqual(response.status_code, 200)
  327. reponse_json = response.json()
  328. self.assertEqual(reponse_json['category'], self.category_b.pk)
  329. self.override_other_acl({})
  330. thread_json = self.get_thread_json()
  331. self.assertEqual(thread_json['category']['id'], self.category_b.pk)
  332. def test_move_thread_with_top(self):
  333. """api moves thread to other category, sets top"""
  334. self.override_acl({'can_move_threads': True})
  335. self.override_other_acl({'can_start_threads': 2})
  336. response = self.patch(
  337. self.api_link, [
  338. {
  339. 'op': 'replace',
  340. 'path': 'category',
  341. 'value': self.category_b.pk,
  342. },
  343. {
  344. 'op': 'add',
  345. 'path': 'top-category',
  346. 'value': Category.objects.root_category().pk,
  347. },
  348. {
  349. 'op': 'replace',
  350. 'path': 'flatten-categories',
  351. 'value': None,
  352. },
  353. ]
  354. )
  355. self.assertEqual(response.status_code, 200)
  356. reponse_json = response.json()
  357. self.assertEqual(reponse_json['category'], self.category_b.pk)
  358. self.override_other_acl({})
  359. thread_json = self.get_thread_json()
  360. self.assertEqual(thread_json['category']['id'], self.category_b.pk)
  361. def test_move_thread_no_permission(self):
  362. """api move thread to other category with no permission fails"""
  363. self.override_acl({'can_move_threads': False})
  364. self.override_other_acl({})
  365. response = self.patch(
  366. self.api_link, [
  367. {
  368. 'op': 'replace',
  369. 'path': 'category',
  370. 'value': self.category_b.pk,
  371. },
  372. ]
  373. )
  374. self.assertEqual(response.status_code, 400)
  375. response_json = response.json()
  376. self.assertEqual(
  377. response_json['detail'][0], "You don't have permission to move this thread."
  378. )
  379. self.override_other_acl({})
  380. thread_json = self.get_thread_json()
  381. self.assertEqual(thread_json['category']['id'], self.category.pk)
  382. def test_move_thread_no_category_access(self):
  383. """api move thread to category with no access fails"""
  384. self.override_acl({'can_move_threads': True})
  385. self.override_other_acl({'can_see': False})
  386. response = self.patch(
  387. self.api_link, [
  388. {
  389. 'op': 'replace',
  390. 'path': 'category',
  391. 'value': self.category_b.pk,
  392. },
  393. ]
  394. )
  395. self.assertEqual(response.status_code, 400)
  396. response_json = response.json()
  397. self.assertEqual(response_json['detail'][0], 'NOT FOUND')
  398. self.override_other_acl({})
  399. thread_json = self.get_thread_json()
  400. self.assertEqual(thread_json['category']['id'], self.category.pk)
  401. def test_move_thread_no_category_browse(self):
  402. """api move thread to category with no browsing access fails"""
  403. self.override_acl({'can_move_threads': True})
  404. self.override_other_acl({'can_browse': False})
  405. response = self.patch(
  406. self.api_link, [
  407. {
  408. 'op': 'replace',
  409. 'path': 'category',
  410. 'value': self.category_b.pk,
  411. },
  412. ]
  413. )
  414. self.assertEqual(response.status_code, 400)
  415. response_json = response.json()
  416. self.assertEqual(
  417. response_json['detail'][0],
  418. 'You don\'t have permission to browse "Category B" contents.'
  419. )
  420. self.override_other_acl({})
  421. thread_json = self.get_thread_json()
  422. self.assertEqual(thread_json['category']['id'], self.category.pk)
  423. def test_move_thread_same_category(self):
  424. """api move thread to category it's already in fails"""
  425. self.override_acl({'can_move_threads': True})
  426. self.override_other_acl({'can_start_threads': 2})
  427. response = self.patch(
  428. self.api_link, [
  429. {
  430. 'op': 'replace',
  431. 'path': 'category',
  432. 'value': self.thread.category_id,
  433. },
  434. ]
  435. )
  436. self.assertEqual(response.status_code, 400)
  437. response_json = response.json()
  438. self.assertEqual(
  439. response_json['detail'][0], "You can't move thread to the category it's already in."
  440. )
  441. self.override_other_acl({})
  442. thread_json = self.get_thread_json()
  443. self.assertEqual(thread_json['category']['id'], self.category.pk)
  444. def test_thread_flatten_categories(self):
  445. """api flatten thread categories"""
  446. response = self.patch(
  447. self.api_link, [
  448. {
  449. 'op': 'replace',
  450. 'path': 'flatten-categories',
  451. 'value': None,
  452. },
  453. ]
  454. )
  455. self.assertEqual(response.status_code, 200)
  456. response_json = response.json()
  457. self.assertEqual(response_json['category'], self.category.pk)
  458. class ThreadCloseApiTests(ThreadPatchApiTestCase):
  459. def test_close_thread(self):
  460. """api makes it possible to close thread"""
  461. self.override_acl({'can_close_threads': True})
  462. response = self.patch(
  463. self.api_link, [
  464. {
  465. 'op': 'replace',
  466. 'path': 'is-closed',
  467. 'value': True,
  468. },
  469. ]
  470. )
  471. self.assertEqual(response.status_code, 200)
  472. response_json = response.json()
  473. self.assertTrue(response_json['is_closed'])
  474. thread_json = self.get_thread_json()
  475. self.assertTrue(thread_json['is_closed'])
  476. def test_open_thread(self):
  477. """api makes it possible to open thread"""
  478. self.thread.is_closed = True
  479. self.thread.save()
  480. thread_json = self.get_thread_json()
  481. self.assertTrue(thread_json['is_closed'])
  482. self.override_acl({'can_close_threads': True})
  483. response = self.patch(
  484. self.api_link, [
  485. {
  486. 'op': 'replace',
  487. 'path': 'is-closed',
  488. 'value': False,
  489. },
  490. ]
  491. )
  492. self.assertEqual(response.status_code, 200)
  493. response_json = response.json()
  494. self.assertFalse(response_json['is_closed'])
  495. thread_json = self.get_thread_json()
  496. self.assertFalse(thread_json['is_closed'])
  497. def test_close_thread_no_permission(self):
  498. """api close thread with no permission fails"""
  499. self.override_acl({'can_close_threads': False})
  500. response = self.patch(
  501. self.api_link, [
  502. {
  503. 'op': 'replace',
  504. 'path': 'is-closed',
  505. 'value': True,
  506. },
  507. ]
  508. )
  509. self.assertEqual(response.status_code, 400)
  510. response_json = response.json()
  511. self.assertEqual(
  512. response_json['detail'][0], "You don't have permission to close this thread."
  513. )
  514. thread_json = self.get_thread_json()
  515. self.assertFalse(thread_json['is_closed'])
  516. def test_open_thread_no_permission(self):
  517. """api open thread with no permission fails"""
  518. self.thread.is_closed = True
  519. self.thread.save()
  520. thread_json = self.get_thread_json()
  521. self.assertTrue(thread_json['is_closed'])
  522. self.override_acl({'can_close_threads': False})
  523. response = self.patch(
  524. self.api_link, [
  525. {
  526. 'op': 'replace',
  527. 'path': 'is-closed',
  528. 'value': False,
  529. },
  530. ]
  531. )
  532. self.assertEqual(response.status_code, 400)
  533. response_json = response.json()
  534. self.assertEqual(
  535. response_json['detail'][0], "You don't have permission to open this thread."
  536. )
  537. thread_json = self.get_thread_json()
  538. self.assertTrue(thread_json['is_closed'])
  539. class ThreadApproveApiTests(ThreadPatchApiTestCase):
  540. def test_approve_thread(self):
  541. """api makes it possible to approve thread"""
  542. self.thread.first_post.is_unapproved = True
  543. self.thread.first_post.save()
  544. self.thread.synchronize()
  545. self.thread.save()
  546. self.assertTrue(self.thread.is_unapproved)
  547. self.assertTrue(self.thread.has_unapproved_posts)
  548. self.override_acl({'can_approve_content': 1})
  549. response = self.patch(
  550. self.api_link, [
  551. {
  552. 'op': 'replace',
  553. 'path': 'is-unapproved',
  554. 'value': False,
  555. },
  556. ]
  557. )
  558. self.assertEqual(response.status_code, 200)
  559. response_json = response.json()
  560. self.assertFalse(response_json['is_unapproved'])
  561. self.assertFalse(response_json['has_unapproved_posts'])
  562. thread_json = self.get_thread_json()
  563. self.assertFalse(thread_json['is_unapproved'])
  564. self.assertFalse(thread_json['has_unapproved_posts'])
  565. thread = Thread.objects.get(pk=self.thread.pk)
  566. self.assertFalse(thread.is_unapproved)
  567. self.assertFalse(thread.has_unapproved_posts)
  568. def test_unapprove_thread(self):
  569. """api returns permission error on approval removal"""
  570. self.override_acl({'can_approve_content': 1})
  571. response = self.patch(
  572. self.api_link, [
  573. {
  574. 'op': 'replace',
  575. 'path': 'is-unapproved',
  576. 'value': True,
  577. },
  578. ]
  579. )
  580. self.assertEqual(response.status_code, 400)
  581. response_json = response.json()
  582. self.assertEqual(response_json['detail'][0], "Content approval can't be reversed.")
  583. class ThreadHideApiTests(ThreadPatchApiTestCase):
  584. def test_hide_thread(self):
  585. """api makes it possible to hide thread"""
  586. self.override_acl({'can_hide_threads': 1})
  587. response = self.patch(
  588. self.api_link, [
  589. {
  590. 'op': 'replace',
  591. 'path': 'is-hidden',
  592. 'value': True,
  593. },
  594. ]
  595. )
  596. self.assertEqual(response.status_code, 200)
  597. reponse_json = response.json()
  598. self.assertTrue(reponse_json['is_hidden'])
  599. self.override_acl({'can_hide_threads': 1})
  600. thread_json = self.get_thread_json()
  601. self.assertTrue(thread_json['is_hidden'])
  602. def test_show_thread(self):
  603. """api makes it possible to unhide thread"""
  604. self.thread.is_hidden = True
  605. self.thread.save()
  606. self.override_acl({'can_hide_threads': 1})
  607. thread_json = self.get_thread_json()
  608. self.assertTrue(thread_json['is_hidden'])
  609. self.override_acl({'can_hide_threads': 1})
  610. response = self.patch(
  611. self.api_link, [
  612. {
  613. 'op': 'replace',
  614. 'path': 'is-hidden',
  615. 'value': False,
  616. },
  617. ]
  618. )
  619. self.assertEqual(response.status_code, 200)
  620. reponse_json = response.json()
  621. self.assertFalse(reponse_json['is_hidden'])
  622. self.override_acl({'can_hide_threads': 1})
  623. thread_json = self.get_thread_json()
  624. self.assertFalse(thread_json['is_hidden'])
  625. def test_hide_thread_no_permission(self):
  626. """api hide thread with no permission fails"""
  627. self.override_acl({'can_hide_threads': 0})
  628. response = self.patch(
  629. self.api_link, [
  630. {
  631. 'op': 'replace',
  632. 'path': 'is-hidden',
  633. 'value': True,
  634. },
  635. ]
  636. )
  637. self.assertEqual(response.status_code, 400)
  638. response_json = response.json()
  639. self.assertEqual(
  640. response_json['detail'][0], "You don't have permission to hide this thread."
  641. )
  642. thread_json = self.get_thread_json()
  643. self.assertFalse(thread_json['is_hidden'])
  644. def test_show_thread_no_permission(self):
  645. """api unhide thread with no permission fails"""
  646. self.thread.is_hidden = True
  647. self.thread.save()
  648. self.override_acl({'can_hide_threads': 1})
  649. thread_json = self.get_thread_json()
  650. self.assertTrue(thread_json['is_hidden'])
  651. self.override_acl({'can_hide_threads': 0})
  652. response = self.patch(
  653. self.api_link, [
  654. {
  655. 'op': 'replace',
  656. 'path': 'is-hidden',
  657. 'value': False,
  658. },
  659. ]
  660. )
  661. self.assertEqual(response.status_code, 404)
  662. class ThreadSubscribeApiTests(ThreadPatchApiTestCase):
  663. def test_subscribe_thread(self):
  664. """api makes it possible to subscribe thread"""
  665. response = self.patch(
  666. self.api_link, [
  667. {
  668. 'op': 'replace',
  669. 'path': 'subscription',
  670. 'value': 'notify',
  671. },
  672. ]
  673. )
  674. self.assertEqual(response.status_code, 200)
  675. reponse_json = response.json()
  676. self.assertFalse(reponse_json['subscription'])
  677. thread_json = self.get_thread_json()
  678. self.assertFalse(thread_json['subscription'])
  679. subscription = self.user.subscription_set.get(thread=self.thread)
  680. self.assertFalse(subscription.send_email)
  681. def test_subscribe_thread_with_email(self):
  682. """api makes it possible to subscribe thread with emails"""
  683. response = self.patch(
  684. self.api_link, [
  685. {
  686. 'op': 'replace',
  687. 'path': 'subscription',
  688. 'value': 'email',
  689. },
  690. ]
  691. )
  692. self.assertEqual(response.status_code, 200)
  693. reponse_json = response.json()
  694. self.assertTrue(reponse_json['subscription'])
  695. thread_json = self.get_thread_json()
  696. self.assertTrue(thread_json['subscription'])
  697. subscription = self.user.subscription_set.get(thread=self.thread)
  698. self.assertTrue(subscription.send_email)
  699. def test_unsubscribe_thread(self):
  700. """api makes it possible to unsubscribe thread"""
  701. response = self.patch(
  702. self.api_link, [
  703. {
  704. 'op': 'replace',
  705. 'path': 'subscription',
  706. 'value': 'remove',
  707. },
  708. ]
  709. )
  710. self.assertEqual(response.status_code, 200)
  711. reponse_json = response.json()
  712. self.assertIsNone(reponse_json['subscription'])
  713. thread_json = self.get_thread_json()
  714. self.assertIsNone(thread_json['subscription'])
  715. self.assertEqual(self.user.subscription_set.count(), 0)
  716. def test_subscribe_as_guest(self):
  717. """api makes it impossible to subscribe thread"""
  718. self.logout_user()
  719. response = self.patch(
  720. self.api_link, [
  721. {
  722. 'op': 'replace',
  723. 'path': 'subscription',
  724. 'value': 'email',
  725. },
  726. ]
  727. )
  728. self.assertEqual(response.status_code, 403)
  729. def test_subscribe_nonexistant_thread(self):
  730. """api makes it impossible to subscribe nonexistant thread"""
  731. bad_api_link = self.api_link.replace(
  732. six.text_type(self.thread.pk), six.text_type(self.thread.pk + 9)
  733. )
  734. response = self.patch(
  735. bad_api_link, [
  736. {
  737. 'op': 'replace',
  738. 'path': 'subscription',
  739. 'value': 'email',
  740. },
  741. ]
  742. )
  743. self.assertEqual(response.status_code, 404)