test_privatethread_patch_api.py 27 KB

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