1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
6 use Pleroma.Web.ConnCase
10 alias Pleroma.Web.ActivityPub.ActivityPub
11 alias Pleroma.Web.ActivityPub.InternalFetchActor
12 alias Pleroma.Web.CommonAPI
13 alias Pleroma.Web.OAuth.Token
15 import Pleroma.Factory
17 describe "account fetching" do
23 |> get("/api/v1/accounts/#{user.id}")
25 assert %{"id" => id} = json_response(conn, 200)
26 assert id == to_string(user.id)
30 |> get("/api/v1/accounts/-1")
32 assert %{"error" => "Can't find user"} = json_response(conn, 404)
35 test "works by nickname" do
40 |> get("/api/v1/accounts/#{user.nickname}")
42 assert %{"id" => id} = json_response(conn, 200)
46 test "works by nickname for remote users" do
47 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
48 Pleroma.Config.put([:instance, :limit_to_local_content], false)
49 user = insert(:user, nickname: "user@example.com", local: false)
53 |> get("/api/v1/accounts/#{user.nickname}")
55 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
56 assert %{"id" => id} = json_response(conn, 200)
60 test "respects limit_to_local_content == :all for remote user nicknames" do
61 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
62 Pleroma.Config.put([:instance, :limit_to_local_content], :all)
64 user = insert(:user, nickname: "user@example.com", local: false)
68 |> get("/api/v1/accounts/#{user.nickname}")
70 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
71 assert json_response(conn, 404)
74 test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do
75 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
76 Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
78 user = insert(:user, nickname: "user@example.com", local: false)
79 reading_user = insert(:user)
83 |> get("/api/v1/accounts/#{user.nickname}")
85 assert json_response(conn, 404)
89 |> assign(:user, reading_user)
90 |> get("/api/v1/accounts/#{user.nickname}")
92 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
93 assert %{"id" => id} = json_response(conn, 200)
97 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
98 # Need to set an old-style integer ID to reproduce the problem
99 # (these are no longer assigned to new accounts but were preserved
100 # for existing accounts during the migration to flakeIDs)
101 user_one = insert(:user, %{id: 1212})
102 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
106 |> get("/api/v1/accounts/#{user_one.id}")
110 |> get("/api/v1/accounts/#{user_two.nickname}")
114 |> get("/api/v1/accounts/#{user_two.id}")
116 acc_one = json_response(resp_one, 200)
117 acc_two = json_response(resp_two, 200)
118 acc_three = json_response(resp_three, 200)
119 refute acc_one == acc_two
120 assert acc_two == acc_three
123 test "returns 404 when user is invisible", %{conn: conn} do
124 user = insert(:user, %{invisible: true})
128 |> get("/api/v1/accounts/#{user.nickname}")
129 |> json_response(404)
131 assert %{"error" => "Can't find user"} = resp
134 test "returns 404 for internal.fetch actor", %{conn: conn} do
135 %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor()
139 |> get("/api/v1/accounts/internal.fetch")
140 |> json_response(404)
142 assert %{"error" => "Can't find user"} = resp
146 describe "user timelines" do
147 test "gets a users statuses", %{conn: conn} do
148 user_one = insert(:user)
149 user_two = insert(:user)
150 user_three = insert(:user)
152 {:ok, user_three} = User.follow(user_three, user_one)
154 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
156 {:ok, direct_activity} =
157 CommonAPI.post(user_one, %{
158 "status" => "Hi, @#{user_two.nickname}.",
159 "visibility" => "direct"
162 {:ok, private_activity} =
163 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
167 |> get("/api/v1/accounts/#{user_one.id}/statuses")
169 assert [%{"id" => id}] = json_response(resp, 200)
170 assert id == to_string(activity.id)
174 |> assign(:user, user_two)
175 |> get("/api/v1/accounts/#{user_one.id}/statuses")
177 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
178 assert id_one == to_string(direct_activity.id)
179 assert id_two == to_string(activity.id)
183 |> assign(:user, user_three)
184 |> get("/api/v1/accounts/#{user_one.id}/statuses")
186 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
187 assert id_one == to_string(private_activity.id)
188 assert id_two == to_string(activity.id)
191 test "unimplemented pinned statuses feature", %{conn: conn} do
192 note = insert(:note_activity)
193 user = User.get_cached_by_ap_id(note.data["actor"])
197 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
199 assert json_response(conn, 200) == []
202 test "gets an users media", %{conn: conn} do
203 note = insert(:note_activity)
204 user = User.get_cached_by_ap_id(note.data["actor"])
207 content_type: "image/jpg",
208 path: Path.absname("test/fixtures/image.jpg"),
209 filename: "an_image.jpg"
212 {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id)
214 {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
218 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
220 assert [%{"id" => id}] = json_response(conn, 200)
221 assert id == to_string(image_post.id)
225 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
227 assert [%{"id" => id}] = json_response(conn, 200)
228 assert id == to_string(image_post.id)
231 test "gets a user's statuses without reblogs", %{conn: conn} do
233 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
234 {:ok, _, _} = CommonAPI.repeat(post.id, user)
238 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
240 assert [%{"id" => id}] = json_response(conn, 200)
241 assert id == to_string(post.id)
245 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
247 assert [%{"id" => id}] = json_response(conn, 200)
248 assert id == to_string(post.id)
251 test "filters user's statuses by a hashtag", %{conn: conn} do
253 {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
254 {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
258 |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
260 assert [%{"id" => id}] = json_response(conn, 200)
261 assert id == to_string(post.id)
264 test "the user views their own timelines and excludes direct messages", %{conn: conn} do
266 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
267 {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
271 |> assign(:user, user)
272 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_visibilities" => ["direct"]})
274 assert [%{"id" => id}] = json_response(conn, 200)
275 assert id == to_string(public_activity.id)
279 describe "followers" do
280 test "getting followers", %{conn: conn} do
282 other_user = insert(:user)
283 {:ok, user} = User.follow(user, other_user)
287 |> get("/api/v1/accounts/#{other_user.id}/followers")
289 assert [%{"id" => id}] = json_response(conn, 200)
290 assert id == to_string(user.id)
293 test "getting followers, hide_followers", %{conn: conn} do
295 other_user = insert(:user, hide_followers: true)
296 {:ok, _user} = User.follow(user, other_user)
300 |> get("/api/v1/accounts/#{other_user.id}/followers")
302 assert [] == json_response(conn, 200)
305 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
307 other_user = insert(:user, hide_followers: true)
308 {:ok, _user} = User.follow(user, other_user)
312 |> assign(:user, other_user)
313 |> get("/api/v1/accounts/#{other_user.id}/followers")
315 refute [] == json_response(conn, 200)
318 test "getting followers, pagination", %{conn: conn} do
320 follower1 = insert(:user)
321 follower2 = insert(:user)
322 follower3 = insert(:user)
323 {:ok, _} = User.follow(follower1, user)
324 {:ok, _} = User.follow(follower2, user)
325 {:ok, _} = User.follow(follower3, user)
329 |> assign(:user, user)
333 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
335 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
336 assert id3 == follower3.id
337 assert id2 == follower2.id
341 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
343 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
344 assert id2 == follower2.id
345 assert id1 == follower1.id
349 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
351 assert [%{"id" => id2}] = json_response(res_conn, 200)
352 assert id2 == follower2.id
354 assert [link_header] = get_resp_header(res_conn, "link")
355 assert link_header =~ ~r/min_id=#{follower2.id}/
356 assert link_header =~ ~r/max_id=#{follower2.id}/
360 describe "following" do
361 test "getting following", %{conn: conn} do
363 other_user = insert(:user)
364 {:ok, user} = User.follow(user, other_user)
368 |> get("/api/v1/accounts/#{user.id}/following")
370 assert [%{"id" => id}] = json_response(conn, 200)
371 assert id == to_string(other_user.id)
374 test "getting following, hide_follows", %{conn: conn} do
375 user = insert(:user, hide_follows: true)
376 other_user = insert(:user)
377 {:ok, user} = User.follow(user, other_user)
381 |> get("/api/v1/accounts/#{user.id}/following")
383 assert [] == json_response(conn, 200)
386 test "getting following, hide_follows, same user requesting", %{conn: conn} do
387 user = insert(:user, hide_follows: true)
388 other_user = insert(:user)
389 {:ok, user} = User.follow(user, other_user)
393 |> assign(:user, user)
394 |> get("/api/v1/accounts/#{user.id}/following")
396 refute [] == json_response(conn, 200)
399 test "getting following, pagination", %{conn: conn} do
401 following1 = insert(:user)
402 following2 = insert(:user)
403 following3 = insert(:user)
404 {:ok, _} = User.follow(user, following1)
405 {:ok, _} = User.follow(user, following2)
406 {:ok, _} = User.follow(user, following3)
410 |> assign(:user, user)
414 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
416 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
417 assert id3 == following3.id
418 assert id2 == following2.id
422 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
424 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
425 assert id2 == following2.id
426 assert id1 == following1.id
430 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
432 assert [%{"id" => id2}] = json_response(res_conn, 200)
433 assert id2 == following2.id
435 assert [link_header] = get_resp_header(res_conn, "link")
436 assert link_header =~ ~r/min_id=#{following2.id}/
437 assert link_header =~ ~r/max_id=#{following2.id}/
441 describe "follow/unfollow" do
442 test "following / unfollowing a user", %{conn: conn} do
444 other_user = insert(:user)
448 |> assign(:user, user)
449 |> post("/api/v1/accounts/#{other_user.id}/follow")
451 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
453 user = User.get_cached_by_id(user.id)
457 |> assign(:user, user)
458 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
460 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
462 user = User.get_cached_by_id(user.id)
466 |> assign(:user, user)
467 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
469 assert %{"id" => id} = json_response(conn, 200)
470 assert id == to_string(other_user.id)
473 test "following without reblogs" do
474 follower = insert(:user)
475 followed = insert(:user)
476 other_user = insert(:user)
480 |> assign(:user, follower)
481 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
483 assert %{"showing_reblogs" => false} = json_response(conn, 200)
485 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
486 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
490 |> assign(:user, User.get_cached_by_id(follower.id))
491 |> get("/api/v1/timelines/home")
493 assert [] == json_response(conn, 200)
497 |> assign(:user, User.get_cached_by_id(follower.id))
498 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
500 assert %{"showing_reblogs" => true} = json_response(conn, 200)
504 |> assign(:user, User.get_cached_by_id(follower.id))
505 |> get("/api/v1/timelines/home")
507 expected_activity_id = reblog.id
508 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
511 test "following / unfollowing errors" do
516 |> assign(:user, user)
519 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
520 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
523 user = User.get_cached_by_id(user.id)
524 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
525 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
527 # self follow via uri
528 user = User.get_cached_by_id(user.id)
529 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
530 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
532 # follow non existing user
533 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
534 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
536 # follow non existing user via uri
537 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
538 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
540 # unfollow non existing user
541 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
542 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
546 describe "mute/unmute" do
547 test "with notifications", %{conn: conn} do
549 other_user = insert(:user)
553 |> assign(:user, user)
554 |> post("/api/v1/accounts/#{other_user.id}/mute")
556 response = json_response(conn, 200)
558 assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
559 user = User.get_cached_by_id(user.id)
563 |> assign(:user, user)
564 |> post("/api/v1/accounts/#{other_user.id}/unmute")
566 response = json_response(conn, 200)
567 assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
570 test "without notifications", %{conn: conn} do
572 other_user = insert(:user)
576 |> assign(:user, user)
577 |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
579 response = json_response(conn, 200)
581 assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
582 user = User.get_cached_by_id(user.id)
586 |> assign(:user, user)
587 |> post("/api/v1/accounts/#{other_user.id}/unmute")
589 response = json_response(conn, 200)
590 assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
594 describe "pinned statuses" do
597 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
599 [user: user, activity: activity]
602 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
603 {:ok, _} = CommonAPI.pin(activity.id, user)
607 |> assign(:user, user)
608 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
609 |> json_response(200)
611 id_str = to_string(activity.id)
613 assert [%{"id" => ^id_str, "pinned" => true}] = result
617 test "blocking / unblocking a user", %{conn: conn} do
619 other_user = insert(:user)
623 |> assign(:user, user)
624 |> post("/api/v1/accounts/#{other_user.id}/block")
626 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
628 user = User.get_cached_by_id(user.id)
632 |> assign(:user, user)
633 |> post("/api/v1/accounts/#{other_user.id}/unblock")
635 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
638 describe "create account by app" do
642 email: "lain@example.org",
643 password: "PlzDontHackLain",
647 [valid_params: valid_params]
650 test "Account registration via Application", %{conn: conn} do
653 |> post("/api/v1/apps", %{
654 client_name: "client_name",
655 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
656 scopes: "read, write, follow"
660 "client_id" => client_id,
661 "client_secret" => client_secret,
663 "name" => "client_name",
664 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
667 } = json_response(conn, 200)
671 |> post("/oauth/token", %{
672 grant_type: "client_credentials",
673 client_id: client_id,
674 client_secret: client_secret
677 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
678 json_response(conn, 200)
681 token_from_db = Repo.get_by(Token, token: token)
684 assert scope == "read write follow"
688 |> put_req_header("authorization", "Bearer " <> token)
689 |> post("/api/v1/accounts", %{
691 email: "lain@example.org",
692 password: "PlzDontHackLain",
698 "access_token" => token,
699 "created_at" => _created_at,
701 "token_type" => "Bearer"
702 } = json_response(conn, 200)
704 token_from_db = Repo.get_by(Token, token: token)
706 token_from_db = Repo.preload(token_from_db, :user)
707 assert token_from_db.user
709 assert token_from_db.user.confirmation_pending
712 test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
713 _user = insert(:user, email: "lain@example.org")
714 app_token = insert(:oauth_token, user: nil)
718 |> put_req_header("authorization", "Bearer " <> app_token.token)
720 res = post(conn, "/api/v1/accounts", valid_params)
721 assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"}
724 test "rate limit", %{conn: conn} do
725 app_token = insert(:oauth_token, user: nil)
728 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
729 |> Map.put(:remote_ip, {15, 15, 15, 15})
734 |> post("/api/v1/accounts", %{
735 username: "#{i}lain",
736 email: "#{i}lain@example.org",
737 password: "PlzDontHackLain",
742 "access_token" => token,
743 "created_at" => _created_at,
745 "token_type" => "Bearer"
746 } = json_response(conn, 200)
748 token_from_db = Repo.get_by(Token, token: token)
750 token_from_db = Repo.preload(token_from_db, :user)
751 assert token_from_db.user
753 assert token_from_db.user.confirmation_pending
758 |> post("/api/v1/accounts", %{
760 email: "6lain@example.org",
761 password: "PlzDontHackLain",
765 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
768 test "returns bad_request if missing required params", %{
770 valid_params: valid_params
772 app_token = insert(:oauth_token, user: nil)
776 |> put_req_header("authorization", "Bearer " <> app_token.token)
778 res = post(conn, "/api/v1/accounts", valid_params)
779 assert json_response(res, 200)
781 [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
782 |> Stream.zip(valid_params)
783 |> Enum.each(fn {ip, {attr, _}} ->
786 |> Map.put(:remote_ip, ip)
787 |> post("/api/v1/accounts", Map.delete(valid_params, attr))
788 |> json_response(400)
790 assert res == %{"error" => "Missing parameters"}
794 test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
797 |> put_req_header("authorization", "Bearer " <> "invalid-token")
799 res = post(conn, "/api/v1/accounts", valid_params)
800 assert json_response(res, 403) == %{"error" => "Invalid credentials"}
804 describe "GET /api/v1/accounts/:id/lists - account_lists" do
805 test "returns lists to which the account belongs", %{conn: conn} do
807 other_user = insert(:user)
808 assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user)
809 {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user)
813 |> assign(:user, user)
814 |> get("/api/v1/accounts/#{other_user.id}/lists")
815 |> json_response(200)
817 assert res == [%{"id" => to_string(list.id), "title" => "Test List"}]
821 describe "verify_credentials" do
822 test "verify_credentials", %{conn: conn} do
827 |> assign(:user, user)
828 |> get("/api/v1/accounts/verify_credentials")
830 response = json_response(conn, 200)
832 assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
833 assert response["pleroma"]["chat_token"]
834 assert id == to_string(user.id)
837 test "verify_credentials default scope unlisted", %{conn: conn} do
838 user = insert(:user, default_scope: "unlisted")
842 |> assign(:user, user)
843 |> get("/api/v1/accounts/verify_credentials")
845 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
846 assert id == to_string(user.id)
849 test "locked accounts", %{conn: conn} do
850 user = insert(:user, default_scope: "private")
854 |> assign(:user, user)
855 |> get("/api/v1/accounts/verify_credentials")
857 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
858 assert id == to_string(user.id)
862 describe "user relationships" do
863 test "returns the relationships for the current user", %{conn: conn} do
865 other_user = insert(:user)
866 {:ok, user} = User.follow(user, other_user)
870 |> assign(:user, user)
871 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
873 assert [relationship] = json_response(conn, 200)
875 assert to_string(other_user.id) == relationship["id"]
878 test "returns an empty list on a bad request", %{conn: conn} do
883 |> assign(:user, user)
884 |> get("/api/v1/accounts/relationships", %{})
886 assert [] = json_response(conn, 200)
890 test "getting a list of mutes", %{conn: conn} do
892 other_user = insert(:user)
894 {:ok, _user_relationships} = User.mute(user, other_user)
898 |> assign(:user, user)
899 |> get("/api/v1/mutes")
901 other_user_id = to_string(other_user.id)
902 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
905 test "getting a list of blocks", %{conn: conn} do
907 other_user = insert(:user)
909 {:ok, _user_relationship} = User.block(user, other_user)
913 |> assign(:user, user)
914 |> get("/api/v1/blocks")
916 other_user_id = to_string(other_user.id)
917 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)