test_privatethread_patch_api.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. import json
  2. from django.contrib.auth import get_user_model
  3. from django.core import mail
  4. from misago.acl.testutils import override_acl
  5. from misago.threads import testutils
  6. from misago.threads.models import Thread, ThreadParticipant
  7. from .test_privatethreads import PrivateThreadsTestCase
  8. UserModel = get_user_model()
  9. class PrivateThreadPatchApiTestCase(PrivateThreadsTestCase):
  10. def setUp(self):
  11. super().setUp()
  12. self.thread = testutils.post_thread(self.category, poster=self.user)
  13. self.api_link = self.thread.get_api_url()
  14. self.other_user = UserModel.objects.create_user(
  15. 'BobBoberson', 'bob@boberson.com', 'pass123'
  16. )
  17. def patch(self, api_link, ops):
  18. return self.client.patch(api_link, json.dumps(ops), content_type="application/json")
  19. class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
  20. def test_add_participant_not_owner(self):
  21. """non-owner can't add participant"""
  22. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  23. response = self.patch(
  24. self.api_link, [
  25. {
  26. 'op': 'add',
  27. 'path': 'participants',
  28. 'value': self.user.username,
  29. },
  30. ]
  31. )
  32. self.assertContains(
  33. response, "be thread owner to add new participants to it", status_code=400
  34. )
  35. def test_add_empty_username(self):
  36. """path validates username"""
  37. ThreadParticipant.objects.set_owner(self.thread, self.user)
  38. response = self.patch(
  39. self.api_link, [
  40. {
  41. 'op': 'add',
  42. 'path': 'participants',
  43. 'value': '',
  44. },
  45. ]
  46. )
  47. self.assertContains(
  48. response, "You have to enter new participant's username.", status_code=400
  49. )
  50. def test_add_nonexistant_user(self):
  51. """can't user two times"""
  52. ThreadParticipant.objects.set_owner(self.thread, self.user)
  53. response = self.patch(
  54. self.api_link, [
  55. {
  56. 'op': 'add',
  57. 'path': 'participants',
  58. 'value': 'InvalidUser',
  59. },
  60. ]
  61. )
  62. self.assertContains(response, "No user with such name exists.", status_code=400)
  63. def test_add_already_participant(self):
  64. """can't add user that is already participant"""
  65. ThreadParticipant.objects.set_owner(self.thread, self.user)
  66. response = self.patch(
  67. self.api_link, [
  68. {
  69. 'op': 'add',
  70. 'path': 'participants',
  71. 'value': self.user.username,
  72. },
  73. ]
  74. )
  75. self.assertContains(response, "This user is already thread participant", status_code=400)
  76. def test_add_blocking_user(self):
  77. """can't add user that is already participant"""
  78. ThreadParticipant.objects.set_owner(self.thread, self.user)
  79. self.other_user.blocks.add(self.user)
  80. response = self.patch(
  81. self.api_link, [
  82. {
  83. 'op': 'add',
  84. 'path': 'participants',
  85. 'value': self.other_user.username,
  86. },
  87. ]
  88. )
  89. self.assertContains(response, "BobBoberson is blocking you.", status_code=400)
  90. def test_add_no_perm_user(self):
  91. """can't add user that has no permission to use private threads"""
  92. ThreadParticipant.objects.set_owner(self.thread, self.user)
  93. override_acl(self.other_user, {'can_use_private_threads': 0})
  94. response = self.patch(
  95. self.api_link, [
  96. {
  97. 'op': 'add',
  98. 'path': 'participants',
  99. 'value': self.other_user.username,
  100. },
  101. ]
  102. )
  103. self.assertContains(response, "BobBoberson can't participate", status_code=400)
  104. def test_add_too_many_users(self):
  105. """can't add user that is already participant"""
  106. ThreadParticipant.objects.set_owner(self.thread, self.user)
  107. for i in range(self.user.acl_cache['max_private_thread_participants']):
  108. user = UserModel.objects.create_user(
  109. 'User{}'.format(i), 'user{}@example.com'.format(i), 'Pass.123'
  110. )
  111. ThreadParticipant.objects.add_participants(self.thread, [user])
  112. response = self.patch(
  113. self.api_link, [
  114. {
  115. 'op': 'add',
  116. 'path': 'participants',
  117. 'value': self.other_user.username,
  118. },
  119. ]
  120. )
  121. self.assertContains(
  122. response, "You can't add any more new users to this thread.", status_code=400
  123. )
  124. def test_add_user_closed_thread(self):
  125. """adding user to closed thread fails for non-moderator"""
  126. ThreadParticipant.objects.set_owner(self.thread, self.user)
  127. self.thread.is_closed = True
  128. self.thread.save()
  129. response = self.patch(
  130. self.api_link, [
  131. {
  132. 'op': 'add',
  133. 'path': 'participants',
  134. 'value': self.other_user.username,
  135. },
  136. ]
  137. )
  138. self.assertContains(
  139. response, "Only moderators can add participants to closed threads.", status_code=400
  140. )
  141. def test_add_user(self):
  142. """adding user to thread add user to thread as participant, sets event and emails him"""
  143. ThreadParticipant.objects.set_owner(self.thread, self.user)
  144. self.patch(
  145. self.api_link, [
  146. {
  147. 'op': 'add',
  148. 'path': 'participants',
  149. 'value': self.other_user.username,
  150. },
  151. ]
  152. )
  153. # event was set on thread
  154. event = self.thread.post_set.order_by('id').last()
  155. self.assertTrue(event.is_event)
  156. self.assertTrue(event.event_type, 'added_participant')
  157. # notification about new private thread was sent to other user
  158. self.assertEqual(len(mail.outbox), 1)
  159. email = mail.outbox[-1]
  160. self.assertIn(self.user.username, email.subject)
  161. self.assertIn(self.thread.title, email.subject)
  162. def test_add_user_to_other_user_thread_moderator(self):
  163. """moderators can add users to other users threads"""
  164. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  165. self.thread.has_reported_posts = True
  166. self.thread.save()
  167. override_acl(self.user, {'can_moderate_private_threads': 1})
  168. self.patch(
  169. self.api_link, [
  170. {
  171. 'op': 'add',
  172. 'path': 'participants',
  173. 'value': self.user.username,
  174. },
  175. ]
  176. )
  177. # event was set on thread
  178. event = self.thread.post_set.order_by('id').last()
  179. self.assertTrue(event.is_event)
  180. self.assertTrue(event.event_type, 'entered_thread')
  181. # notification about new private thread wasn't send because we invited ourselves
  182. self.assertEqual(len(mail.outbox), 0)
  183. def test_add_user_to_closed_moderator(self):
  184. """moderators can add users to closed threads"""
  185. ThreadParticipant.objects.set_owner(self.thread, self.user)
  186. self.thread.is_closed = True
  187. self.thread.save()
  188. override_acl(self.user, {'can_moderate_private_threads': 1})
  189. self.patch(
  190. self.api_link, [
  191. {
  192. 'op': 'add',
  193. 'path': 'participants',
  194. 'value': self.other_user.username,
  195. },
  196. ]
  197. )
  198. # event was set on thread
  199. event = self.thread.post_set.order_by('id').last()
  200. self.assertTrue(event.is_event)
  201. self.assertTrue(event.event_type, 'added_participant')
  202. # notification about new private thread was sent to other user
  203. self.assertEqual(len(mail.outbox), 1)
  204. email = mail.outbox[-1]
  205. self.assertIn(self.user.username, email.subject)
  206. self.assertIn(self.thread.title, email.subject)
  207. class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
  208. def test_remove_empty(self):
  209. """api handles empty user id"""
  210. ThreadParticipant.objects.set_owner(self.thread, self.user)
  211. response = self.patch(
  212. self.api_link, [
  213. {
  214. 'op': 'remove',
  215. 'path': 'participants',
  216. 'value': '',
  217. },
  218. ]
  219. )
  220. self.assertContains(response, "A valid integer is required.", status_code=400)
  221. def test_remove_invalid(self):
  222. """api validates user id type"""
  223. ThreadParticipant.objects.set_owner(self.thread, self.user)
  224. response = self.patch(
  225. self.api_link, [
  226. {
  227. 'op': 'remove',
  228. 'path': 'participants',
  229. 'value': 'string',
  230. },
  231. ]
  232. )
  233. self.assertContains(response, "A valid integer is required.", status_code=400)
  234. def test_remove_nonexistant(self):
  235. """removed user has to be participant"""
  236. ThreadParticipant.objects.set_owner(self.thread, self.user)
  237. response = self.patch(
  238. self.api_link, [
  239. {
  240. 'op': 'remove',
  241. 'path': 'participants',
  242. 'value': self.other_user.pk,
  243. },
  244. ]
  245. )
  246. self.assertContains(response, "Participant doesn't exist.", status_code=400)
  247. def test_remove_not_owner(self):
  248. """api validates if user trying to remove other user is an owner"""
  249. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  250. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  251. response = self.patch(
  252. self.api_link, [
  253. {
  254. 'op': 'remove',
  255. 'path': 'participants',
  256. 'value': self.other_user.pk,
  257. },
  258. ]
  259. )
  260. self.assertContains(
  261. response, "be thread owner to remove participants from it", status_code=400
  262. )
  263. def test_owner_remove_user_closed_thread(self):
  264. """api disallows owner to remove other user from closed thread"""
  265. ThreadParticipant.objects.set_owner(self.thread, self.user)
  266. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  267. self.thread.is_closed = True
  268. self.thread.save()
  269. response = self.patch(
  270. self.api_link, [
  271. {
  272. 'op': 'remove',
  273. 'path': 'participants',
  274. 'value': self.other_user.pk,
  275. },
  276. ]
  277. )
  278. self.assertContains(
  279. response, "moderators can remove participants from closed threads", status_code=400
  280. )
  281. def test_user_leave_thread(self):
  282. """api allows user to remove himself from thread"""
  283. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  284. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  285. self.user.subscription_set.create(
  286. category=self.category,
  287. thread=self.thread,
  288. )
  289. response = self.patch(
  290. self.api_link, [
  291. {
  292. 'op': 'remove',
  293. 'path': 'participants',
  294. 'value': self.user.pk,
  295. },
  296. ]
  297. )
  298. self.assertEqual(response.status_code, 200)
  299. self.assertFalse(response.json()['deleted'])
  300. # thread still exists
  301. self.assertTrue(Thread.objects.get(pk=self.thread.pk))
  302. # leave event has valid type
  303. event = self.thread.post_set.order_by('id').last()
  304. self.assertTrue(event.is_event)
  305. self.assertTrue(event.event_type, 'participant_left')
  306. # valid users were flagged for sync
  307. self.assertTrue(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  308. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  309. # user was removed from participation
  310. self.assertEqual(self.thread.participants.count(), 1)
  311. self.assertEqual(self.thread.participants.filter(pk=self.user.pk).count(), 0)
  312. # thread was removed from user subscriptions
  313. self.assertEqual(self.user.subscription_set.count(), 0)
  314. def test_user_leave_closed_thread(self):
  315. """api allows user to remove himself from closed thread"""
  316. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  317. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  318. self.thread.is_closed = True
  319. self.thread.save()
  320. response = self.patch(
  321. self.api_link, [
  322. {
  323. 'op': 'remove',
  324. 'path': 'participants',
  325. 'value': self.user.pk,
  326. },
  327. ]
  328. )
  329. self.assertEqual(response.status_code, 200)
  330. self.assertFalse(response.json()['deleted'])
  331. # thread still exists
  332. self.assertTrue(Thread.objects.get(pk=self.thread.pk))
  333. # leave event has valid type
  334. event = self.thread.post_set.order_by('id').last()
  335. self.assertTrue(event.is_event)
  336. self.assertTrue(event.event_type, 'participant_left')
  337. # valid users were flagged for sync
  338. self.assertTrue(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  339. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  340. # user was removed from participation
  341. self.assertEqual(self.thread.participants.count(), 1)
  342. self.assertEqual(self.thread.participants.filter(pk=self.user.pk).count(), 0)
  343. def test_moderator_remove_user(self):
  344. """api allows moderator to remove other user"""
  345. removed_user = UserModel.objects.create_user('Vigilante', 'test@test.com', 'pass123')
  346. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  347. ThreadParticipant.objects.add_participants(self.thread, [self.user, removed_user])
  348. override_acl(self.user, {'can_moderate_private_threads': True})
  349. response = self.patch(
  350. self.api_link, [
  351. {
  352. 'op': 'remove',
  353. 'path': 'participants',
  354. 'value': removed_user.pk,
  355. },
  356. ]
  357. )
  358. self.assertEqual(response.status_code, 200)
  359. self.assertFalse(response.json()['deleted'])
  360. # thread still exists
  361. self.assertTrue(Thread.objects.get(pk=self.thread.pk))
  362. # leave event has valid type
  363. event = self.thread.post_set.order_by('id').last()
  364. self.assertTrue(event.is_event)
  365. self.assertTrue(event.event_type, 'participant_removed')
  366. # valid users were flagged for sync
  367. self.assertTrue(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  368. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  369. self.assertTrue(UserModel.objects.get(pk=removed_user.pk).sync_unread_private_threads)
  370. # user was removed from participation
  371. self.assertEqual(self.thread.participants.count(), 2)
  372. self.assertEqual(self.thread.participants.filter(pk=removed_user.pk).count(), 0)
  373. def test_owner_remove_user(self):
  374. """api allows owner to remove other user"""
  375. ThreadParticipant.objects.set_owner(self.thread, self.user)
  376. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  377. response = self.patch(
  378. self.api_link, [
  379. {
  380. 'op': 'remove',
  381. 'path': 'participants',
  382. 'value': self.other_user.pk,
  383. },
  384. ]
  385. )
  386. self.assertEqual(response.status_code, 200)
  387. self.assertFalse(response.json()['deleted'])
  388. # thread still exists
  389. self.assertTrue(Thread.objects.get(pk=self.thread.pk))
  390. # leave event has valid type
  391. event = self.thread.post_set.order_by('id').last()
  392. self.assertTrue(event.is_event)
  393. self.assertTrue(event.event_type, 'participant_removed')
  394. # valid users were flagged for sync
  395. self.assertTrue(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  396. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  397. # user was removed from participation
  398. self.assertEqual(self.thread.participants.count(), 1)
  399. self.assertEqual(self.thread.participants.filter(pk=self.other_user.pk).count(), 0)
  400. def test_owner_leave_thread(self):
  401. """api allows owner to remove hisemf from thread, causing thread to close"""
  402. ThreadParticipant.objects.set_owner(self.thread, self.user)
  403. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  404. response = self.patch(
  405. self.api_link, [
  406. {
  407. 'op': 'remove',
  408. 'path': 'participants',
  409. 'value': self.user.pk,
  410. },
  411. ]
  412. )
  413. self.assertEqual(response.status_code, 200)
  414. self.assertFalse(response.json()['deleted'])
  415. # thread still exists and is closed
  416. self.assertTrue(Thread.objects.get(pk=self.thread.pk).is_closed)
  417. # leave event has valid type
  418. event = self.thread.post_set.order_by('id').last()
  419. self.assertTrue(event.is_event)
  420. self.assertTrue(event.event_type, 'owner_left')
  421. # valid users were flagged for sync
  422. self.assertTrue(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  423. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  424. # user was removed from participation
  425. self.assertEqual(self.thread.participants.count(), 1)
  426. self.assertEqual(self.thread.participants.filter(pk=self.user.pk).count(), 0)
  427. def test_last_user_leave_thread(self):
  428. """api allows last user leave thread, causing thread to delete"""
  429. ThreadParticipant.objects.set_owner(self.thread, self.user)
  430. response = self.patch(
  431. self.api_link, [
  432. {
  433. 'op': 'remove',
  434. 'path': 'participants',
  435. 'value': self.user.pk,
  436. },
  437. ]
  438. )
  439. self.assertEqual(response.status_code, 200)
  440. self.assertTrue(response.json()['deleted'])
  441. # thread is gone
  442. with self.assertRaises(Thread.DoesNotExist):
  443. Thread.objects.get(pk=self.thread.pk)
  444. # valid users were flagged for sync
  445. self.assertTrue(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  446. class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
  447. def test_empty_user_id(self):
  448. """api handles empty user id"""
  449. ThreadParticipant.objects.set_owner(self.thread, self.user)
  450. response = self.patch(
  451. self.api_link, [
  452. {
  453. 'op': 'replace',
  454. 'path': 'owner',
  455. 'value': '',
  456. },
  457. ]
  458. )
  459. self.assertContains(response, "A valid integer is required.", status_code=400)
  460. def test_invalid_user_id(self):
  461. """api handles invalid user id"""
  462. ThreadParticipant.objects.set_owner(self.thread, self.user)
  463. response = self.patch(
  464. self.api_link, [
  465. {
  466. 'op': 'replace',
  467. 'path': 'owner',
  468. 'value': 'dsadsa',
  469. },
  470. ]
  471. )
  472. self.assertContains(response, "A valid integer is required.", status_code=400)
  473. def test_nonexistant_user_id(self):
  474. """api handles nonexistant user id"""
  475. ThreadParticipant.objects.set_owner(self.thread, self.user)
  476. response = self.patch(
  477. self.api_link, [
  478. {
  479. 'op': 'replace',
  480. 'path': 'owner',
  481. 'value': self.other_user.pk,
  482. },
  483. ]
  484. )
  485. self.assertContains(response, "Participant doesn't exist.", status_code=400)
  486. def test_no_permission(self):
  487. """non-moderator/owner can't change owner"""
  488. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  489. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  490. response = self.patch(
  491. self.api_link, [
  492. {
  493. 'op': 'replace',
  494. 'path': 'owner',
  495. 'value': self.user.pk,
  496. },
  497. ]
  498. )
  499. self.assertContains(
  500. response, "thread owner and moderators can change threads owners", status_code=400
  501. )
  502. def test_no_change(self):
  503. """api validates that new owner id is same as current owner"""
  504. ThreadParticipant.objects.set_owner(self.thread, self.user)
  505. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  506. response = self.patch(
  507. self.api_link, [
  508. {
  509. 'op': 'replace',
  510. 'path': 'owner',
  511. 'value': self.user.pk,
  512. },
  513. ]
  514. )
  515. self.assertContains(response, "This user already is thread owner.", status_code=400)
  516. def test_change_closed_thread_owner(self):
  517. """non-moderator can't change owner in closed thread"""
  518. ThreadParticipant.objects.set_owner(self.thread, self.user)
  519. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  520. self.thread.is_closed = True
  521. self.thread.save()
  522. response = self.patch(
  523. self.api_link, [
  524. {
  525. 'op': 'replace',
  526. 'path': 'owner',
  527. 'value': self.other_user.pk,
  528. },
  529. ]
  530. )
  531. self.assertContains(
  532. response, "Only moderators can change closed threads owners.", status_code=400
  533. )
  534. def test_owner_change_thread_owner(self):
  535. """owner can pass thread ownership to other participant"""
  536. ThreadParticipant.objects.set_owner(self.thread, self.user)
  537. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  538. response = self.patch(
  539. self.api_link, [
  540. {
  541. 'op': 'replace',
  542. 'path': 'owner',
  543. 'value': self.other_user.pk,
  544. },
  545. ]
  546. )
  547. self.assertEqual(response.status_code, 200)
  548. # valid users were flagged for sync
  549. self.assertFalse(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  550. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  551. # ownership was transfered
  552. self.assertEqual(self.thread.participants.count(), 2)
  553. self.assertTrue(ThreadParticipant.objects.get(user=self.other_user).is_owner)
  554. self.assertFalse(ThreadParticipant.objects.get(user=self.user).is_owner)
  555. # change was recorded in event
  556. event = self.thread.post_set.order_by('id').last()
  557. self.assertTrue(event.is_event)
  558. self.assertTrue(event.event_type, 'changed_owner')
  559. def test_moderator_change_owner(self):
  560. """moderator can change thread owner to other user"""
  561. new_owner = UserModel.objects.create_user('NewOwner', 'new@owner.com', 'pass123')
  562. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  563. ThreadParticipant.objects.add_participants(self.thread, [self.user, new_owner])
  564. override_acl(self.user, {'can_moderate_private_threads': 1})
  565. response = self.patch(
  566. self.api_link, [
  567. {
  568. 'op': 'replace',
  569. 'path': 'owner',
  570. 'value': new_owner.pk,
  571. },
  572. ]
  573. )
  574. self.assertEqual(response.status_code, 200)
  575. # valid users were flagged for sync
  576. self.assertTrue(UserModel.objects.get(pk=new_owner.pk).sync_unread_private_threads)
  577. self.assertFalse(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  578. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  579. # ownership was transfered
  580. self.assertEqual(self.thread.participants.count(), 3)
  581. self.assertTrue(ThreadParticipant.objects.get(user=new_owner).is_owner)
  582. self.assertFalse(ThreadParticipant.objects.get(user=self.user).is_owner)
  583. self.assertFalse(ThreadParticipant.objects.get(user=self.other_user).is_owner)
  584. # change was recorded in event
  585. event = self.thread.post_set.order_by('id').last()
  586. self.assertTrue(event.is_event)
  587. self.assertTrue(event.event_type, 'changed_owner')
  588. def test_moderator_takeover(self):
  589. """moderator can takeover the thread"""
  590. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  591. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  592. override_acl(self.user, {'can_moderate_private_threads': 1})
  593. response = self.patch(
  594. self.api_link, [
  595. {
  596. 'op': 'replace',
  597. 'path': 'owner',
  598. 'value': self.user.pk,
  599. },
  600. ]
  601. )
  602. self.assertEqual(response.status_code, 200)
  603. # valid users were flagged for sync
  604. self.assertFalse(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  605. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  606. # ownership was transfered
  607. self.assertEqual(self.thread.participants.count(), 2)
  608. self.assertTrue(ThreadParticipant.objects.get(user=self.user).is_owner)
  609. self.assertFalse(ThreadParticipant.objects.get(user=self.other_user).is_owner)
  610. # change was recorded in event
  611. event = self.thread.post_set.order_by('id').last()
  612. self.assertTrue(event.is_event)
  613. self.assertTrue(event.event_type, 'tookover')
  614. def test_moderator_closed_thread_takeover(self):
  615. """moderator can takeover closed thread thread"""
  616. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  617. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  618. self.thread.is_closed = True
  619. self.thread.save()
  620. override_acl(self.user, {'can_moderate_private_threads': 1})
  621. response = self.patch(
  622. self.api_link, [
  623. {
  624. 'op': 'replace',
  625. 'path': 'owner',
  626. 'value': self.user.pk,
  627. },
  628. ]
  629. )
  630. self.assertEqual(response.status_code, 200)
  631. # valid users were flagged for sync
  632. self.assertFalse(UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads)
  633. self.assertTrue(UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads)
  634. # ownership was transfered
  635. self.assertEqual(self.thread.participants.count(), 2)
  636. self.assertTrue(ThreadParticipant.objects.get(user=self.user).is_owner)
  637. self.assertFalse(ThreadParticipant.objects.get(user=self.other_user).is_owner)
  638. # change was recorded in event
  639. event = self.thread.post_set.order_by('id').last()
  640. self.assertTrue(event.is_event)
  641. self.assertTrue(event.event_type, 'tookover')