Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms
[akkoma] / test / web / common_api / common_api_test.exs
index d69ed38a72f5fd67914714addc9ed915729ea6df..9e129e5a723ab62187f053f958b27952f1876f46 100644 (file)
 # Pleroma: A lightweight social networking server
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.CommonAPITest do
   use Pleroma.DataCase
   alias Pleroma.Activity
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.CommonAPITest do
   use Pleroma.DataCase
   alias Pleroma.Activity
+  alias Pleroma.Chat
   alias Pleroma.Conversation.Participation
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Conversation.Participation
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.AdminAPI.AccountView
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.AdminAPI.AccountView
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
+  import Mock
 
   require Pleroma.Constants
 
 
   require Pleroma.Constants
 
-  clear_config([:instance, :safe_dm_mentions])
-  clear_config([:instance, :limit])
-  clear_config([:instance, :max_pinned_statuses])
+  setup do: clear_config([:instance, :safe_dm_mentions])
+  setup do: clear_config([:instance, :limit])
+  setup do: clear_config([:instance, :max_pinned_statuses])
+
+  describe "posting chat messages" do
+    setup do: clear_config([:instance, :chat_limit])
+
+    test "it posts a chat message without content but with an attachment" do
+      author = insert(:user)
+      recipient = insert(:user)
+
+      file = %Plug.Upload{
+        content_type: "image/jpg",
+        path: Path.absname("test/fixtures/image.jpg"),
+        filename: "an_image.jpg"
+      }
+
+      {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
+
+      {:ok, activity} =
+        CommonAPI.post_chat_message(
+          author,
+          recipient,
+          nil,
+          media_id: upload.id
+        )
+
+      assert activity
+    end
+
+    test "it posts a chat message" do
+      author = insert(:user)
+      recipient = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post_chat_message(
+          author,
+          recipient,
+          "a test message <script>alert('uuu')</script> :firefox:"
+        )
+
+      assert activity.data["type"] == "Create"
+      assert activity.local
+      object = Object.normalize(activity)
+
+      assert object.data["type"] == "ChatMessage"
+      assert object.data["to"] == [recipient.ap_id]
+
+      assert object.data["content"] ==
+               "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt; :firefox:"
+
+      assert object.data["emoji"] == %{
+               "firefox" => "http://localhost:4001/emoji/Firefox.gif"
+             }
+
+      assert Chat.get(author.id, recipient.ap_id)
+      assert Chat.get(recipient.id, author.ap_id)
+
+      assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
+    end
+
+    test "it reject messages over the local limit" do
+      Pleroma.Config.put([:instance, :chat_limit], 2)
+
+      author = insert(:user)
+      recipient = insert(:user)
+
+      {:error, message} =
+        CommonAPI.post_chat_message(
+          author,
+          recipient,
+          "123"
+        )
+
+      assert message == :content_too_long
+    end
+  end
+
+  describe "unblocking" do
+    test "it works even without an existing block activity" do
+      blocked = insert(:user)
+      blocker = insert(:user)
+      User.block(blocker, blocked)
+
+      assert User.blocks?(blocker, blocked)
+      assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
+      refute User.blocks?(blocker, blocked)
+    end
+  end
+
+  describe "deletion" do
+    test "it works with pruned objects" do
+      user = insert(:user)
+
+      {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
+
+      clear_config([:instance, :federating], true)
+
+      Object.normalize(post, false)
+      |> Object.prune()
+
+      with_mock Pleroma.Web.Federator,
+        publish: fn _ -> nil end do
+        assert {:ok, delete} = CommonAPI.delete(post.id, user)
+        assert delete.local
+        assert called(Pleroma.Web.Federator.publish(delete))
+      end
+
+      refute Activity.get_by_id(post.id)
+    end
+
+    test "it allows users to delete their posts" do
+      user = insert(:user)
+
+      {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
+
+      clear_config([:instance, :federating], true)
+
+      with_mock Pleroma.Web.Federator,
+        publish: fn _ -> nil end do
+        assert {:ok, delete} = CommonAPI.delete(post.id, user)
+        assert delete.local
+        assert called(Pleroma.Web.Federator.publish(delete))
+      end
+
+      refute Activity.get_by_id(post.id)
+    end
+
+    test "it does not allow a user to delete their posts" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
+
+      assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
+      assert Activity.get_by_id(post.id)
+    end
+
+    test "it allows moderators to delete other user's posts" do
+      user = insert(:user)
+      moderator = insert(:user, is_moderator: true)
+
+      {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
+
+      assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
+      assert delete.local
+
+      refute Activity.get_by_id(post.id)
+    end
+
+    test "it allows admins to delete other user's posts" do
+      user = insert(:user)
+      moderator = insert(:user, is_admin: true)
+
+      {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
+
+      assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
+      assert delete.local
+
+      refute Activity.get_by_id(post.id)
+    end
+
+    test "superusers deleting non-local posts won't federate the delete" do
+      # This is the user of the ingested activity
+      _user =
+        insert(:user,
+          local: false,
+          ap_id: "http://mastodon.example.org/users/admin",
+          last_refreshed_at: NaiveDateTime.utc_now()
+        )
+
+      moderator = insert(:user, is_admin: true)
+
+      data =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Jason.decode!()
+
+      {:ok, post} = Transmogrifier.handle_incoming(data)
+
+      with_mock Pleroma.Web.Federator,
+        publish: fn _ -> nil end do
+        assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
+        assert delete.local
+        refute called(Pleroma.Web.Federator.publish(:_))
+      end
+
+      refute Activity.get_by_id(post.id)
+    end
+  end
+
+  test "favoriting race condition" do
+    user = insert(:user)
+    users_serial = insert_list(10, :user)
+    users = insert_list(10, :user)
+
+    {:ok, activity} = CommonAPI.post(user, %{status: "."})
+
+    users_serial
+    |> Enum.map(fn user ->
+      CommonAPI.favorite(user, activity.id)
+    end)
+
+    object = Object.get_by_ap_id(activity.data["object"])
+    assert object.data["like_count"] == 10
+
+    users
+    |> Enum.map(fn user ->
+      Task.async(fn ->
+        CommonAPI.favorite(user, activity.id)
+      end)
+    end)
+    |> Enum.map(&Task.await/1)
+
+    object = Object.get_by_ap_id(activity.data["object"])
+    assert object.data["like_count"] == 20
+  end
+
+  test "repeating race condition" do
+    user = insert(:user)
+    users_serial = insert_list(10, :user)
+    users = insert_list(10, :user)
+
+    {:ok, activity} = CommonAPI.post(user, %{status: "."})
+
+    users_serial
+    |> Enum.map(fn user ->
+      CommonAPI.repeat(activity.id, user)
+    end)
+
+    object = Object.get_by_ap_id(activity.data["object"])
+    assert object.data["announcement_count"] == 10
+
+    users
+    |> Enum.map(fn user ->
+      Task.async(fn ->
+        CommonAPI.repeat(activity.id, user)
+      end)
+    end)
+    |> Enum.map(&Task.await/1)
+
+    object = Object.get_by_ap_id(activity.data["object"])
+    assert object.data["announcement_count"] == 20
+  end
 
   test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
     user = insert(:user)
 
   test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
     user = insert(:user)
-    {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
+    {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
 
     [participation] = Participation.for_user(user)
 
     {:ok, convo_reply} =
 
     [participation] = Participation.for_user(user)
 
     {:ok, convo_reply} =
-      CommonAPI.post(user, %{"status" => ".", "in_reply_to_conversation_id" => participation.id})
+      CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
 
     assert Visibility.is_direct?(convo_reply)
 
 
     assert Visibility.is_direct?(convo_reply)
 
@@ -42,8 +285,8 @@ defmodule Pleroma.Web.CommonAPITest do
 
     {:ok, activity} =
       CommonAPI.post(har, %{
 
     {:ok, activity} =
       CommonAPI.post(har, %{
-        "status" => "@#{jafnhar.nickname} hey",
-        "visibility" => "direct"
+        status: "@#{jafnhar.nickname} hey",
+        visibility: "direct"
       })
 
     assert har.ap_id in activity.recipients
       })
 
     assert har.ap_id in activity.recipients
@@ -53,10 +296,10 @@ defmodule Pleroma.Web.CommonAPITest do
 
     {:ok, activity} =
       CommonAPI.post(har, %{
 
     {:ok, activity} =
       CommonAPI.post(har, %{
-        "status" => "I don't really like @#{tridi.nickname}",
-        "visibility" => "direct",
-        "in_reply_to_status_id" => activity.id,
-        "in_reply_to_conversation_id" => participation.id
+        status: "I don't really like @#{tridi.nickname}",
+        visibility: "direct",
+        in_reply_to_status_id: activity.id,
+        in_reply_to_conversation_id: participation.id
       })
 
     assert har.ap_id in activity.recipients
       })
 
     assert har.ap_id in activity.recipients
@@ -68,12 +311,13 @@ defmodule Pleroma.Web.CommonAPITest do
     har = insert(:user)
     jafnhar = insert(:user)
     tridi = insert(:user)
     har = insert(:user)
     jafnhar = insert(:user)
     tridi = insert(:user)
+
     Pleroma.Config.put([:instance, :safe_dm_mentions], true)
 
     {:ok, activity} =
       CommonAPI.post(har, %{
     Pleroma.Config.put([:instance, :safe_dm_mentions], true)
 
     {:ok, activity} =
       CommonAPI.post(har, %{
-        "status" => "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
-        "visibility" => "direct"
+        status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
+        visibility: "direct"
       })
 
     refute tridi.ap_id in activity.recipients
       })
 
     refute tridi.ap_id in activity.recipients
@@ -82,7 +326,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
   test "it de-duplicates tags" do
     user = insert(:user)
 
   test "it de-duplicates tags" do
     user = insert(:user)
-    {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})
+    {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
 
     object = Object.normalize(activity)
 
 
     object = Object.normalize(activity)
 
@@ -91,23 +335,11 @@ defmodule Pleroma.Web.CommonAPITest do
 
   test "it adds emoji in the object" do
     user = insert(:user)
 
   test "it adds emoji in the object" do
     user = insert(:user)
-    {:ok, activity} = CommonAPI.post(user, %{"status" => ":firefox:"})
+    {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
 
     assert Object.normalize(activity).data["emoji"]["firefox"]
   end
 
 
     assert Object.normalize(activity).data["emoji"]["firefox"]
   end
 
-  test "it adds emoji when updating profiles" do
-    user = insert(:user, %{name: ":firefox:"})
-
-    {:ok, activity} = CommonAPI.update(user)
-    user = User.get_cached_by_ap_id(user.ap_id)
-    [firefox] = user.info.source_data["tag"]
-
-    assert firefox["name"] == ":firefox:"
-
-    assert Pleroma.Constants.as_public() in activity.recipients
-  end
-
   describe "posting" do
     test "it supports explicit addressing" do
       user = insert(:user)
   describe "posting" do
     test "it supports explicit addressing" do
       user = insert(:user)
@@ -117,9 +349,9 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, activity} =
         CommonAPI.post(user, %{
 
       {:ok, activity} =
         CommonAPI.post(user, %{
-          "status" =>
+          status:
             "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
             "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
-          "to" => [user_two.nickname, user_four.nickname, "nonexistent"]
+          to: [user_two.nickname, user_four.nickname, "nonexistent"]
         })
 
       assert user.ap_id in activity.recipients
         })
 
       assert user.ap_id in activity.recipients
@@ -135,13 +367,13 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, activity} =
         CommonAPI.post(user, %{
 
       {:ok, activity} =
         CommonAPI.post(user, %{
-          "status" => post,
-          "content_type" => "text/html"
+          status: post,
+          content_type: "text/html"
         })
 
       object = Object.normalize(activity)
 
         })
 
       object = Object.normalize(activity)
 
-      assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
+      assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
     end
 
     test "it filters out obviously bad tags when accepting a post as Markdown" do
     end
 
     test "it filters out obviously bad tags when accepting a post as Markdown" do
@@ -151,43 +383,68 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, activity} =
         CommonAPI.post(user, %{
 
       {:ok, activity} =
         CommonAPI.post(user, %{
-          "status" => post,
-          "content_type" => "text/markdown"
+          status: post,
+          content_type: "text/markdown"
         })
 
       object = Object.normalize(activity)
 
         })
 
       object = Object.normalize(activity)
 
-      assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
+      assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
     end
 
     test "it does not allow replies to direct messages that are not direct messages themselves" do
       user = insert(:user)
 
     end
 
     test "it does not allow replies to direct messages that are not direct messages themselves" do
       user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
+      {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
 
       assert {:ok, _} =
                CommonAPI.post(user, %{
 
       assert {:ok, _} =
                CommonAPI.post(user, %{
-                 "status" => "suya..",
-                 "visibility" => "direct",
-                 "in_reply_to_status_id" => activity.id
+                 status: "suya..",
+                 visibility: "direct",
+                 in_reply_to_status_id: activity.id
                })
 
       Enum.each(["public", "private", "unlisted"], fn visibility ->
         assert {:error, "The message visibility must be direct"} =
                  CommonAPI.post(user, %{
                })
 
       Enum.each(["public", "private", "unlisted"], fn visibility ->
         assert {:error, "The message visibility must be direct"} =
                  CommonAPI.post(user, %{
-                   "status" => "suya..",
-                   "visibility" => visibility,
-                   "in_reply_to_status_id" => activity.id
+                   status: "suya..",
+                   visibility: visibility,
+                   in_reply_to_status_id: activity.id
                  })
       end)
     end
 
                  })
       end)
     end
 
+    test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
+      user = insert(:user)
+      other_user = insert(:user)
+      third_user = insert(:user)
+
+      {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
+
+      {:ok, open_answer} =
+        CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
+
+      # The OP is implicitly added
+      assert user.ap_id in open_answer.recipients
+
+      {:ok, secret_answer} =
+        CommonAPI.post(other_user, %{
+          status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
+          in_reply_to_status_id: post.id,
+          visibility: "direct"
+        })
+
+      assert third_user.ap_id in secret_answer.recipients
+
+      # The OP is not added
+      refute user.ap_id in secret_answer.recipients
+    end
+
     test "it allows to address a list" do
       user = insert(:user)
       {:ok, list} = Pleroma.List.create("foo", user)
 
     test "it allows to address a list" do
       user = insert(:user)
       {:ok, list} = Pleroma.List.create("foo", user)
 
-      {:ok, activity} =
-        CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
+      {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
 
       assert activity.data["bcc"] == [list.ap_id]
       assert activity.recipients == [list.ap_id, user.ap_id]
 
       assert activity.data["bcc"] == [list.ap_id]
       assert activity.recipients == [list.ap_id, user.ap_id]
@@ -198,16 +455,18 @@ defmodule Pleroma.Web.CommonAPITest do
       user = insert(:user)
 
       assert {:error, "Cannot post an empty status without attachments"} =
       user = insert(:user)
 
       assert {:error, "Cannot post an empty status without attachments"} =
-               CommonAPI.post(user, %{"status" => ""})
+               CommonAPI.post(user, %{status: ""})
     end
 
     end
 
-    test "it returns error when character limit is exceeded" do
+    test "it validates character limits are correctly enforced" do
       Pleroma.Config.put([:instance, :limit], 5)
 
       user = insert(:user)
 
       assert {:error, "The status is over the character limit"} =
       Pleroma.Config.put([:instance, :limit], 5)
 
       user = insert(:user)
 
       assert {:error, "The status is over the character limit"} =
-               CommonAPI.post(user, %{"status" => "foobar"})
+               CommonAPI.post(user, %{status: "foobar"})
+
+      assert {:ok, activity} = CommonAPI.post(user, %{status: "12345"})
     end
 
     test "it can handle activities that expire" do
     end
 
     test "it can handle activities that expire" do
@@ -218,8 +477,7 @@ defmodule Pleroma.Web.CommonAPITest do
         |> NaiveDateTime.truncate(:second)
         |> NaiveDateTime.add(1_000_000, :second)
 
         |> NaiveDateTime.truncate(:second)
         |> NaiveDateTime.add(1_000_000, :second)
 
-      assert {:ok, activity} =
-               CommonAPI.post(user, %{"status" => "chai", "expires_in" => 1_000_000})
+      assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
 
       assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
       assert expiration.scheduled_at == expires_at
 
       assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
       assert expiration.scheduled_at == expires_at
@@ -227,52 +485,97 @@ defmodule Pleroma.Web.CommonAPITest do
   end
 
   describe "reactions" do
   end
 
   describe "reactions" do
+    test "reacting to a status with an emoji" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
+
+      {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
+
+      assert reaction.data["actor"] == user.ap_id
+      assert reaction.data["content"] == "👍"
+
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
+
+      {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
+    end
+
+    test "unreacting to a status with an emoji" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
+      {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
+
+      {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
+
+      assert unreaction.data["type"] == "Undo"
+      assert unreaction.data["object"] == reaction.data["id"]
+      assert unreaction.local
+    end
+
     test "repeating a status" do
       user = insert(:user)
       other_user = insert(:user)
 
     test "repeating a status" do
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
 
 
-      {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
+      {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
+      assert Visibility.is_public?(announce_activity)
+    end
+
+    test "can't repeat a repeat" do
+      user = insert(:user)
+      other_user = insert(:user)
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
+
+      {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
+
+      refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
     end
 
     test "repeating a status privately" do
       user = insert(:user)
       other_user = insert(:user)
 
     end
 
     test "repeating a status privately" do
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
 
 
-      {:ok, %Activity{} = announce_activity, _} =
-        CommonAPI.repeat(activity.id, user, %{"visibility" => "private"})
+      {:ok, %Activity{} = announce_activity} =
+        CommonAPI.repeat(activity.id, user, %{visibility: "private"})
 
       assert Visibility.is_private?(announce_activity)
 
       assert Visibility.is_private?(announce_activity)
+      refute Visibility.visible_for_user?(announce_activity, nil)
     end
 
     test "favoriting a status" do
       user = insert(:user)
       other_user = insert(:user)
 
     end
 
     test "favoriting a status" do
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+      {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
 
 
-      {:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
+      {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
+      assert data["type"] == "Like"
+      assert data["actor"] == user.ap_id
+      assert data["object"] == post_activity.data["object"]
     end
 
     end
 
-    test "retweeting a status twice returns an error" do
+    test "retweeting a status twice returns the status" do
       user = insert(:user)
       other_user = insert(:user)
 
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
-      {:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user)
-      {:error, _} = CommonAPI.repeat(activity.id, user)
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
+      {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
+      {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
     end
 
     end
 
-    test "favoriting a status twice returns an error" do
+    test "favoriting a status twice returns ok, but without the like activity" do
       user = insert(:user)
       other_user = insert(:user)
 
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
-      {:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user)
-      {:error, _} = CommonAPI.favorite(activity.id, user)
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
+      {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
+      assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
     end
   end
 
     end
   end
 
@@ -281,7 +584,7 @@ defmodule Pleroma.Web.CommonAPITest do
       Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
 
       user = insert(:user)
       Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
 
       user = insert(:user)
-      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
+      {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
 
       [user: user, activity: activity]
     end
 
       [user: user, activity: activity]
     end
@@ -292,11 +595,26 @@ defmodule Pleroma.Web.CommonAPITest do
       id = activity.id
       user = refresh_record(user)
 
       id = activity.id
       user = refresh_record(user)
 
-      assert %User{info: %{pinned_activities: [^id]}} = user
+      assert %User{pinned_activities: [^id]} = user
+    end
+
+    test "pin poll", %{user: user} do
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          status: "How is fediverse today?",
+          poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
+        })
+
+      assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
+
+      id = activity.id
+      user = refresh_record(user)
+
+      assert %User{pinned_activities: [^id]} = user
     end
 
     test "unlisted statuses can be pinned", %{user: user} do
     end
 
     test "unlisted statuses can be pinned", %{user: user} do
-      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"})
+      {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
     end
 
       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
     end
 
@@ -307,7 +625,7 @@ defmodule Pleroma.Web.CommonAPITest do
     end
 
     test "max pinned statuses", %{user: user, activity: activity_one} do
     end
 
     test "max pinned statuses", %{user: user, activity: activity_one} do
-      {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
+      {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
 
       assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
 
 
       assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
 
@@ -322,11 +640,13 @@ defmodule Pleroma.Web.CommonAPITest do
 
       user = refresh_record(user)
 
 
       user = refresh_record(user)
 
-      assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
+      id = activity.id
+
+      assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
 
       user = refresh_record(user)
 
 
       user = refresh_record(user)
 
-      assert %User{info: %{pinned_activities: []}} = user
+      assert %User{pinned_activities: []} = user
     end
 
     test "should unpin when deleting a status", %{user: user, activity: activity} do
     end
 
     test "should unpin when deleting a status", %{user: user, activity: activity} do
@@ -338,7 +658,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
       user = refresh_record(user)
 
 
       user = refresh_record(user)
 
-      assert %User{info: %{pinned_activities: []}} = user
+      assert %User{pinned_activities: []} = user
     end
   end
 
     end
   end
 
@@ -373,7 +693,7 @@ defmodule Pleroma.Web.CommonAPITest do
       reporter = insert(:user)
       target_user = insert(:user)
 
       reporter = insert(:user)
       target_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
+      {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
 
       reporter_ap_id = reporter.ap_id
       target_ap_id = target_user.ap_id
 
       reporter_ap_id = reporter.ap_id
       target_ap_id = target_user.ap_id
@@ -381,9 +701,9 @@ defmodule Pleroma.Web.CommonAPITest do
       comment = "foobar"
 
       report_data = %{
       comment = "foobar"
 
       report_data = %{
-        "account_id" => target_user.id,
-        "comment" => comment,
-        "status_ids" => [activity.id]
+        account_id: target_user.id,
+        comment: comment,
+        status_ids: [activity.id]
       }
 
       note_obj = %{
       }
 
       note_obj = %{
@@ -413,9 +733,9 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, %Activity{id: report_id}} =
         CommonAPI.report(reporter, %{
 
       {:ok, %Activity{id: report_id}} =
         CommonAPI.report(reporter, %{
-          "account_id" => target_user.id,
-          "comment" => "I feel offended",
-          "status_ids" => [activity.id]
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
         })
 
       {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
         })
 
       {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
@@ -434,13 +754,42 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, %Activity{id: report_id}} =
         CommonAPI.report(reporter, %{
 
       {:ok, %Activity{id: report_id}} =
         CommonAPI.report(reporter, %{
-          "account_id" => target_user.id,
-          "comment" => "I feel offended",
-          "status_ids" => [activity.id]
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
         })
 
       assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
     end
         })
 
       assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
     end
+
+    test "updates state of multiple reports" do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %Activity{id: first_report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      {:ok, %Activity{id: second_report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel very offended!",
+          status_ids: [activity.id]
+        })
+
+      {:ok, report_ids} =
+        CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
+
+      first_report = Activity.get_by_id(first_report_id)
+      second_report = Activity.get_by_id(second_report_id)
+
+      assert report_ids -- [first_report_id, second_report_id] == []
+      assert first_report.data["state"] == "resolved"
+      assert second_report.data["state"] == "resolved"
+    end
   end
 
   describe "reblog muting" do
   end
 
   describe "reblog muting" do
@@ -453,14 +802,14 @@ defmodule Pleroma.Web.CommonAPITest do
     end
 
     test "add a reblog mute", %{muter: muter, muted: muted} do
     end
 
     test "add a reblog mute", %{muter: muter, muted: muted} do
-      {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
+      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
 
       assert User.showing_reblogs?(muter, muted) == false
     end
 
     test "remove a reblog mute", %{muter: muter, muted: muted} do
 
       assert User.showing_reblogs?(muter, muted) == false
     end
 
     test "remove a reblog mute", %{muter: muter, muted: muted} do
-      {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
-      {:ok, muter} = CommonAPI.show_reblogs(muter, muted)
+      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
+      {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
 
       assert User.showing_reblogs?(muter, muted) == true
     end
 
       assert User.showing_reblogs?(muter, muted) == true
     end
@@ -470,7 +819,7 @@ defmodule Pleroma.Web.CommonAPITest do
     test "also unsubscribes a user" do
       [follower, followed] = insert_pair(:user)
       {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
     test "also unsubscribes a user" do
       [follower, followed] = insert_pair(:user)
       {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
-      {:ok, followed} = User.subscribe(follower, followed)
+      {:ok, _subscription} = User.subscribe(follower, followed)
 
       assert User.subscribed_to?(follower, followed)
 
 
       assert User.subscribed_to?(follower, followed)
 
@@ -478,11 +827,55 @@ defmodule Pleroma.Web.CommonAPITest do
 
       refute User.subscribed_to?(follower, followed)
     end
 
       refute User.subscribed_to?(follower, followed)
     end
+
+    test "cancels a pending follow for a local user" do
+      follower = insert(:user)
+      followed = insert(:user, locked: true)
+
+      assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
+               CommonAPI.follow(follower, followed)
+
+      assert User.get_follow_state(follower, followed) == :follow_pending
+      assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
+      assert User.get_follow_state(follower, followed) == nil
+
+      assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
+               Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
+
+      assert %{
+               data: %{
+                 "type" => "Undo",
+                 "object" => %{"type" => "Follow", "state" => "cancelled"}
+               }
+             } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
+    end
+
+    test "cancels a pending follow for a remote user" do
+      follower = insert(:user)
+      followed = insert(:user, locked: true, local: false, ap_enabled: true)
+
+      assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
+               CommonAPI.follow(follower, followed)
+
+      assert User.get_follow_state(follower, followed) == :follow_pending
+      assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
+      assert User.get_follow_state(follower, followed) == nil
+
+      assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
+               Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
+
+      assert %{
+               data: %{
+                 "type" => "Undo",
+                 "object" => %{"type" => "Follow", "state" => "cancelled"}
+               }
+             } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
+    end
   end
 
   describe "accept_follow_request/2" do
     test "after acceptance, it sets all existing pending follow request states to 'accept'" do
   end
 
   describe "accept_follow_request/2" do
     test "after acceptance, it sets all existing pending follow request states to 'accept'" do
-      user = insert(:user, info: %{locked: true})
+      user = insert(:user, locked: true)
       follower = insert(:user)
       follower_two = insert(:user)
 
       follower = insert(:user)
       follower_two = insert(:user)
 
@@ -502,7 +895,7 @@ defmodule Pleroma.Web.CommonAPITest do
     end
 
     test "after rejection, it sets all existing pending follow request states to 'reject'" do
     end
 
     test "after rejection, it sets all existing pending follow request states to 'reject'" do
-      user = insert(:user, info: %{locked: true})
+      user = insert(:user, locked: true)
       follower = insert(:user)
       follower_two = insert(:user)
 
       follower = insert(:user)
       follower_two = insert(:user)
 
@@ -520,6 +913,14 @@ defmodule Pleroma.Web.CommonAPITest do
       assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
       assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
     end
       assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
       assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
     end
+
+    test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
+      user = insert(:user, locked: true)
+      not_follower = insert(:user)
+      CommonAPI.accept_follow_request(not_follower, user)
+
+      assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
+    end
   end
 
   describe "vote/3" do
   end
 
   describe "vote/3" do
@@ -529,8 +930,8 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, activity} =
         CommonAPI.post(user, %{
 
       {:ok, activity} =
         CommonAPI.post(user, %{
-          "status" => "Am I cute?",
-          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
+          status: "Am I cute?",
+          poll: %{options: ["Yes", "No"], expires_in: 20}
         })
 
       object = Object.normalize(activity)
         })
 
       object = Object.normalize(activity)
@@ -547,10 +948,10 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, activity} =
         CommonAPI.listen(user, %{
 
       {:ok, activity} =
         CommonAPI.listen(user, %{
-          "title" => "lain radio episode 1",
-          "album" => "lain radio",
-          "artist" => "lain",
-          "length" => 180_000
+          title: "lain radio episode 1",
+          album: "lain radio",
+          artist: "lain",
+          length: 180_000
         })
 
       object = Object.normalize(activity)
         })
 
       object = Object.normalize(activity)
@@ -565,11 +966,11 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, activity} =
         CommonAPI.listen(user, %{
 
       {:ok, activity} =
         CommonAPI.listen(user, %{
-          "title" => "lain radio episode 1",
-          "album" => "lain radio",
-          "artist" => "lain",
-          "length" => 180_000,
-          "visibility" => "private"
+          title: "lain radio episode 1",
+          album: "lain radio",
+          artist: "lain",
+          length: 180_000,
+          visibility: "private"
         })
 
       object = Object.normalize(activity)
         })
 
       object = Object.normalize(activity)