test_privatethread_patch_api.py 27 KB

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