test_privatethread_patch_api.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  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. UserModel = 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 = UserModel.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 = UserModel.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(
  291. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  292. )
  293. self.assertTrue(
  294. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  295. )
  296. # user was removed from participation
  297. self.assertEqual(self.thread.participants.count(), 1)
  298. self.assertEqual(self.thread.participants.filter(pk=self.user.pk).count(), 0)
  299. # thread was removed from user subscriptions
  300. self.assertEqual(self.user.subscription_set.count(), 0)
  301. def test_user_leave_closed_thread(self):
  302. """api allows user to remove himself from closed thread"""
  303. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  304. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  305. self.thread.is_closed = True
  306. self.thread.save()
  307. response = self.patch(
  308. self.api_link,
  309. [{"op": "remove", "path": "participants", "value": self.user.pk}],
  310. )
  311. self.assertEqual(response.status_code, 200)
  312. self.assertFalse(response.json()["deleted"])
  313. # thread still exists
  314. self.assertTrue(Thread.objects.get(pk=self.thread.pk))
  315. # leave event has valid type
  316. event = self.thread.post_set.order_by("id").last()
  317. self.assertTrue(event.is_event)
  318. self.assertTrue(event.event_type, "participant_left")
  319. # valid users were flagged for sync
  320. self.assertTrue(
  321. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  322. )
  323. self.assertTrue(
  324. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  325. )
  326. # user was removed from participation
  327. self.assertEqual(self.thread.participants.count(), 1)
  328. self.assertEqual(self.thread.participants.filter(pk=self.user.pk).count(), 0)
  329. @patch_user_acl({"can_moderate_private_threads": True})
  330. def test_moderator_remove_user(self):
  331. """api allows moderator to remove other user"""
  332. removed_user = UserModel.objects.create_user(
  333. "Vigilante", "test@test.com", "pass123"
  334. )
  335. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  336. ThreadParticipant.objects.add_participants(
  337. self.thread, [self.user, removed_user]
  338. )
  339. response = self.patch(
  340. self.api_link,
  341. [{"op": "remove", "path": "participants", "value": removed_user.pk}],
  342. )
  343. self.assertEqual(response.status_code, 200)
  344. self.assertFalse(response.json()["deleted"])
  345. # thread still exists
  346. self.assertTrue(Thread.objects.get(pk=self.thread.pk))
  347. # leave event has valid type
  348. event = self.thread.post_set.order_by("id").last()
  349. self.assertTrue(event.is_event)
  350. self.assertTrue(event.event_type, "participant_removed")
  351. # valid users were flagged for sync
  352. self.assertTrue(
  353. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  354. )
  355. self.assertTrue(
  356. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  357. )
  358. self.assertTrue(
  359. UserModel.objects.get(pk=removed_user.pk).sync_unread_private_threads
  360. )
  361. # user was removed from participation
  362. self.assertEqual(self.thread.participants.count(), 2)
  363. self.assertEqual(self.thread.participants.filter(pk=removed_user.pk).count(), 0)
  364. def test_owner_remove_user(self):
  365. """api allows owner to remove other user"""
  366. ThreadParticipant.objects.set_owner(self.thread, self.user)
  367. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  368. response = self.patch(
  369. self.api_link,
  370. [{"op": "remove", "path": "participants", "value": self.other_user.pk}],
  371. )
  372. self.assertEqual(response.status_code, 200)
  373. self.assertFalse(response.json()["deleted"])
  374. # thread still exists
  375. self.assertTrue(Thread.objects.get(pk=self.thread.pk))
  376. # leave event has valid type
  377. event = self.thread.post_set.order_by("id").last()
  378. self.assertTrue(event.is_event)
  379. self.assertTrue(event.event_type, "participant_removed")
  380. # valid users were flagged for sync
  381. self.assertTrue(
  382. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  383. )
  384. self.assertTrue(
  385. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  386. )
  387. # user was removed from participation
  388. self.assertEqual(self.thread.participants.count(), 1)
  389. self.assertEqual(
  390. self.thread.participants.filter(pk=self.other_user.pk).count(), 0
  391. )
  392. def test_owner_leave_thread(self):
  393. """api allows owner to remove hisemf from thread, causing thread to close"""
  394. ThreadParticipant.objects.set_owner(self.thread, self.user)
  395. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  396. response = self.patch(
  397. self.api_link,
  398. [{"op": "remove", "path": "participants", "value": self.user.pk}],
  399. )
  400. self.assertEqual(response.status_code, 200)
  401. self.assertFalse(response.json()["deleted"])
  402. # thread still exists and is closed
  403. self.assertTrue(Thread.objects.get(pk=self.thread.pk).is_closed)
  404. # leave event has valid type
  405. event = self.thread.post_set.order_by("id").last()
  406. self.assertTrue(event.is_event)
  407. self.assertTrue(event.event_type, "owner_left")
  408. # valid users were flagged for sync
  409. self.assertTrue(
  410. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  411. )
  412. self.assertTrue(
  413. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  414. )
  415. # user was removed from participation
  416. self.assertEqual(self.thread.participants.count(), 1)
  417. self.assertEqual(self.thread.participants.filter(pk=self.user.pk).count(), 0)
  418. def test_last_user_leave_thread(self):
  419. """api allows last user leave thread, causing thread to delete"""
  420. ThreadParticipant.objects.set_owner(self.thread, self.user)
  421. response = self.patch(
  422. self.api_link,
  423. [{"op": "remove", "path": "participants", "value": self.user.pk}],
  424. )
  425. self.assertEqual(response.status_code, 200)
  426. self.assertTrue(response.json()["deleted"])
  427. # thread is gone
  428. with self.assertRaises(Thread.DoesNotExist):
  429. Thread.objects.get(pk=self.thread.pk)
  430. # valid users were flagged for sync
  431. self.assertTrue(
  432. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  433. )
  434. class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
  435. def test_empty_user_id(self):
  436. """api handles empty user id"""
  437. ThreadParticipant.objects.set_owner(self.thread, self.user)
  438. response = self.patch(
  439. self.api_link, [{"op": "replace", "path": "owner", "value": ""}]
  440. )
  441. self.assertEqual(response.status_code, 400)
  442. self.assertEqual(
  443. response.json(),
  444. {"id": self.thread.pk, "detail": ["A valid integer is required."]},
  445. )
  446. def test_invalid_user_id(self):
  447. """api handles invalid user id"""
  448. ThreadParticipant.objects.set_owner(self.thread, self.user)
  449. response = self.patch(
  450. self.api_link, [{"op": "replace", "path": "owner", "value": "dsadsa"}]
  451. )
  452. self.assertEqual(response.status_code, 400)
  453. self.assertEqual(
  454. response.json(),
  455. {"id": self.thread.pk, "detail": ["A valid integer is required."]},
  456. )
  457. def test_nonexistant_user_id(self):
  458. """api handles nonexistant user id"""
  459. ThreadParticipant.objects.set_owner(self.thread, self.user)
  460. response = self.patch(
  461. self.api_link,
  462. [{"op": "replace", "path": "owner", "value": self.other_user.pk}],
  463. )
  464. self.assertEqual(response.status_code, 400)
  465. self.assertEqual(
  466. response.json(),
  467. {"id": self.thread.pk, "detail": ["Participant doesn't exist."]},
  468. )
  469. def test_no_permission(self):
  470. """non-moderator/owner can't change owner"""
  471. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  472. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  473. response = self.patch(
  474. self.api_link, [{"op": "replace", "path": "owner", "value": self.user.pk}]
  475. )
  476. self.assertEqual(response.status_code, 400)
  477. self.assertEqual(
  478. response.json(),
  479. {
  480. "id": self.thread.pk,
  481. "detail": [
  482. "Only thread owner and moderators can change threads owners."
  483. ],
  484. },
  485. )
  486. def test_no_change(self):
  487. """api validates that new owner id is same as current owner"""
  488. ThreadParticipant.objects.set_owner(self.thread, self.user)
  489. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  490. response = self.patch(
  491. self.api_link, [{"op": "replace", "path": "owner", "value": self.user.pk}]
  492. )
  493. self.assertEqual(response.status_code, 400)
  494. self.assertEqual(
  495. response.json(),
  496. {"id": self.thread.pk, "detail": ["This user already is thread owner."]},
  497. )
  498. def test_change_closed_thread_owner(self):
  499. """non-moderator can't change owner in closed thread"""
  500. ThreadParticipant.objects.set_owner(self.thread, self.user)
  501. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  502. self.thread.is_closed = True
  503. self.thread.save()
  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, 400)
  509. self.assertEqual(
  510. response.json(),
  511. {
  512. "id": self.thread.pk,
  513. "detail": ["Only moderators can change closed threads owners."],
  514. },
  515. )
  516. def test_owner_change_thread_owner(self):
  517. """owner can pass thread ownership to other participant"""
  518. ThreadParticipant.objects.set_owner(self.thread, self.user)
  519. ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
  520. response = self.patch(
  521. self.api_link,
  522. [{"op": "replace", "path": "owner", "value": self.other_user.pk}],
  523. )
  524. self.assertEqual(response.status_code, 200)
  525. # valid users were flagged for sync
  526. self.assertFalse(
  527. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  528. )
  529. self.assertTrue(
  530. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  531. )
  532. # ownership was transfered
  533. self.assertEqual(self.thread.participants.count(), 2)
  534. self.assertTrue(ThreadParticipant.objects.get(user=self.other_user).is_owner)
  535. self.assertFalse(ThreadParticipant.objects.get(user=self.user).is_owner)
  536. # change was recorded in event
  537. event = self.thread.post_set.order_by("id").last()
  538. self.assertTrue(event.is_event)
  539. self.assertTrue(event.event_type, "changed_owner")
  540. @patch_user_acl({"can_moderate_private_threads": True})
  541. def test_moderator_change_owner(self):
  542. """moderator can change thread owner to other user"""
  543. new_owner = UserModel.objects.create_user(
  544. "NewOwner", "new@owner.com", "pass123"
  545. )
  546. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  547. ThreadParticipant.objects.add_participants(self.thread, [self.user, new_owner])
  548. response = self.patch(
  549. self.api_link, [{"op": "replace", "path": "owner", "value": new_owner.pk}]
  550. )
  551. self.assertEqual(response.status_code, 200)
  552. # valid users were flagged for sync
  553. self.assertTrue(
  554. UserModel.objects.get(pk=new_owner.pk).sync_unread_private_threads
  555. )
  556. self.assertFalse(
  557. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  558. )
  559. self.assertTrue(
  560. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  561. )
  562. # ownership was transferred
  563. self.assertEqual(self.thread.participants.count(), 3)
  564. self.assertTrue(ThreadParticipant.objects.get(user=new_owner).is_owner)
  565. self.assertFalse(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, "changed_owner")
  571. @patch_user_acl({"can_moderate_private_threads": True})
  572. def test_moderator_takeover(self):
  573. """moderator can takeover the thread"""
  574. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  575. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  576. response = self.patch(
  577. self.api_link, [{"op": "replace", "path": "owner", "value": self.user.pk}]
  578. )
  579. self.assertEqual(response.status_code, 200)
  580. # valid users were flagged for sync
  581. self.assertFalse(
  582. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  583. )
  584. self.assertTrue(
  585. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  586. )
  587. # ownership was transfered
  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")
  595. @patch_user_acl({"can_moderate_private_threads": True})
  596. def test_moderator_closed_thread_takeover(self):
  597. """moderator can takeover closed thread thread"""
  598. ThreadParticipant.objects.set_owner(self.thread, self.other_user)
  599. ThreadParticipant.objects.add_participants(self.thread, [self.user])
  600. self.thread.is_closed = True
  601. self.thread.save()
  602. response = self.patch(
  603. self.api_link, [{"op": "replace", "path": "owner", "value": self.user.pk}]
  604. )
  605. self.assertEqual(response.status_code, 200)
  606. # valid users were flagged for sync
  607. self.assertFalse(
  608. UserModel.objects.get(pk=self.user.pk).sync_unread_private_threads
  609. )
  610. self.assertTrue(
  611. UserModel.objects.get(pk=self.other_user.pk).sync_unread_private_threads
  612. )
  613. # ownership was transferred
  614. self.assertEqual(self.thread.participants.count(), 2)
  615. self.assertTrue(ThreadParticipant.objects.get(user=self.user).is_owner)
  616. self.assertFalse(ThreadParticipant.objects.get(user=self.other_user).is_owner)
  617. # change was recorded in event
  618. event = self.thread.post_set.order_by("id").last()
  619. self.assertTrue(event.is_event)
  620. self.assertTrue(event.event_type, "tookover")