test_users_api.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. import json
  2. from datetime import timedelta
  3. from django.contrib.auth import get_user_model
  4. from django.urls import reverse
  5. from django.utils.encoding import smart_str
  6. from misago.acl.testutils import override_acl
  7. from misago.categories.models import Category
  8. from misago.conf import settings
  9. from misago.core import threadstore
  10. from misago.core.cache import cache
  11. from misago.threads.models import Post, Thread
  12. from misago.threads.testutils import post_thread
  13. from ..activepostersranking import build_active_posters_ranking
  14. from ..models import BAN_USERNAME, Ban, Rank
  15. from ..testutils import AuthenticatedUserTestCase
  16. class ActivePostersListTests(AuthenticatedUserTestCase):
  17. """
  18. tests for active posters list (GET /users/?list=active)
  19. """
  20. def setUp(self):
  21. super(ActivePostersListTests, self).setUp()
  22. self.link = '/api/users/?list=active'
  23. cache.clear()
  24. threadstore.clear()
  25. self.category = Category.objects.all_categories()[:1][0]
  26. self.category.labels = []
  27. def test_empty_list(self):
  28. """empty list is served"""
  29. response = self.client.get(self.link)
  30. self.assertEqual(response.status_code, 200)
  31. self.assertNotContains(response, self.user.username)
  32. response = self.client.get(self.link)
  33. self.assertEqual(response.status_code, 200)
  34. self.assertNotContains(response, self.user.username)
  35. def test_filled_list(self):
  36. """filled list is served"""
  37. post_thread(self.category, poster=self.user)
  38. self.user.posts = 1
  39. self.user.save()
  40. build_active_posters_ranking()
  41. response = self.client.get(self.link)
  42. self.assertEqual(response.status_code, 200)
  43. self.assertContains(response, self.user.username)
  44. self.assertContains(response, '"is_online":true')
  45. self.assertContains(response, '"is_offline":false')
  46. self.logout_user()
  47. build_active_posters_ranking()
  48. response = self.client.get(self.link)
  49. self.assertEqual(response.status_code, 200)
  50. self.assertContains(response, self.user.username)
  51. self.assertContains(response, '"is_online":false')
  52. self.assertContains(response, '"is_offline":true')
  53. class FollowersListTests(AuthenticatedUserTestCase):
  54. """
  55. tests for generic list (GET /users/) filtered by followers
  56. """
  57. def setUp(self):
  58. super(FollowersListTests, self).setUp()
  59. self.link = '/api/users/?&followers=%s'
  60. def test_nonexistent_user(self):
  61. """list for non-existing user returns 404"""
  62. response = self.client.get(self.link % 31242)
  63. self.assertEqual(response.status_code, 404)
  64. def test_empty_list(self):
  65. """user without followers returns 200"""
  66. response = self.client.get(self.link % self.user.pk)
  67. self.assertEqual(response.status_code, 200)
  68. def test_filled_list(self):
  69. """user with followers returns 200"""
  70. User = get_user_model()
  71. test_follower = User.objects.create_user(
  72. "TestFollower", "test@follower.com", self.USER_PASSWORD)
  73. self.user.followed_by.add(test_follower)
  74. response = self.client.get(self.link % self.user.pk)
  75. self.assertEqual(response.status_code, 200)
  76. self.assertContains(response, test_follower.username)
  77. class FollowsListTests(AuthenticatedUserTestCase):
  78. """
  79. tests for generic list (GET /users/) filtered by follows
  80. """
  81. def setUp(self):
  82. super(FollowsListTests, self).setUp()
  83. self.link = '/api/users/?&follows=%s'
  84. def test_nonexistent_user(self):
  85. """list for non-existing user returns 404"""
  86. response = self.client.get(self.link % 1321)
  87. self.assertEqual(response.status_code, 404)
  88. def test_empty_list(self):
  89. """user without follows returns 200"""
  90. response = self.client.get(self.link % self.user.pk)
  91. self.assertEqual(response.status_code, 200)
  92. def test_filled_list(self):
  93. """user with follows returns 200"""
  94. User = get_user_model()
  95. test_follower = User.objects.create_user(
  96. "TestFollower", "test@follower.com", self.USER_PASSWORD)
  97. self.user.follows.add(test_follower)
  98. response = self.client.get(self.link % self.user.pk)
  99. self.assertEqual(response.status_code, 200)
  100. self.assertContains(response, test_follower.username)
  101. def test_filled_list_search(self):
  102. """follows list is searchable"""
  103. User = get_user_model()
  104. test_follower = User.objects.create_user(
  105. "TestFollower", "test@follower.com", self.USER_PASSWORD)
  106. self.user.follows.add(test_follower)
  107. api_link = self.link % self.user.pk
  108. response = self.client.get('%s&name=%s' % (api_link, 'test'))
  109. self.assertEqual(response.status_code, 200)
  110. self.assertContains(response, test_follower.username)
  111. class RankListTests(AuthenticatedUserTestCase):
  112. """
  113. tests for generic list (GET /users/) filtered by rank
  114. """
  115. def setUp(self):
  116. super(RankListTests, self).setUp()
  117. self.link = '/api/users/?rank=%s'
  118. def test_nonexistent_rank(self):
  119. """list for non-existing rank returns 404"""
  120. response = self.client.get(self.link % 1421)
  121. self.assertEqual(response.status_code, 404)
  122. def test_empty_list(self):
  123. """tab rank without members returns 200"""
  124. test_rank = Rank.objects.create(
  125. name="Test rank",
  126. slug="test-rank",
  127. is_tab=True
  128. )
  129. response = self.client.get(self.link % test_rank.pk)
  130. self.assertEqual(response.status_code, 200)
  131. def test_disabled_list(self):
  132. """non-tab rank returns 404"""
  133. self.user.rank.is_tab = False
  134. self.user.rank.save()
  135. response = self.client.get(self.link % self.user.rank.pk)
  136. self.assertEqual(response.status_code, 404)
  137. def test_list_search(self):
  138. """rank list is not searchable"""
  139. api_link = self.link % self.user.rank.pk
  140. response = self.client.get('%s&name=%s' % (api_link, 'test'))
  141. self.assertEqual(response.status_code, 404)
  142. def test_filled_list(self):
  143. """tab rank with members return 200"""
  144. self.user.rank.is_tab = True
  145. self.user.rank.save()
  146. response = self.client.get(self.link % self.user.rank.pk)
  147. self.assertEqual(response.status_code, 200)
  148. self.assertContains(response, self.user.username)
  149. def test_disabled_users(self):
  150. """api follows disabled users visibility"""
  151. test_rank = Rank.objects.create(
  152. name="Test rank",
  153. slug="test-rank",
  154. is_tab=True
  155. )
  156. User = get_user_model()
  157. test_user = User.objects.create_user(
  158. 'Visible', 'visible@te.com', 'Pass.123',
  159. rank=test_rank, is_active=False
  160. )
  161. response = self.client.get(self.link % test_rank.pk)
  162. self.assertNotContains(response, test_user.get_absolute_url())
  163. # api shows disabled accounts to staff
  164. self.user.is_staff = True
  165. self.user.save()
  166. response = self.client.get(self.link % test_rank.pk)
  167. self.assertContains(response, test_user.get_absolute_url())
  168. class SearchNamesListTests(AuthenticatedUserTestCase):
  169. """
  170. tests for generic list (GET /users/) filtered by username disallowing searches
  171. """
  172. def setUp(self):
  173. super(SearchNamesListTests, self).setUp()
  174. self.link = '/api/users/?&name='
  175. def test_empty_list(self):
  176. """empty list returns 404"""
  177. response = self.client.get(self.link + 'this-user-is-fake')
  178. self.assertEqual(response.status_code, 404)
  179. def test_filled_list(self):
  180. """results list returns 404"""
  181. response = self.client.get(self.link + self.user.slug)
  182. self.assertEqual(response.status_code, 404)
  183. class UserRetrieveTests(AuthenticatedUserTestCase):
  184. def setUp(self):
  185. super(UserRetrieveTests, self).setUp()
  186. User = get_user_model()
  187. self.test_user = User.objects.create_user('Tyrael', 't123@test.com', 'pass123')
  188. self.link = reverse('misago:api:user-detail', kwargs={
  189. 'pk': self.test_user.pk
  190. })
  191. def test_get_user(self):
  192. """api user retrieve endpoint has no showstoppers"""
  193. response = self.client.get(self.link)
  194. self.assertEqual(response.status_code, 200)
  195. def test_disabled_user(self):
  196. """api user retrieve handles disabled users"""
  197. self.user.is_staff = False
  198. self.user.save()
  199. self.test_user.is_active = False
  200. self.test_user.save()
  201. response = self.client.get(self.link)
  202. self.assertEqual(response.status_code, 404)
  203. self.user.is_staff = True
  204. self.user.save()
  205. response = self.client.get(self.link)
  206. self.assertEqual(response.status_code, 200)
  207. class UserCategoriesOptionsTests(AuthenticatedUserTestCase):
  208. """
  209. tests for user forum options RPC (POST to /api/users/1/forum-options/)
  210. """
  211. def setUp(self):
  212. super(UserCategoriesOptionsTests, self).setUp()
  213. self.link = '/api/users/%s/forum-options/' % self.user.pk
  214. def test_empty_request(self):
  215. """empty request is handled"""
  216. response = self.client.post(self.link)
  217. self.assertEqual(response.status_code, 400)
  218. fields = (
  219. 'limits_private_thread_invites_to',
  220. 'subscribe_to_started_threads',
  221. 'subscribe_to_replied_threads'
  222. )
  223. for field in fields:
  224. self.assertContains(response, '"%s"' % field, status_code=400)
  225. def test_change_forum_options(self):
  226. """forum options are changed"""
  227. response = self.client.post(self.link, data={
  228. 'limits_private_thread_invites_to': 1,
  229. 'subscribe_to_started_threads': 2,
  230. 'subscribe_to_replied_threads': 1
  231. })
  232. self.assertEqual(response.status_code, 200)
  233. self.reload_user();
  234. self.assertFalse(self.user.is_hiding_presence)
  235. self.assertEqual(self.user.limits_private_thread_invites_to, 1)
  236. self.assertEqual(self.user.subscribe_to_started_threads, 2)
  237. self.assertEqual(self.user.subscribe_to_replied_threads, 1)
  238. response = self.client.post(self.link, data={
  239. 'is_hiding_presence': 'true',
  240. 'limits_private_thread_invites_to': 1,
  241. 'subscribe_to_started_threads': 2,
  242. 'subscribe_to_replied_threads': 1
  243. })
  244. self.assertEqual(response.status_code, 200)
  245. self.reload_user();
  246. self.assertTrue(self.user.is_hiding_presence)
  247. self.assertEqual(self.user.limits_private_thread_invites_to, 1)
  248. self.assertEqual(self.user.subscribe_to_started_threads, 2)
  249. self.assertEqual(self.user.subscribe_to_replied_threads, 1)
  250. response = self.client.post(self.link, data={
  251. 'is_hiding_presence': 'false',
  252. 'limits_private_thread_invites_to': 1,
  253. 'subscribe_to_started_threads': 2,
  254. 'subscribe_to_replied_threads': 1
  255. })
  256. self.assertEqual(response.status_code, 200)
  257. self.reload_user();
  258. self.assertFalse(self.user.is_hiding_presence)
  259. self.assertEqual(self.user.limits_private_thread_invites_to, 1)
  260. self.assertEqual(self.user.subscribe_to_started_threads, 2)
  261. self.assertEqual(self.user.subscribe_to_replied_threads, 1)
  262. class UserFollowTests(AuthenticatedUserTestCase):
  263. """
  264. tests for user follow RPC (POST to /api/users/1/follow/)
  265. """
  266. def setUp(self):
  267. super(UserFollowTests, self).setUp()
  268. User = get_user_model()
  269. self.other_user = User.objects.create_user(
  270. "OtherUser", "other@user.com", "pass123")
  271. self.link = '/api/users/%s/follow/' % self.other_user.pk
  272. def test_follow_unauthenticated(self):
  273. """you have to sign in to follow users"""
  274. self.logout_user()
  275. response = self.client.post(self.link)
  276. self.assertContains(response, "action is not available to guests", status_code=403)
  277. def test_follow_myself(self):
  278. """you can't follow yourself"""
  279. response = self.client.post('/api/users/%s/follow/' % self.user.pk)
  280. self.assertContains(response, "can't add yourself to followed", status_code=403)
  281. def test_cant_follow(self):
  282. """no permission to follow users"""
  283. override_acl(self.user, {
  284. 'can_follow_users': 0,
  285. })
  286. response = self.client.post(self.link)
  287. self.assertContains(response, "can't follow other users", status_code=403)
  288. def test_follow(self):
  289. """follow and unfollow other user"""
  290. response = self.client.post(self.link)
  291. self.assertEqual(response.status_code, 200)
  292. User = get_user_model()
  293. user = User.objects.get(pk=self.user.pk)
  294. self.assertEqual(user.followers, 0)
  295. self.assertEqual(user.following, 1)
  296. self.assertEqual(user.follows.count(), 1)
  297. self.assertEqual(user.followed_by.count(), 0)
  298. followed = User.objects.get(pk=self.other_user.pk)
  299. self.assertEqual(followed.followers, 1)
  300. self.assertEqual(followed.following, 0)
  301. self.assertEqual(followed.follows.count(), 0)
  302. self.assertEqual(followed.followed_by.count(), 1)
  303. response = self.client.post(self.link)
  304. self.assertEqual(response.status_code, 200)
  305. user = User.objects.get(pk=self.user.pk)
  306. self.assertEqual(user.followers, 0)
  307. self.assertEqual(user.following, 0)
  308. self.assertEqual(user.follows.count(), 0)
  309. self.assertEqual(user.followed_by.count(), 0)
  310. followed = User.objects.get(pk=self.other_user.pk)
  311. self.assertEqual(followed.followers, 0)
  312. self.assertEqual(followed.following, 0)
  313. self.assertEqual(followed.follows.count(), 0)
  314. self.assertEqual(followed.followed_by.count(), 0)
  315. class UserBanTests(AuthenticatedUserTestCase):
  316. """
  317. tests for ban endpoint (GET to /api/users/1/ban/)
  318. """
  319. def setUp(self):
  320. super(UserBanTests, self).setUp()
  321. User = get_user_model()
  322. self.other_user = User.objects.create_user(
  323. "OtherUser", "other@user.com", "pass123")
  324. self.link = '/api/users/%s/ban/' % self.other_user.pk
  325. def test_no_permission(self):
  326. """user has no permission to access ban"""
  327. override_acl(self.user, {
  328. 'can_see_ban_details': 0
  329. })
  330. response = self.client.get(self.link)
  331. self.assertContains(response, "can't see users bans details", status_code=403)
  332. def test_no_ban(self):
  333. """api returns empty json"""
  334. override_acl(self.user, {
  335. 'can_see_ban_details': 1
  336. })
  337. response = self.client.get(self.link)
  338. self.assertEqual(response.status_code, 200)
  339. self.assertEqual(smart_str(response.content), '{}')
  340. def test_ban_details(self):
  341. """api returns ban json"""
  342. override_acl(self.user, {
  343. 'can_see_ban_details': 1
  344. })
  345. Ban.objects.create(check_type=BAN_USERNAME,
  346. banned_value=self.other_user.username,
  347. user_message='Nope!')
  348. response = self.client.get(self.link)
  349. self.assertEqual(response.status_code, 200)
  350. ban_json = json.loads(smart_str(response.content))
  351. self.assertEqual(ban_json['user_message']['plain'], 'Nope!')
  352. self.assertEqual(ban_json['user_message']['html'], '<p>Nope!</p>')
  353. class UserDeleteTests(AuthenticatedUserTestCase):
  354. """
  355. tests for user delete RPC (POST to /api/users/1/delete/)
  356. """
  357. def setUp(self):
  358. super(UserDeleteTests, self).setUp()
  359. User = get_user_model()
  360. self.other_user = User.objects.create_user(
  361. "OtherUser", "other@user.com", "pass123")
  362. self.link = '/api/users/%s/delete/' % self.other_user.pk
  363. self.threads = Thread.objects.count()
  364. self.posts = Post.objects.count()
  365. self.category = Category.objects.all_categories()[:1][0]
  366. post_thread(self.category, poster=self.other_user)
  367. self.other_user.posts = 1
  368. self.other_user.threads = 1
  369. self.other_user.save()
  370. def test_delete_no_permission(self):
  371. """raises 403 error when no permission to delete"""
  372. override_acl(self.user, {
  373. 'can_delete_users_newer_than': 0,
  374. 'can_delete_users_with_less_posts_than': 0,
  375. })
  376. response = self.client.post(self.link)
  377. self.assertEqual(response.status_code, 403)
  378. self.assertContains(response, "can't delete users", status_code=403)
  379. def test_delete_too_many_posts(self):
  380. """raises 403 error when user has too many posts"""
  381. override_acl(self.user, {
  382. 'can_delete_users_newer_than': 0,
  383. 'can_delete_users_with_less_posts_than': 5,
  384. })
  385. self.other_user.posts = 6
  386. self.other_user.save()
  387. response = self.client.post(self.link)
  388. self.assertEqual(response.status_code, 403)
  389. self.assertContains(response, "can't delete users", status_code=403)
  390. def test_delete_too_many_posts(self):
  391. """raises 403 error when user has too many posts"""
  392. override_acl(self.user, {
  393. 'can_delete_users_newer_than': 0,
  394. 'can_delete_users_with_less_posts_than': 5,
  395. })
  396. self.other_user.posts = 6
  397. self.other_user.save()
  398. response = self.client.post(self.link)
  399. self.assertEqual(response.status_code, 403)
  400. self.assertContains(response, "can't delete users", status_code=403)
  401. self.assertContains(response, "made more than 5 posts", status_code=403)
  402. def test_delete_too_old_member(self):
  403. """raises 403 error when user is too old"""
  404. override_acl(self.user, {
  405. 'can_delete_users_newer_than': 5,
  406. 'can_delete_users_with_less_posts_than': 0,
  407. })
  408. self.other_user.joined_on -= timedelta(days=6)
  409. self.other_user.save()
  410. response = self.client.post(self.link)
  411. self.assertEqual(response.status_code, 403)
  412. self.assertContains(response, "can't delete users", status_code=403)
  413. self.assertContains(response, "members for more than 5 days", status_code=403)
  414. def test_delete_self(self):
  415. """raises 403 error when attempting to delete oneself"""
  416. override_acl(self.user, {
  417. 'can_delete_users_newer_than': 10,
  418. 'can_delete_users_with_less_posts_than': 10,
  419. })
  420. response = self.client.post('/api/users/%s/delete/' % self.user.pk)
  421. self.assertContains(response, "can't delete yourself", status_code=403)
  422. def test_delete_admin(self):
  423. """raises 403 error when attempting to delete admin"""
  424. override_acl(self.user, {
  425. 'can_delete_users_newer_than': 10,
  426. 'can_delete_users_with_less_posts_than': 10,
  427. })
  428. self.other_user.is_staff = True
  429. self.other_user.save()
  430. response = self.client.post(self.link)
  431. self.assertContains(response, "can't delete administrators", status_code=403)
  432. def test_delete_superadmin(self):
  433. """raises 403 error when attempting to delete superadmin"""
  434. override_acl(self.user, {
  435. 'can_delete_users_newer_than': 10,
  436. 'can_delete_users_with_less_posts_than': 10,
  437. })
  438. self.other_user.is_superuser = True
  439. self.other_user.save()
  440. response = self.client.post(self.link)
  441. self.assertContains(response, "can't delete administrators", status_code=403)
  442. def test_delete_with_content(self):
  443. """returns 200 and deletes user with content"""
  444. override_acl(self.user, {
  445. 'can_delete_users_newer_than': 10,
  446. 'can_delete_users_with_less_posts_than': 10,
  447. })
  448. response = self.client.post(self.link, json.dumps({
  449. 'with_content': True
  450. }), content_type="application/json")
  451. self.assertEqual(response.status_code, 200)
  452. User = get_user_model()
  453. with self.assertRaises(User.DoesNotExist):
  454. User.objects.get(pk=self.other_user.pk)
  455. self.assertEqual(Thread.objects.count(), self.threads)
  456. self.assertEqual(Post.objects.count(), self.posts)
  457. def test_delete_without_content(self):
  458. """returns 200 and deletes user without content"""
  459. override_acl(self.user, {
  460. 'can_delete_users_newer_than': 10,
  461. 'can_delete_users_with_less_posts_than': 10,
  462. })
  463. response = self.client.post(self.link, json.dumps({
  464. 'with_content': False
  465. }), content_type="application/json")
  466. self.assertEqual(response.status_code, 200)
  467. User = get_user_model()
  468. with self.assertRaises(User.DoesNotExist):
  469. User.objects.get(pk=self.other_user.pk)
  470. self.assertEqual(Thread.objects.count(), self.threads + 1)
  471. self.assertEqual(Post.objects.count(), self.posts + 2)