Merge branch 'align-mastodon-conversations' into 'develop'
[akkoma] / test / web / mastodon_api / mastodon_api_controller_test.exs
index 3ac5c37a69d23561bd134d487dae8030c3c99bf5..9482f4cb8511654059c7d6b7fbdf04079eb7c5e8 100644 (file)
@@ -16,6 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.MastodonAPI.FilterView
   alias Pleroma.Web.OAuth.App
+  alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.OStatus
   alias Pleroma.Web.Push
   alias Pleroma.Web.TwitterAPI.TwitterAPI
@@ -80,6 +81,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end)
   end
 
+  test "the public timeline when public is set to false", %{conn: conn} do
+    public = Pleroma.Config.get([:instance, :public])
+    Pleroma.Config.put([:instance, :public], false)
+
+    on_exit(fn ->
+      Pleroma.Config.put([:instance, :public], public)
+    end)
+
+    assert conn
+           |> get("/api/v1/timelines/public", %{"local" => "False"})
+           |> json_response(403) == %{"error" => "This resource requires authentication."}
+  end
+
   test "posting a status", %{conn: conn} do
     user = insert(:user)
 
@@ -300,6 +314,69 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert status["url"] != direct.data["id"]
   end
 
+  test "Conversations", %{conn: conn} do
+    user_one = insert(:user)
+    user_two = insert(:user)
+    user_three = insert(:user)
+
+    {:ok, user_two} = User.follow(user_two, user_one)
+
+    {:ok, direct} =
+      CommonAPI.post(user_one, %{
+        "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
+        "visibility" => "direct"
+      })
+
+    {:ok, _follower_only} =
+      CommonAPI.post(user_one, %{
+        "status" => "Hi @#{user_two.nickname}!",
+        "visibility" => "private"
+      })
+
+    res_conn =
+      conn
+      |> assign(:user, user_one)
+      |> get("/api/v1/conversations")
+
+    assert response = json_response(res_conn, 200)
+
+    assert [
+             %{
+               "id" => res_id,
+               "accounts" => res_accounts,
+               "last_status" => res_last_status,
+               "unread" => unread
+             }
+           ] = response
+
+    account_ids = Enum.map(res_accounts, & &1["id"])
+    assert length(res_accounts) == 2
+    assert user_two.id in account_ids
+    assert user_three.id in account_ids
+    assert is_binary(res_id)
+    assert unread == true
+    assert res_last_status["id"] == direct.id
+
+    # Apparently undocumented API endpoint
+    res_conn =
+      conn
+      |> assign(:user, user_one)
+      |> post("/api/v1/conversations/#{res_id}/read")
+
+    assert response = json_response(res_conn, 200)
+    assert length(response["accounts"]) == 2
+    assert response["last_status"]["id"] == direct.id
+    assert response["unread"] == false
+
+    # (vanilla) Mastodon frontend behaviour
+    res_conn =
+      conn
+      |> assign(:user, user_one)
+      |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
+
+    assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
+  end
+
   test "doesn't include DMs from blocked users", %{conn: conn} do
     blocker = insert(:user)
     blocked = insert(:user)
@@ -342,7 +419,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     activity = Activity.get_by_id(id)
 
     assert activity.data["context"] == replied_to.data["context"]
-    assert activity.data["object"]["inReplyToStatusId"] == replied_to.id
+    assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
   end
 
   test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
@@ -373,7 +450,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   end
 
   test "verify_credentials default scope unlisted", %{conn: conn} do
-    user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "unlisted"}})
+    user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
 
     conn =
       conn
@@ -445,7 +522,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   describe "deleting a status" do
     test "when you created it", %{conn: conn} do
       activity = insert(:note_activity)
-      author = User.get_by_ap_id(activity.data["actor"])
+      author = User.get_cached_by_ap_id(activity.data["actor"])
 
       conn =
         conn
@@ -513,6 +590,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert response = json_response(conn, 200)
       assert response["phrase"] == filter.phrase
       assert response["context"] == filter.context
