# Pleroma: A lightweight social networking server # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Config alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ApiSpec alias Pleroma.Web.CommonAPI alias Pleroma.Web.OAuth.Token import OpenApiSpex.TestAssertions import Pleroma.Factory describe "account fetching" do setup do: clear_config([:instance, :limit_to_local_content]) test "works by id" do user = insert(:user) conn = build_conn() |> get("/api/v1/accounts/#{user.id}") assert %{"id" => id} = json_response(conn, 200) assert id == to_string(user.id) conn = build_conn() |> get("/api/v1/accounts/-1") assert %{"error" => "Can't find user"} = json_response(conn, 404) end test "works by nickname" do user = insert(:user) conn = build_conn() |> get("/api/v1/accounts/#{user.nickname}") assert %{"id" => id} = json_response(conn, 200) assert id == user.id end test "works by nickname for remote users" do Config.put([:instance, :limit_to_local_content], false) user = insert(:user, nickname: "user@example.com", local: false) conn = build_conn() |> get("/api/v1/accounts/#{user.nickname}") assert %{"id" => id} = json_response(conn, 200) assert id == user.id end test "respects limit_to_local_content == :all for remote user nicknames" do Config.put([:instance, :limit_to_local_content], :all) user = insert(:user, nickname: "user@example.com", local: false) conn = build_conn() |> get("/api/v1/accounts/#{user.nickname}") assert json_response(conn, 404) end test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do Config.put([:instance, :limit_to_local_content], :unauthenticated) user = insert(:user, nickname: "user@example.com", local: false) reading_user = insert(:user) conn = build_conn() |> get("/api/v1/accounts/#{user.nickname}") assert json_response(conn, 404) conn = build_conn() |> assign(:user, reading_user) |> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"])) |> get("/api/v1/accounts/#{user.nickname}") assert %{"id" => id} = json_response(conn, 200) assert id == user.id end test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do # Need to set an old-style integer ID to reproduce the problem # (these are no longer assigned to new accounts but were preserved # for existing accounts during the migration to flakeIDs) user_one = insert(:user, %{id: 1212}) user_two = insert(:user, %{nickname: "#{user_one.id}garbage"}) resp_one = conn |> get("/api/v1/accounts/#{user_one.id}") resp_two = conn |> get("/api/v1/accounts/#{user_two.nickname}") resp_three = conn |> get("/api/v1/accounts/#{user_two.id}") acc_one = json_response(resp_one, 200) acc_two = json_response(resp_two, 200) acc_three = json_response(resp_three, 200) refute acc_one == acc_two assert acc_two == acc_three end test "returns 404 when user is invisible", %{conn: conn} do user = insert(:user, %{invisible: true}) resp = conn |> get("/api/v1/accounts/#{user.nickname}") |> json_response(404) assert %{"error" => "Can't find user"} = resp end test "returns 404 for internal.fetch actor", %{conn: conn} do %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor() resp = conn |> get("/api/v1/accounts/internal.fetch") |> json_response(404) assert %{"error" => "Can't find user"} = resp end end defp local_and_remote_users do local = insert(:user) remote = insert(:user, local: false) {:ok, local: local, remote: remote} end describe "user fetching with restrict unauthenticated profiles for local and remote" do setup do: local_and_remote_users() setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true) setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do res_conn = get(conn, "/api/v1/accounts/#{local.id}") assert json_response(res_conn, :not_found) == %{ "error" => "Can't find user" } res_conn = get(conn, "/api/v1/accounts/#{remote.id}") assert json_response(res_conn, :not_found) == %{ "error" => "Can't find user" } end test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/accounts/#{local.id}") assert %{"id" => _} = json_response(res_conn, 200) res_conn = get(conn, "/api/v1/accounts/#{remote.id}") assert %{"id" => _} = json_response(res_conn, 200) end end describe "user fetching with restrict unauthenticated profiles for local" do setup do: local_and_remote_users() setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do res_conn = get(conn, "/api/v1/accounts/#{local.id}") assert json_response(res_conn, :not_found) == %{ "error" => "Can't find user" } res_conn = get(conn, "/api/v1/accounts/#{remote.id}") assert %{"id" => _} = json_response(res_conn, 200) end test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/accounts/#{local.id}") assert %{"id" => _} = json_response(res_conn, 200) res_conn = get(conn, "/api/v1/accounts/#{remote.id}") assert %{"id" => _} = json_response(res_conn, 200) end end describe "user fetching with restrict unauthenticated profiles for remote" do setup do: local_and_remote_users() setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do res_conn = get(conn, "/api/v1/accounts/#{local.id}") assert %{"id" => _} = json_response(res_conn, 200) res_conn = get(conn, "/api/v1/accounts/#{remote.id}") assert json_response(res_conn, :not_found) == %{ "error" => "Can't find user" } end test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/accounts/#{local.id}") assert %{"id" => _} = json_response(res_conn, 200) res_conn = get(conn, "/api/v1/accounts/#{remote.id}") assert %{"id" => _} = json_response(res_conn, 200) end end describe "user timelines" do setup do: oauth_access(["read:statuses"]) test "respects blocks", %{user: user_one, conn: conn} do user_two = insert(:user) user_three = insert(:user) User.block(user_one, user_two) {:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"}) {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three) assert resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") |> json_response(200) assert [%{"id" => id}] = resp assert_schema(resp, "StatusesResponse", ApiSpec.spec()) assert id == activity.id # Even a blocked user will deliver the full user timeline, there would be # no point in looking at a blocked users timeline otherwise assert resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") |> json_response(200) assert [%{"id" => id}] = resp assert id == activity.id assert_schema(resp, "StatusesResponse", ApiSpec.spec()) # Third user's timeline includes the repeat when viewed by unauthenticated user resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses") |> json_response(200) assert [%{"id" => id}] = resp assert id == repeat.id assert_schema(resp, "StatusesResponse", ApiSpec.spec()) # When viewing a third user's timeline, the blocked users' statuses will NOT be shown resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses") assert [] = json_response(resp, 200) end test "gets users statuses", %{conn: conn} do user_one = insert(:user) user_two = insert(:user) user_three = insert(:user) {:ok, _user_three} = User.follow(user_three, user_one) {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"}) {:ok, direct_activity} = CommonAPI.post(user_one, %{ "status" => "Hi, @#{user_two.nickname}.", "visibility" => "direct" }) {:ok, private_activity} = CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"}) resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses") |> json_response(200) assert [%{"id" => id}] = resp assert id == to_string(activity.id) assert_schema(resp, "StatusesResponse", ApiSpec.spec()) resp = conn |> assign(:user, user_two) |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"])) |> get("/api/v1/accounts/#{user_one.id}/statuses") |> json_response(200) assert [%{"id" => id_one}, %{"id" => id_two}] = resp assert id_one == to_string(direct_activity.id) assert id_two == to_string(activity.id) assert_schema(resp, "StatusesResponse", ApiSpec.spec()) resp = conn |> assign(:user, user_three) |> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"])) |> get("/api/v1/accounts/#{user_one.id}/statuses") |> json_response(200) assert [%{"id" => id_one}, %{"id" => id_two}] = resp assert id_one == to_string(private_activity.id) assert id_two == to_string(activity.id) assert_schema(resp, "StatusesResponse", ApiSpec.spec()) end test "unimplemented pinned statuses feature", %{conn: conn} do note = insert(:note_activity) user = User.get_cached_by_ap_id(note.data["actor"]) conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?pinned=true") assert json_response(conn, 200) == [] end test "gets an users media", %{conn: conn} do note = insert(:note_activity) user = User.get_cached_by_ap_id(note.data["actor"]) file = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id) {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]}) conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(image_post.id) assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec()) conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses?only_media=1") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(image_post.id) assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec()) end test "gets a user's statuses without reblogs", %{user: user, conn: conn} do {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"}) {:ok, _, _} = CommonAPI.repeat(post.id, user) conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=true") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(post.id) assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec()) conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=1") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(post.id) assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec()) end test "filters user's statuses by a hashtag", %{user: user, conn: conn} do {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"}) {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"}) conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?tagged=hashtag") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(post.id) assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec()) end test "the user views their own timelines and excludes direct messages", %{ user: user, conn: conn } do {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_visibilities[]=direct") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(public_activity.id) assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec()) end end defp local_and_remote_activities(%{local: local, remote: remote}) do insert(:note_activity, user: local) insert(:note_activity, user: remote, local: false) :ok end describe "statuses with restrict unauthenticated profiles for local and remote" do setup do: local_and_remote_users() setup :local_and_remote_activities setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true) setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") assert json_response(res_conn, :not_found) == %{ "error" => "Can't find user" } res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") assert json_response(res_conn, :not_found) == %{ "error" => "Can't find user" } end test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") assert length(json_response(res_conn, 200)) == 1 assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec()) res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") assert length(json_response(res_conn, 200)) == 1 assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec()) end end describe "statuses with restrict unauthenticated profiles for local" do setup do: local_and_remote_users() setup :local_and_remote_activities setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") assert json_response(res_conn, :not_found) == %{ "error" => "Can't find user" } res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") assert length(json_response(res_conn, 200)) == 1 assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec()) end test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") assert length(json_response(res_conn, 200)) == 1 assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec()) res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") assert length(json_response(res_conn, 200)) == 1 assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec()) end end describe "statuses with restrict unauthenticated profiles for remote" do setup do: local_and_remote_users() setup :local_and_remote_activities setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") assert length(json_response(res_conn, 200)) == 1 assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec()) res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") assert json_response(res_conn, :not_found) == %{ "error" => "Can't find user" } end test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") assert length(json_response(res_conn, 200)) == 1 assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec()) res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") assert length(json_response(res_conn, 200)) == 1 assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec()) end end describe "followers" do setup do: oauth_access(["read:accounts"]) test "getting followers", %{user: user, conn: conn} do other_user = insert(:user) {:ok, user} = User.follow(user, other_user) conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(user.id) assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec()) end test "getting followers, hide_followers", %{user: user, conn: conn} do other_user = insert(:user, hide_followers: true) {:ok, _user} = User.follow(user, other_user) conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") assert [] == json_response(conn, 200) end test "getting followers, hide_followers, same user requesting" do user = insert(:user) other_user = insert(:user, hide_followers: true) {:ok, _user} = User.follow(user, other_user) conn = build_conn() |> assign(:user, other_user) |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) |> get("/api/v1/accounts/#{other_user.id}/followers") refute [] == json_response(conn, 200) assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec()) end test "getting followers, pagination", %{user: user, conn: conn} do follower1 = insert(:user) follower2 = insert(:user) follower3 = insert(:user) {:ok, _} = User.follow(follower1, user) {:ok, _} = User.follow(follower2, user) {:ok, _} = User.follow(follower3, user) res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) assert id3 == follower3.id assert id2 == follower2.id assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec()) res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) assert id2 == follower2.id assert id1 == follower1.id res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") assert [%{"id" => id2}] = json_response(res_conn, 200) assert id2 == follower2.id assert [link_header] = get_resp_header(res_conn, "link") assert link_header =~ ~r/min_id=#{follower2.id}/ assert link_header =~ ~r/max_id=#{follower2.id}/ assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec()) end end describe "following" do setup do: oauth_access(["read:accounts"]) test "getting following", %{user: user, conn: conn} do other_user = insert(:user) {:ok, user} = User.follow(user, other_user) conn = get(conn, "/api/v1/accounts/#{user.id}/following") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(other_user.id) assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec()) end test "getting following, hide_follows, other user requesting" do user = insert(:user, hide_follows: true) other_user = insert(:user) {:ok, user} = User.follow(user, other_user) conn = build_conn() |> assign(:user, other_user) |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) |> get("/api/v1/accounts/#{user.id}/following") assert [] == json_response(conn, 200) assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec()) end test "getting following, hide_follows, same user requesting" do user = insert(:user, hide_follows: true) other_user = insert(:user) {:ok, user} = User.follow(user, other_user) conn = build_conn() |> assign(:user, user) |> assign(:token, insert(:oauth_token, user: user, scopes: ["read:accounts"])) |> get("/api/v1/accounts/#{user.id}/following") refute [] == json_response(conn, 200) end test "getting following, pagination", %{user: user, conn: conn} do following1 = insert(:user) following2 = insert(:user) following3 = insert(:user) {:ok, _} = User.follow(user, following1) {:ok, _} = User.follow(user, following2) {:ok, _} = User.follow(user, following3) res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) assert id3 == following3.id assert id2 == following2.id assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec()) res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) assert id2 == following2.id assert id1 == following1.id assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec()) res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") assert [%{"id" => id2}] = json_response(res_conn, 200) assert id2 == following2.id assert [link_header] = get_resp_header(res_conn, "link") assert link_header =~ ~r/min_id=#{following2.id}/ assert link_header =~ ~r/max_id=#{following2.id}/ assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec()) end end describe "follow/unfollow" do setup do: oauth_access(["follow"]) test "following / unfollowing a user", %{conn: conn} do other_user = insert(:user) ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/follow") assert %{"id" => _id, "following" => true} = json_response(ret_conn, 200) assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec()) ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/unfollow") assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200) assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec()) conn = conn |> put_req_header("content-type", "application/json") |> post("/api/v1/follows", %{"uri" => other_user.nickname}) assert %{"id" => id} = json_response(conn, 200) assert id == to_string(other_user.id) assert_schema(json_response(conn, 200), "Account", ApiSpec.spec()) end test "cancelling follow request", %{conn: conn} do %{id: other_user_id} = insert(:user, %{locked: true}) resp = conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok) assert %{"id" => ^other_user_id, "following" => false, "requested" => true} = resp assert_schema(resp, "AccountRelationship", ApiSpec.spec()) resp = conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok) assert %{"id" => ^other_user_id, "following" => false, "requested" => false} = resp assert_schema(resp, "AccountRelationship", ApiSpec.spec()) end test "following without reblogs" do %{conn: conn} = oauth_access(["follow", "read:statuses"]) followed = insert(:user) other_user = insert(:user) ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false") assert %{"showing_reblogs" => false} = json_response(ret_conn, 200) assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec()) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) ret_conn = get(conn, "/api/v1/timelines/home") assert [] == json_response(ret_conn, 200) ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=true") assert %{"showing_reblogs" => true} = json_response(ret_conn, 200) assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec()) conn = get(conn, "/api/v1/timelines/home") expected_activity_id = reblog.id assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) end test "following / unfollowing errors", %{user: user, conn: conn} do # self follow conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") assert %{"error" => "Record not found"} = json_response(conn_res, 404) # self unfollow user = User.get_cached_by_id(user.id) conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow") assert %{"error" => "Record not found"} = json_response(conn_res, 404) # self follow via uri user = User.get_cached_by_id(user.id) conn_res = conn |> put_req_header("content-type", "multipart/form-data") |> post("/api/v1/follows", %{"uri" => user.nickname}) assert %{"error" => "Record not found"} = json_response(conn_res, 404) # follow non existing user conn_res = post(conn, "/api/v1/accounts/doesntexist/follow") assert %{"error" => "Record not found"} = json_response(conn_res, 404) # follow non existing user via uri conn_res = conn |> put_req_header("content-type", "multipart/form-data") |> post("/api/v1/follows", %{"uri" => "doesntexist"}) assert %{"error" => "Record not found"} = json_response(conn_res, 404) # unfollow non existing user conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow") assert %{"error" => "Record not found"} = json_response(conn_res, 404) end end describe "mute/unmute" do setup do: oauth_access(["write:mutes"]) test "with notifications", %{conn: conn} do other_user = insert(:user) ret_conn = conn |> put_req_header("content-type", "application/json") |> post("/api/v1/accounts/#{other_user.id}/mute") response = json_response(ret_conn, 200) assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response assert_schema(response, "AccountRelationship", ApiSpec.spec()) conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute") response = json_response(conn, 200) assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response assert_schema(response, "AccountRelationship", ApiSpec.spec()) end test "without notifications", %{conn: conn} do other_user = insert(:user) ret_conn = conn |> put_req_header("content-type", "multipart/form-data") |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) response = json_response(ret_conn, 200) assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response assert_schema(response, "AccountRelationship", ApiSpec.spec()) conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute") response = json_response(conn, 200) assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response assert_schema(response, "AccountRelationship", ApiSpec.spec()) end end describe "pinned statuses" do setup do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) %{conn: conn} = oauth_access(["read:statuses"], user: user) [conn: conn, user: user, activity: activity] end test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do {:ok, _} = CommonAPI.pin(activity.id, user) result = conn |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") |> json_response(200) id_str = to_string(activity.id) assert [%{"id" => ^id_str, "pinned" => true}] = result end end test "blocking / unblocking a user" do %{conn: conn} = oauth_access(["follow"]) other_user = insert(:user) ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block") assert %{"id" => _id, "blocking" => true} = json_response(ret_conn, 200) assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec()) conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock") assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec()) end describe "create account by app" do setup do valid_params = %{ username: "lain", email: "lain@example.org", password: "PlzDontHackLain", agreement: true } [valid_params: valid_params] end setup do: clear_config([:instance, :account_activation_required]) test "Account registration via Application", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") |> post("/api/v1/apps", %{ client_name: "client_name", redirect_uris: "urn:ietf:wg:oauth:2.0:oob", scopes: "read, write, follow" }) %{ "client_id" => client_id, "client_secret" => client_secret, "id" => _, "name" => "client_name", "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", "vapid_key" => _, "website" => nil } = json_response(conn, 200) conn = post(conn, "/oauth/token", %{ grant_type: "client_credentials", client_id: client_id, client_secret: client_secret }) assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = json_response(conn, 200) assert token token_from_db = Repo.get_by(Token, token: token) assert token_from_db assert refresh assert scope == "read write follow" conn = build_conn() |> put_req_header("content-type", "multipart/form-data") |> put_req_header("authorization", "Bearer " <> token) |> post("/api/v1/accounts", %{ username: "lain", email: "lain@example.org", password: "PlzDontHackLain", bio: "Test Bio", agreement: true }) %{ "access_token" => token, "created_at" => _created_at, "scope" => _scope, "token_type" => "Bearer" } = json_response(conn, 200) token_from_db = Repo.get_by(Token, token: token) assert token_from_db token_from_db = Repo.preload(token_from_db, :user) assert token_from_db.user assert token_from_db.user.confirmation_pending end test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do _user = insert(:user, email: "lain@example.org") app_token = insert(:oauth_token, user: nil) res = conn |> put_req_header("authorization", "Bearer " <> app_token.token) |> put_req_header("content-type", "application/json") |> post("/api/v1/accounts", valid_params) assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"} end test "returns bad_request if missing required params", %{ conn: conn, valid_params: valid_params } do app_token = insert(:oauth_token, user: nil) conn = conn |> put_req_header("authorization", "Bearer " <> app_token.token) |> put_req_header("content-type", "application/json") res = post(conn, "/api/v1/accounts", valid_params) assert json_response(res, 200) [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}] |> Stream.zip(Map.delete(valid_params, :email)) |> Enum.each(fn {ip, {attr, _}} -> res = conn |> Map.put(:remote_ip, ip) |> post("/api/v1/accounts", Map.delete(valid_params, attr)) |> json_response(400) assert res == %{"error" => "Missing parameters"} end) end setup do: clear_config([:instance, :account_activation_required]) test "returns bad_request if missing email params when :account_activation_required is enabled", %{conn: conn, valid_params: valid_params} do Pleroma.Config.put([:instance, :account_activation_required], true) app_token = insert(:oauth_token, user: nil) conn = conn |> put_req_header("authorization", "Bearer " <> app_token.token) |> put_req_header("content-type", "application/json") res = conn |> Map.put(:remote_ip, {127, 0, 0, 5}) |> post("/api/v1/accounts", Map.delete(valid_params, :email)) assert json_response(res, 400) == %{"error" => "Missing parameters"} res = conn |> Map.put(:remote_ip, {127, 0, 0, 6}) |> post("/api/v1/accounts", Map.put(valid_params, :email, "")) assert json_response(res, 400) == %{"error" => "{\"email\":[\"can't be blank\"]}"} end test "allow registration without an email", %{conn: conn, valid_params: valid_params} do app_token = insert(:oauth_token, user: nil) conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token) res = conn |> put_req_header("content-type", "application/json") |> Map.put(:remote_ip, {127, 0, 0, 7}) |> post("/api/v1/accounts", Map.delete(valid_params, :email)) assert json_response(res, 200) end test "allow registration with an empty email", %{conn: conn, valid_params: valid_params} do app_token = insert(:oauth_token, user: nil) conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token) res = conn |> put_req_header("content-type", "application/json") |> Map.put(:remote_ip, {127, 0, 0, 8}) |> post("/api/v1/accounts", Map.put(valid_params, :email, "")) assert json_response(res, 200) end test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do res = conn |> put_req_header("authorization", "Bearer " <> "invalid-token") |> put_req_header("content-type", "multipart/form-data") |> post("/api/v1/accounts", valid_params) assert json_response(res, 403) == %{"error" => "Invalid credentials"} end end describe "create account by app / rate limit" do setup do: clear_config([:rate_limit, :app_account_creation], {10_000, 2}) test "respects rate limit setting", %{conn: conn} do app_token = insert(:oauth_token, user: nil) conn = conn |> put_req_header("authorization", "Bearer " <> app_token.token) |> Map.put(:remote_ip, {15, 15, 15, 15}) |> put_req_header("content-type", "multipart/form-data") for i <- 1..2 do conn = conn |> post("/api/v1/accounts", %{ username: "#{i}lain", email: "#{i}lain@example.org", password: "PlzDontHackLain", agreement: true }) %{ "access_token" => token, "created_at" => _created_at, "scope" => _scope, "token_type" => "Bearer" } = json_response(conn, 200) token_from_db = Repo.get_by(Token, token: token) assert token_from_db token_from_db = Repo.preload(token_from_db, :user) assert token_from_db.user assert token_from_db.user.confirmation_pending end conn = post(conn, "/api/v1/accounts", %{ username: "6lain", email: "6lain@example.org", password: "PlzDontHackLain", agreement: true }) assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"} end end describe "GET /api/v1/accounts/:id/lists - account_lists" do test "returns lists to which the account belongs" do %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user) {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) res = conn |> get("/api/v1/accounts/#{other_user.id}/lists") |> json_response(200) assert res == [%{"id" => to_string(list.id), "title" => "Test List"}] assert_schema(res, "ListsResponse", ApiSpec.spec()) end end describe "verify_credentials" do test "verify_credentials" do %{user: user, conn: conn} = oauth_access(["read:accounts"]) conn = get(conn, "/api/v1/accounts/verify_credentials") response = json_response(conn, 200) assert %{"id" => id, "source" => %{"privacy" => "public"}} = response assert response["pleroma"]["chat_token"] assert id == to_string(user.id) end test "verify_credentials default scope unlisted" do user = insert(:user, default_scope: "unlisted") %{conn: conn} = oauth_access(["read:accounts"], user: user) conn = get(conn, "/api/v1/accounts/verify_credentials") assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) assert id == to_string(user.id) end test "locked accounts" do user = insert(:user, default_scope: "private") %{conn: conn} = oauth_access(["read:accounts"], user: user) conn = get(conn, "/api/v1/accounts/verify_credentials") assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200) assert id == to_string(user.id) end end describe "user relationships" do setup do: oauth_access(["read:follows"]) test "returns the relationships for the current user", %{user: user, conn: conn} do %{id: other_user_id} = other_user = insert(:user) {:ok, _user} = User.follow(user, other_user) assert [%{"id" => ^other_user_id}] = conn |> get("/api/v1/accounts/relationships?id=#{other_user.id}") |> json_response(200) assert [%{"id" => ^other_user_id}] = conn |> get("/api/v1/accounts/relationships?id[]=#{other_user.id}") |> json_response(200) end test "returns an empty list on a bad request", %{conn: conn} do conn = get(conn, "/api/v1/accounts/relationships", %{}) assert [] = json_response(conn, 200) end end test "getting a list of mutes" do %{user: user, conn: conn} = oauth_access(["read:mutes"]) other_user = insert(:user) {:ok, _user_relationships} = User.mute(user, other_user) conn = get(conn, "/api/v1/mutes") other_user_id = to_string(other_user.id) assert [%{"id" => ^other_user_id}] = json_response(conn, 200) assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec()) end test "getting a list of blocks" do %{user: user, conn: conn} = oauth_access(["read:blocks"]) other_user = insert(:user) {:ok, _user_relationship} = User.block(user, other_user) conn = conn |> assign(:user, user) |> get("/api/v1/blocks") other_user_id = to_string(other_user.id) assert [%{"id" => ^other_user_id}] = json_response(conn, 200) assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec()) end end