+      assert response["irreversible"] == false
       assert response["id"] != nil
       assert response["id"] != ""
     end
@@ -944,6 +1022,58 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
       assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
     end
+
+    test "destroy multiple", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+      {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+      {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
+      {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
+
+      notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
+      notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
+      notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
+      notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
+
+      conn =
+        conn
+        |> assign(:user, user)
+
+      conn_res =
+        conn
+        |> get("/api/v1/notifications")
+
+      result = json_response(conn_res, 200)
+      assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
+
+      conn2 =
+        conn
+        |> assign(:user, other_user)
+
+      conn_res =
+        conn2
+        |> get("/api/v1/notifications")
+
+      result = json_response(conn_res, 200)
+      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
+
+      conn_destroy =
+        conn
+        |> delete("/api/v1/notifications/destroy_multiple", %{
+          "ids" => [notification1_id, notification2_id]
+        })
+
+      assert json_response(conn_destroy, 200) == %{}
+
+      conn_res =
+        conn2
+        |> get("/api/v1/notifications")
+
+      result = json_response(conn_res, 200)
+      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
+    end
   end
 
   describe "reblogging" do
@@ -956,8 +1086,47 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         |> assign(:user, user)
         |> post("/api/v1/statuses/#{activity.id}/reblog")
 
-      assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
-               json_response(conn, 200)
+      assert %{
+               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
+               "reblogged" => true
+             } = json_response(conn, 200)
+
+      assert to_string(activity.id) == id
+    end
+
+    test "reblogged status for another user", %{conn: conn} do
+      activity = insert(:note_activity)
+      user1 = insert(:user)
+      user2 = insert(:user)
+      user3 = insert(:user)
+      CommonAPI.favorite(activity.id, user2)
+      {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
+      {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
+      {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
+
+      conn_res =
+        conn
+        |> assign(:user, user3)
+        |> get("/api/v1/statuses/#{reblog_activity1.id}")
+
+      assert %{
+               "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
+               "reblogged" => false,
+               "favourited" => false,
+               "bookmarked" => false
+             } = json_response(conn_res, 200)
+
+      conn_res =
+        conn
+        |> assign(:user, user2)
+        |> get("/api/v1/statuses/#{reblog_activity1.id}")
+
+      assert %{
+               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
+               "reblogged" => true,
+               "favourited" => true,
+               "bookmarked" => true
+             } = json_response(conn_res, 200)
 
       assert to_string(activity.id) == id
     end
@@ -1076,7 +1245,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
     test "unimplemented pinned statuses feature", %{conn: conn} do
       note = insert(:note_activity)
-      user = User.get_by_ap_id(note.data["actor"])
+      user = User.get_cached_by_ap_id(note.data["actor"])
 
       conn =
         conn
@@ -1087,7 +1256,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
     test "gets an users media", %{conn: conn} do
       note = insert(:note_activity)
-      user = User.get_by_ap_id(note.data["actor"])
+      user = User.get_cached_by_ap_id(note.data["actor"])
 
       file = %Plug.Upload{
         content_type: "image/jpg",
@@ -1157,13 +1326,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
   describe "locked accounts" do
     test "/api/v1/follow_requests works" do
-      user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
+      user = insert(:user, %{info: %User.Info{locked: true}})
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
 
-      user = User.get_by_id(user.id)
-      other_user = User.get_by_id(other_user.id)
+      user = User.get_cached_by_id(user.id)
+      other_user = User.get_cached_by_id(other_user.id)
 
       assert User.following?(other_user, user) == false
 
@@ -1182,8 +1351,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
 
-      user = User.get_by_id(user.id)
-      other_user = User.get_by_id(other_user.id)
+      user = User.get_cached_by_id(user.id)
+      other_user = User.get_cached_by_id(other_user.id)
 
       assert User.following?(other_user, user) == false
 
@@ -1195,14 +1364,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert relationship = json_response(conn, 200)
       assert to_string(other_user.id) == relationship["id"]
 
-      user = User.get_by_id(user.id)
-      other_user = User.get_by_id(other_user.id)
+      user = User.get_cached_by_id(user.id)
+      other_user = User.get_cached_by_id(other_user.id)
 
       assert User.following?(other_user, user) == true
     end
 
     test "verify_credentials", %{conn: conn} do
-      user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "private"}})
+      user = insert(:user, %{info: %User.Info{default_scope: "private"}})
 
       conn =
         conn
@@ -1214,12 +1383,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
 
     test "/api/v1/follow_requests/:id/reject works" do
-      user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
+      user = insert(:user, %{info: %User.Info{locked: true}})
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
 
-      user = User.get_by_id(user.id)
+      user = User.get_cached_by_id(user.id)
 
       conn =
         build_conn()
@@ -1229,8 +1398,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert relationship = json_response(conn, 200)
       assert to_string(other_user.id) == relationship["id"]
 
-      user = User.get_by_id(user.id)
-      other_user = User.get_by_id(other_user.id)
+      user = User.get_cached_by_id(user.id)
+      other_user = User.get_cached_by_id(other_user.id)
 
       assert User.following?(other_user, user) == false
     end
@@ -1290,6 +1459,72 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert object.data["actor"] == User.ap_id(user)
   end
 
+  test "mascot upload", %{conn: conn} do
+    user = insert(:user)
+
+    non_image_file = %Plug.Upload{
+      content_type: "audio/mpeg",
+      path: Path.absname("test/fixtures/sound.mp3"),
+      filename: "sound.mp3"
+    }
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
+
+    assert json_response(conn, 415)
+
+    file = %Plug.Upload{
+      content_type: "image/jpg",
+      path: Path.absname("test/fixtures/image.jpg"),
+      filename: "an_image.jpg"
+    }
+
+    conn =
+      build_conn()
+      |> assign(:user, user)
+      |> put("/api/v1/pleroma/mascot", %{"file" => file})
+
+    assert %{"id" => _, "type" => image} = json_response(conn, 200)
+  end
+
+  test "mascot retrieving", %{conn: conn} do
+    user = insert(:user)
+    # When user hasn't set a mascot, we should just get pleroma tan back
+    conn =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/pleroma/mascot")
+
+    assert %{"url" => url} = json_response(conn, 200)
+    assert url =~ "pleroma-fox-tan-smol"
+
+    # When a user sets their mascot, we should get that back
+    file = %Plug.Upload{
+      content_type: "image/jpg",
+      path: Path.absname("test/fixtures/image.jpg"),
+      filename: "an_image.jpg"
+    }
+
+    conn =
+      build_conn()
+      |> assign(:user, user)
+      |> put("/api/v1/pleroma/mascot", %{"file" => file})
+
+    assert json_response(conn, 200)
+
+    user = User.get_cached_by_id(user.id)
+
+    conn =
+      build_conn()
+      |> assign(:user, user)
+      |> get("/api/v1/pleroma/mascot")
+
+    assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
+    assert url =~ "an_image"
+  end
+
   test "hashtag timeline", %{conn: conn} do
     following = insert(:user)
 
@@ -1421,7 +1656,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert id2 == follower2.id
 
     assert [link_header] = get_resp_header(res_conn, "link")
-    assert link_header =~ ~r/since_id=#{follower2.id}/
+    assert link_header =~ ~r/min_id=#{follower2.id}/
     assert link_header =~ ~r/max_id=#{follower2.id}/
   end
 
@@ -1500,7 +1735,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert id2 == following2.id
 
     assert [link_header] = get_resp_header(res_conn, "link")
-    assert link_header =~ ~r/since_id=#{following2.id}/
+    assert link_header =~ ~r/min_id=#{following2.id}/
     assert link_header =~ ~r/max_id=#{following2.id}/
   end
 
@@ -1515,7 +1750,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
     assert %{"id" => _id, "following" => true} = json_response(conn, 200)
 
-    user = User.get_by_id(user.id)
+    user = User.get_cached_by_id(user.id)
 
     conn =
       build_conn()
@@ -1524,7 +1759,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
     assert %{"id" => _id, "following" => false} = json_response(conn, 200)
 
-    user = User.get_by_id(user.id)
+    user = User.get_cached_by_id(user.id)
 
     conn =
       build_conn()
@@ -1535,6 +1770,78 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert id == to_string(other_user.id)
   end
 
+  test "following without reblogs" do
+    follower = insert(:user)
+    followed = insert(:user)
+    other_user = insert(:user)
+
+    conn =
+      build_conn()
+      |> assign(:user, follower)
+      |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
+
+    assert %{"showing_reblogs" => false} = json_response(conn, 200)
+
+    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
+    {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
+
+    conn =
+      build_conn()
+      |> assign(:user, User.get_cached_by_id(follower.id))
+      |> get("/api/v1/timelines/home")
+
+    assert [] == json_response(conn, 200)
+
+    conn =
+      build_conn()
+      |> assign(:user, follower)
+      |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
+
+    assert %{"showing_reblogs" => true} = json_response(conn, 200)
+
+    conn =
+      build_conn()
+      |> assign(:user, User.get_cached_by_id(follower.id))
+      |> get("/api/v1/timelines/home")
+
+    expected_activity_id = reblog.id
+    assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
+  end
+
+  test "following / unfollowing errors" do
+    user = insert(:user)
+
+    conn =
+      build_conn()
+      |> assign(:user, user)
+
+    # 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 = post(conn, "/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 = post(conn, "/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
+
   test "muting / unmuting a user", %{conn: conn} do
     user = insert(:user)
     other_user = insert(:user)
@@ -1546,7 +1853,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
     assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
 
-    user = User.get_by_id(user.id)
+    user = User.get_cached_by_id(user.id)
 
     conn =
       build_conn()
@@ -1601,7 +1908,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
     assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
 
-    user = User.get_by_id(user.id)
+    user = User.get_cached_by_id(user.id)
 
     conn =
       build_conn()
@@ -1760,7 +2067,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     capture_log(fn ->
       conn =
         conn
-        |> get("/api/v1/search", %{"q" => activity.data["object"]["id"]})
+        |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
 
       assert results = json_response(conn, 200)
 
@@ -1825,6 +2132,199 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert [] = json_response(third_conn, 200)
   end
 
+  describe "getting favorites timeline of specified user" do
+    setup do
+      [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
+      [current_user: current_user, user: user]
+    end
+
+    test "returns list of statuses favorited by specified user", %{
+      conn: conn,
+      current_user: current_user,
+      user: user
+    } do
+      [activity | _] = insert_pair(:note_activity)
+      CommonAPI.favorite(activity.id, user)
+
+      response =
+        conn
+        |> assign(:user, current_user)
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
+        |> json_response(:ok)
+
+      [like] = response
+
+      assert length(response) == 1
+      assert like["id"] == activity.id
+    end
+
+    test "returns favorites for specified user_id when user is not logged in", %{
+      conn: conn,
+      user: user
+    } do
+      activity = insert(:note_activity)
+      CommonAPI.favorite(activity.id, user)
+
+      response =
+        conn
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
+        |> json_response(:ok)
+
+      assert length(response) == 1
+    end
+
+    test "returns favorited DM only when user is logged in and he is one of recipients", %{
+      conn: conn,
+      current_user: current_user,
+      user: user
+    } do
+      {:ok, direct} =
+        CommonAPI.post(current_user, %{
+          "status" => "Hi @#{user.nickname}!",
+          "visibility" => "direct"
+        })
+
+      CommonAPI.favorite(direct.id, user)
+
+      response =
+        conn
+        |> assign(:user, current_user)
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
+        |> json_response(:ok)
+
+      assert length(response) == 1
+
+      anonymous_response =
+        conn
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
+        |> json_response(:ok)
+
+      assert Enum.empty?(anonymous_response)
+    end
+
+    test "does not return others' favorited DM when user is not one of recipients", %{
+      conn: conn,
+      current_user: current_user,
+      user: user
+    } do
+      user_two = insert(:user)
+
+      {:ok, direct} =
+        CommonAPI.post(user_two, %{
+          "status" => "Hi @#{user.nickname}!",
+          "visibility" => "direct"
+        })
+
+      CommonAPI.favorite(direct.id, user)
+
+      response =
+        conn
+        |> assign(:user, current_user)
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
+        |> json_response(:ok)
+
+      assert Enum.empty?(response)
+    end
+
+    test "paginates favorites using since_id and max_id", %{
+      conn: conn,
+      current_user: current_user,
+      user: user
+    } do
+      activities = insert_list(10, :note_activity)
+
+      Enum.each(activities, fn activity ->
+        CommonAPI.favorite(activity.id, user)
+      end)
+
+      third_activity = Enum.at(activities, 2)
+      seventh_activity = Enum.at(activities, 6)
+
+      response =
+        conn
+        |> assign(:user, current_user)
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
+          since_id: third_activity.id,
+          max_id: seventh_activity.id
+        })
+        |> json_response(:ok)
+
+      assert length(response) == 3
+      refute third_activity in response
+      refute seventh_activity in response
+    end
+
+    test "limits favorites using limit parameter", %{
+      conn: conn,
+      current_user: current_user,
+      user: user
+    } do
+      7
+      |> insert_list(:note_activity)
+      |> Enum.each(fn activity ->
+        CommonAPI.favorite(activity.id, user)
+      end)
+
+      response =
+        conn
+        |> assign(:user, current_user)
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
+        |> json_response(:ok)
+
+      assert length(response) == 3
+    end
+
+    test "returns empty response when user does not have any favorited statuses", %{
+      conn: conn,
+      current_user: current_user,
+      user: user
+    } do
+      response =
+        conn
+        |> assign(:user, current_user)
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
+        |> json_response(:ok)
+
+      assert Enum.empty?(response)
+    end
+
+    test "returns 404 error when specified user is not exist", %{conn: conn} do
+      conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
+
+      assert json_response(conn, 404) == %{"error" => "Record not found"}
+    end
+
+    test "returns 403 error when user has hidden own favorites", %{
+      conn: conn,
+      current_user: current_user
+    } do
+      user = insert(:user, %{info: %{hide_favorites: true}})
+      activity = insert(:note_activity)
+      CommonAPI.favorite(activity.id, user)
+
+      conn =
+        conn
+        |> assign(:user, current_user)
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
+
+      assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
+    end
+
+    test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
+      user = insert(:user)
+      activity = insert(:note_activity)
+      CommonAPI.favorite(activity.id, user)
+
+      conn =
+        conn
+        |> assign(:user, current_user)
+        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
+
+      assert user.info.hide_favorites
+      assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
+    end
+  end
+
   describe "updating credentials" do
     test "updates the user's bio", %{conn: conn} do
       user = insert(:user)
@@ -1858,6 +2358,78 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert user["locked"] == true
     end
 
+    test "updates the user's default scope", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
+
+      assert user = json_response(conn, 200)
+      assert user["source"]["privacy"] == "cofe"
+    end
+
+    test "updates the user's hide_followers status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"})
+
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["hide_followers"] == true
+    end
+
+    test "updates the user's hide_follows status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"})
+
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["hide_follows"] == true
+    end
+
+    test "updates the user's hide_favorites status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
+
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["hide_favorites"] == true
+    end
+
+    test "updates the user's show_role status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"})
+
+      assert user = json_response(conn, 200)
+      assert user["source"]["pleroma"]["show_role"] == false
+    end
+
+    test "updates the user's no_rich_text status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
+
+      assert user = json_response(conn, 200)
+      assert user["source"]["pleroma"]["no_rich_text"] == true
+    end
+
     test "updates the user's name", %{conn: conn} do
       user = insert(:user)
 
@@ -1923,19 +2495,47 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         end
       end
     end
+
+    test "updates profile emojos", %{conn: conn} do
+      user = insert(:user)
+
+      note = "*sips :blank:*"
+      name = "I am :firefox:"
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{
+          "note" => note,
+          "display_name" => name
+        })
+
+      assert json_response(conn, 200)
+
+      conn =
+        conn
+        |> get("/api/v1/accounts/#{user.id}")
+
+      assert user = json_response(conn, 200)
+
+      assert user["note"] == note
+      assert user["display_name"] == name
+      assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
+    end
   end
 
   test "get instance information", %{conn: conn} do
     conn = get(conn, "/api/v1/instance")
     assert result = json_response(conn, 200)
 
+    email = Pleroma.Config.get([:instance, :email])
     # Note: not checking for "max_toot_chars" since it's optional
     assert %{
              "uri" => _,
              "title" => _,
              "description" => _,
              "version" => _,
-             "email" => _,
+             "email" => from_config_email,
              "urls" => %{
                "streaming_api" => _
              },
@@ -1944,6 +2544,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
              "languages" => _,
              "registrations" => _
            } = result
+
+    assert email == from_config_email
   end
 
   test "get instance stats", %{conn: conn} do
@@ -1958,7 +2560,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
 
     # Stats should count users with missing or nil `info.deactivated` value
-    user = User.get_by_id(user.id)
+    user = User.get_cached_by_id(user.id)
     info_change = Changeset.change(user.info, %{deactivated: nil})
 
     {:ok, _user} =
@@ -2086,33 +2688,50 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
                |> post("/api/v1/statuses/#{activity_two.id}/pin")
                |> json_response(400)
     end
+  end
 
-    test "Status rich-media Card", %{conn: conn, user: user} do
+  describe "cards" do
+    setup do
       Pleroma.Config.put([:rich_media, :enabled], true)
+
+      on_exit(fn ->
+        Pleroma.Config.put([:rich_media, :enabled], false)
+      end)
+
+      user = insert(:user)
+      %{user: user}
+    end
+
+    test "returns rich-media card", %{conn: conn, user: user} do
       {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
 
+      card_data = %{
+        "image" => "http://ia.media-imdb.com/images/rock.jpg",
+        "provider_name" => "www.imdb.com",
+        "provider_url" => "http://www.imdb.com",
+        "title" => "The Rock",
+        "type" => "link",
+        "url" => "http://www.imdb.com/title/tt0117500/",
+        "description" =>
+          "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
+        "pleroma" => %{
+          "opengraph" => %{
+            "image" => "http://ia.media-imdb.com/images/rock.jpg",
+            "title" => "The Rock",
+            "type" => "video.movie",
+            "url" => "http://www.imdb.com/title/tt0117500/",
+            "description" =>
+              "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
+          }
+        }
+      }
+
       response =
         conn
         |> get("/api/v1/statuses/#{activity.id}/card")
         |> json_response(200)
 
-      assert response == %{
-               "image" => "http://ia.media-imdb.com/images/rock.jpg",
-               "provider_name" => "www.imdb.com",
-               "provider_url" => "http://www.imdb.com",
-               "title" => "The Rock",
-               "type" => "link",
-               "url" => "http://www.imdb.com/title/tt0117500/",
-               "description" => nil,
-               "pleroma" => %{
-                 "opengraph" => %{
-                   "image" => "http://ia.media-imdb.com/images/rock.jpg",
-                   "title" => "The Rock",
-                   "type" => "video.movie",
-                   "url" => "http://www.imdb.com/title/tt0117500/"
-                 }
-               }
-             }
+      assert response == card_data
 
       # works with private posts
       {:ok, activity} =
@@ -2124,9 +2743,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         |> get("/api/v1/statuses/#{activity.id}/card")
         |> json_response(200)
 
-      assert response_two == response
+      assert response_two == card_data
+    end
+
+    test "replaces missing description with an empty string", %{conn: conn, user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp-missing-data"})
+
+      response =
+        conn
+        |> get("/api/v1/statuses/#{activity.id}/card")
+        |> json_response(:ok)
 
-      Pleroma.Config.put([:rich_media, :enabled], false)
+      assert response == %{
+               "type" => "link",
+               "title" => "Pleroma",
+               "description" => "",
+               "image" => nil,
+               "provider_name" => "pleroma.social",
+               "provider_url" => "https://pleroma.social",
+               "url" => "https://pleroma.social/",
+               "pleroma" => %{
+                 "opengraph" => %{
+                   "title" => "Pleroma",
+                   "type" => "website",
+                   "url" => "https://pleroma.social/"
+                 }
+               }
+             }
     end
   end
 
@@ -2213,31 +2856,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
   end
 
-  test "flavours switching (Pleroma Extension)", %{conn: conn} do
-    user = insert(:user)
-
-    get_old_flavour =
-      conn
-      |> assign(:user, user)
-      |> get("/api/v1/pleroma/flavour")
-
-    assert "glitch" == json_response(get_old_flavour, 200)
-
-    set_flavour =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/pleroma/flavour/vanilla")
-
-    assert "vanilla" == json_response(set_flavour, 200)
-
-    get_new_flavour =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/pleroma/flavour/vanilla")
-
-    assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200)
-  end
-
   describe "reports" do
     setup do
       reporter = insert(:user)
@@ -2330,7 +2948,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
       assert [link_header] = get_resp_header(conn, "link")
       assert link_header =~ ~r/media_only=true/
-      assert link_header =~ ~r/since_id=#{notification2.id}/
+      assert link_header =~ ~r/min_id=#{notification2.id}/
       assert link_header =~ ~r/max_id=#{notification1.id}/
     end
   end
@@ -2653,4 +3271,174 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert %{"error" => "Record not found"} = json_response(res_conn, 404)
     end
   end
+
+  test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
+    user1 = insert(:user)
+    user2 = insert(:user)
+    user3 = insert(:user)
+
+    {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
+
+    # Reply to status from another user
+    conn1 =
+      conn
+      |> assign(:user, user2)
+      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
+
+    assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
+
+    activity = Activity.get_by_id_with_object(id)
+
+    assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
+    assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
+
+    # Reblog from the third user
+    conn2 =
+      conn
+      |> assign(:user, user3)
+      |> post("/api/v1/statuses/#{activity.id}/reblog")
+
+    assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
+             json_response(conn2, 200)
+
+    assert to_string(activity.id) == id
+
+    # Getting third user status
+    conn3 =
+      conn
+      |> assign(:user, user3)
+      |> get("api/v1/timelines/home")
+
+    [reblogged_activity] = json_response(conn3, 200)
+
+    assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
+
+    replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
+    assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
+  end
+
+  describe "create account by app" do
+    setup do
+      enabled = Pleroma.Config.get([:app_account_creation, :enabled])
+      max_requests = Pleroma.Config.get([:app_account_creation, :max_requests])
+      interval = Pleroma.Config.get([:app_account_creation, :interval])
+
+      Pleroma.Config.put([:app_account_creation, :enabled], true)
+      Pleroma.Config.put([:app_account_creation, :max_requests], 5)
+      Pleroma.Config.put([:app_account_creation, :interval], 1)
+
+      on_exit(fn ->
+        Pleroma.Config.put([:app_account_creation, :enabled], enabled)
+        Pleroma.Config.put([:app_account_creation, :max_requests], max_requests)
+        Pleroma.Config.put([:app_account_creation, :interval], interval)
+      end)
+
+      :ok
+    end
+
+    test "Account registration via Application", %{conn: conn} do
+      conn =
+        conn
+        |> 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 =
+        conn
+        |> post("/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("authorization", "Bearer " <> token)
+        |> post("/api/v1/accounts", %{
+          username: "lain",
+          email: "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.info.confirmation_pending
+    end
+
+    test "rate limit", %{conn: conn} do
+      app_token = insert(:oauth_token, user: nil)
+
+      conn =
+        put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+        |> Map.put(:remote_ip, {15, 15, 15, 15})
+
+      for i <- 1..5 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.info.confirmation_pending
+      end
+
+      conn =
+        conn
+        |> post("/api/v1/accounts", %{
+          username: "6lain",
+          email: "6lain@example.org",
+          password: "PlzDontHackLain",
+          agreement: true
+        })
+
+      assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}
+    end
+  end
 end