Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/emojireactv...
authorlain <lain@soykaf.club>
Thu, 7 May 2020 16:53:34 +0000 (18:53 +0200)
committerlain <lain@soykaf.club>
Thu, 7 May 2020 16:53:34 +0000 (18:53 +0200)
15 files changed:
1  2 
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/builder.ex
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
test/notification_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/object_validator_test.exs
test/web/activity_pub/side_effects_test.exs
test/web/activity_pub/transmogrifier/undo_handling_test.exs
test/web/activity_pub/transmogrifier_test.exs
test/web/common_api/common_api_test.exs
test/web/pleroma_api/controllers/pleroma_api_controller_test.exs

index fcc3ce7289b71497d681a992b2dd5d6fe88f55a7,5abeb94c601e9f8785be7b073e00509d2a736c0e..4955243ab3ac884606b8e0e17f9c5a572e2066f0
@@@ -356,56 -356,31 +356,6 @@@ defmodule Pleroma.Web.ActivityPub.Activ
      end
    end
  
-   @spec unreact_with_emoji(User.t(), String.t(), keyword()) ::
 -  @spec react_with_emoji(User.t(), Object.t(), String.t(), keyword()) ::
--          {:ok, Activity.t(), Object.t()} | {:error, any()}
-   def unreact_with_emoji(user, reaction_id, options \\ []) do
 -  def react_with_emoji(user, object, emoji, options \\ []) do
--    with {:ok, result} <-
-            Repo.transaction(fn -> do_unreact_with_emoji(user, reaction_id, options) end) do
 -           Repo.transaction(fn -> do_react_with_emoji(user, object, emoji, options) end) do
--      result
--    end
--  end
--
-   defp do_unreact_with_emoji(user, reaction_id, options) do
 -  defp do_react_with_emoji(user, object, emoji, options) do
--    with local <- Keyword.get(options, :local, true),
--         activity_id <- Keyword.get(options, :activity_id, nil),
-          user_ap_id <- user.ap_id,
-          %Activity{actor: ^user_ap_id} = reaction_activity <- Activity.get_by_ap_id(reaction_id),
-          object <- Object.normalize(reaction_activity),
-          unreact_data <- make_undo_data(user, reaction_activity, activity_id),
-          {:ok, activity} <- insert(unreact_data, local),
-          {:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object),
 -         true <- Pleroma.Emoji.is_unicode_emoji?(emoji),
 -         reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id),
 -         {:ok, activity} <- insert(reaction_data, local),
 -         {:ok, object} <- add_emoji_reaction_to_object(activity, object),
--         _ <- notify_and_stream(activity),
--         :ok <- maybe_federate(activity) do
--      {:ok, activity, object}
-     else
-       {:error, error} -> Repo.rollback(error)
-     end
-   end
-   @spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) ::
-           {:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
-   def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do
-     with {:ok, result} <-
-            Repo.transaction(fn -> do_unlike(actor, object, activity_id, local) end) do
-       result
-     end
-   end
-   defp do_unlike(actor, object, activity_id, local) do
-     with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
-          unlike_data <- make_unlike_data(actor, like_activity, activity_id),
-          {:ok, unlike_activity} <- insert(unlike_data, local),
-          {:ok, _activity} <- Repo.delete(like_activity),
-          {:ok, object} <- remove_like_from_object(like_activity, object),
-          _ <- notify_and_stream(unlike_activity),
-          :ok <- maybe_federate(unlike_activity) do
-       {:ok, unlike_activity, like_activity, object}
--    else
-       nil -> {:ok, object}
 -      false -> {:error, false}
--      {:error, error} -> Repo.rollback(error)
--    end
--  end
--
    @spec announce(User.t(), Object.t(), String.t() | nil, boolean(), boolean()) ::
            {:ok, Activity.t(), Object.t()} | {:error, any()}
    def announce(
index d130176cf745c8622191522c9e15a67e6a09d8f9,b0f447e28417a9deb34190084932af70bc8ae9d5..f6544d3f57c1c22c9e1b293dfc8396ac2baba4ec
@@@ -10,18 -10,19 +10,31 @@@ defmodule Pleroma.Web.ActivityPub.Build
    alias Pleroma.Web.ActivityPub.Utils
    alias Pleroma.Web.ActivityPub.Visibility
  
 +  @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
 +  def emoji_react(actor, object, emoji) do
 +    with {:ok, data, meta} <- like(actor, object) do
 +      data =
 +        data
 +        |> Map.put("content", emoji)
 +        |> Map.put("type", "EmojiReact")
 +
 +      {:ok, data, meta}
 +    end
 +  end
 +
+   @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
+   def undo(actor, object) do
+     {:ok,
+      %{
+        "id" => Utils.generate_activity_id(),
+        "actor" => actor.ap_id,
+        "type" => "Undo",
+        "object" => object.data["id"],
+        "to" => object.data["to"] || [],
+        "cc" => object.data["cc"] || []
+      }, []}
+   end
    @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
    def delete(actor, object_id) do
      object = Object.normalize(object_id, false)
index e51a8e0a824bbb5ff17b37751ca7e65a0a7cc614,4782cd8f3e3c37b196219c00e948f501e38698a8..549e5e761e7b5f83983861f3291e019b8fc9a4b9
@@@ -12,9 -12,9 +12,10 @@@ defmodule Pleroma.Web.ActivityPub.Objec
    alias Pleroma.Object
    alias Pleroma.User
    alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
 +  alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
    alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
    alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+   alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
  
    @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
    def validate(object, meta)
index 8e5586e88702df23795a0c507cbc224e5f3758ca,5049cb54ea2804ce21fd11854c366c3f2965f7d5..bfc2ab845d7aac444c7ce8b8a563e768a6f3cfd9
@@@ -25,18 -27,13 +27,25 @@@ defmodule Pleroma.Web.ActivityPub.SideE
      {:ok, object, meta}
    end
  
+   def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do
+     with undone_object <- Activity.get_by_ap_id(undone_object),
+          :ok <- handle_undoing(undone_object) do
+       {:ok, object, meta}
+     end
+   end
 +  # Tasks this handles:
 +  # - Add reaction to object
 +  # - Set up notification
 +  def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
 +    reacted_object = Object.get_by_ap_id(object.data["object"])
 +    Utils.add_emoji_reaction_to_object(object, reacted_object)
 +
 +    Notification.create_notifications(object)
 +
 +    {:ok, object, meta}
 +  end
 +
    # Tasks this handles:
    # - Delete and unpins the create activity
    # - Replace object with Tombstone
Simple merge
index 4b70af5a610b8b01cdb7e7cd6921550ed51b77b0,0a8a7119d16438d4acaa6b88de520d89ddc62228..0739cbfef025421e902fbbd8fcf429036ecab33d
@@@ -16,7 -16,7 +16,6 @@@ defmodule Pleroma.Web.ActivityPub.Activ
    alias Pleroma.Web.ActivityPub.Utils
    alias Pleroma.Web.AdminAPI.AccountView
    alias Pleroma.Web.CommonAPI
--  alias Pleroma.Web.Federator
  
    import ExUnit.CaptureLog
    import Mock
      end
    end
  
-   describe "unreacting to an object" do
 -  describe "react to an object" do
--    test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
--      Config.put([:instance, :federating], true)
--      user = insert(:user)
--      reactor = insert(:user)
--      {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
--      assert object = Object.normalize(activity)
--
-       {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "🔥")
 -      {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
--
--      assert called(Federator.publish(reaction_activity))
-       {:ok, unreaction_activity, _object} =
-         ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
-       assert called(Federator.publish(unreaction_activity))
--    end
--
-     test "adds an undo activity to the db" do
 -    test "adds an emoji reaction activity to the db" do
--      user = insert(:user)
--      reactor = insert(:user)
 -      third_user = insert(:user)
 -      fourth_user = insert(:user)
--      {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
--      assert object = Object.normalize(activity)
-       {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "🔥")
-       {:ok, unreaction_activity, _object} =
-         ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
-       assert unreaction_activity.actor == reactor.ap_id
-       assert unreaction_activity.data["object"] == reaction_activity.data["id"]
-       object = Object.get_by_ap_id(object.data["id"])
-       assert object.data["reaction_count"] == 0
-       assert object.data["reactions"] == []
-     end
-     test "reverts emoji unreact on error" do
-       [user, reactor] = insert_list(2, :user)
-       {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"})
-       object = Object.normalize(activity)
-       {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "😀")
--
-       with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
-         assert {:error, :reverted} =
-                  ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
-       end
 -      {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
--
-       object = Object.get_by_ap_id(object.data["id"])
 -      assert reaction_activity
--
 -      assert reaction_activity.data["actor"] == reactor.ap_id
 -      assert reaction_activity.data["type"] == "EmojiReact"
 -      assert reaction_activity.data["content"] == "🔥"
 -      assert reaction_activity.data["object"] == object.data["id"]
 -      assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]]
 -      assert reaction_activity.data["context"] == object.data["context"]
--      assert object.data["reaction_count"] == 1
-       assert object.data["reactions"] == [["😀", [reactor.ap_id]]]
-     end
-   end
-   describe "unliking" do
-     test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
-       Config.put([:instance, :federating], true)
 -      assert object.data["reactions"] == [["🔥", [reactor.ap_id]]]
--
-       note_activity = insert(:note_activity)
-       object = Object.normalize(note_activity)
-       user = insert(:user)
 -      {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(third_user, object, "☕")
--
-       {:ok, object} = ActivityPub.unlike(user, object)
-       refute called(Federator.publish())
 -      assert object.data["reaction_count"] == 2
 -      assert object.data["reactions"] == [["🔥", [reactor.ap_id]], ["☕", [third_user.ap_id]]]
--
-       {:ok, _like_activity} = CommonAPI.favorite(user, note_activity.id)
-       object = Object.get_by_id(object.id)
-       assert object.data["like_count"] == 1
 -      {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(fourth_user, object, "🔥")
--
-       {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
-       assert object.data["like_count"] == 0
 -      assert object.data["reaction_count"] == 3
--
-       assert called(Federator.publish(unlike_activity))
 -      assert object.data["reactions"] == [
 -               ["🔥", [fourth_user.ap_id, reactor.ap_id]],
 -               ["☕", [third_user.ap_id]]
 -             ]
--    end
--
-     test "reverts unliking on error" do
-       note_activity = insert(:note_activity)
-       user = insert(:user)
 -    test "reverts emoji reaction on error" do
 -      [user, reactor] = insert_list(2, :user)
--
-       {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
-       object = Object.normalize(note_activity)
-       assert object.data["like_count"] == 1
 -      {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"})
 -      object = Object.normalize(activity)
--
--      with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
-         assert {:error, :reverted} = ActivityPub.unlike(user, object)
 -        assert {:error, :reverted} = ActivityPub.react_with_emoji(reactor, object, "😀")
--      end
-       assert Object.get_by_ap_id(object.data["id"]) == object
-       assert object.data["like_count"] == 1
-       assert Activity.get_by_id(like_activity.id)
-     end
-     test "unliking a previously liked object" do
-       note_activity = insert(:note_activity)
-       object = Object.normalize(note_activity)
-       user = insert(:user)
-       # Unliking something that hasn't been liked does nothing
-       {:ok, object} = ActivityPub.unlike(user, object)
-       assert object.data["like_count"] == 0
-       {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
-       object = Object.get_by_id(object.id)
-       assert object.data["like_count"] == 1
-       {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
-       assert object.data["like_count"] == 0
--
-       assert Activity.get_by_id(like_activity.id) == nil
-       assert note_activity.actor in unlike_activity.recipients
 -      object = Object.get_by_ap_id(object.data["id"])
 -      refute object.data["reaction_count"]
 -      refute object.data["reactions"]
--    end
--  end
--
    describe "announcing an object" do
      test "adds an announce activity to the db" do
        note_activity = insert(:note_activity)
index 4cae5207703ae1a813e4e3934bf6212042d5a486,174be5ec61c2b137c6852e7878164a27e422d435..f382adf3e7e38ecdbae370507d4bb9da46ffb9fc
@@@ -10,46 -10,46 +10,86 @@@ defmodule Pleroma.Web.ActivityPub.Objec
  
    import Pleroma.Factory
  
 +  describe "EmojiReacts" do
 +    setup do
 +      user = insert(:user)
 +      {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"})
 +
 +      object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
 +
 +      {:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌")
 +
 +      %{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react}
 +    end
 +
 +    test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do
 +      assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, [])
 +    end
 +
 +    test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do
 +      without_content =
 +        valid_emoji_react
 +        |> Map.delete("content")
 +
 +      {:error, cng} = ObjectValidator.validate(without_content, [])
 +
 +      refute cng.valid?
 +      assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
 +    end
 +
 +    test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
 +      without_emoji_content =
 +        valid_emoji_react
 +        |> Map.put("content", "x")
 +
 +      {:error, cng} = ObjectValidator.validate(without_emoji_content, [])
 +
 +      refute cng.valid?
 +
 +      assert {:content, {"must be a single character emoji", []}} in cng.errors
 +    end
 +  end
 +
+   describe "Undos" do
+     setup do
+       user = insert(:user)
+       {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"})
+       {:ok, like} = CommonAPI.favorite(user, post_activity.id)
+       {:ok, valid_like_undo, []} = Builder.undo(user, like)
+       %{user: user, like: like, valid_like_undo: valid_like_undo}
+     end
+     test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do
+       assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, [])
+     end
+     test "it does not validate if the actor of the undo is not the actor of the object", %{
+       valid_like_undo: valid_like_undo
+     } do
+       other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
+       bad_actor =
+         valid_like_undo
+         |> Map.put("actor", other_user.ap_id)
+       {:error, cng} = ObjectValidator.validate(bad_actor, [])
+       assert {:actor, {"not the same as object actor", []}} in cng.errors
+     end
+     test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do
+       missing_object =
+         valid_like_undo
+         |> Map.put("object", "https://gensokyo.2hu/objects/1")
+       {:error, cng} = ObjectValidator.validate(missing_object, [])
+       assert {:object, {"can't find object", []}} in cng.errors
+       assert length(cng.errors) == 1
+     end
+   end
    describe "deletes" do
      setup do
        user = insert(:user)
index 404b129ea752e850efa3b79c047d512a31585f0b,aafc450d30879744dc69e12dea305e02d98fb1ef..b29a7a7be8dd4a3f9617d16b10d4b92cbd2749ab
@@@ -72,33 -72,106 +72,133 @@@ defmodule Pleroma.Web.ActivityPub.SideE
      end
    end
  
 -      {:ok, reaction, _} = CommonAPI.react_with_emoji(post.id, user, "👍")
 +  describe "EmojiReact objects" do
 +    setup do
 +      poster = insert(:user)
 +      user = insert(:user)
 +
 +      {:ok, post} = CommonAPI.post(poster, %{"status" => "hey"})
 +
 +      {:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌")
 +      {:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true)
 +
 +      %{emoji_react: emoji_react, user: user, poster: poster}
 +    end
 +
 +    test "adds the reaction to the object", %{emoji_react: emoji_react, user: user} do
 +      {:ok, emoji_react, _} = SideEffects.handle(emoji_react)
 +      object = Object.get_by_ap_id(emoji_react.data["object"])
 +
 +      assert object.data["reaction_count"] == 1
 +      assert ["👌", [user.ap_id]] in object.data["reactions"]
 +    end
 +
 +    test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
 +      {:ok, emoji_react, _} = SideEffects.handle(emoji_react)
 +      assert Repo.get_by(Notification, user_id: poster.id, activity_id: emoji_react.id)
 +    end
 +  end
 +
+   describe "Undo objects" do
+     setup do
+       poster = insert(:user)
+       user = insert(:user)
+       {:ok, post} = CommonAPI.post(poster, %{"status" => "hey"})
+       {:ok, like} = CommonAPI.favorite(user, post.id)
++      {:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍")
+       {:ok, announce, _} = CommonAPI.repeat(post.id, user)
+       {:ok, block} = ActivityPub.block(user, poster)
+       User.block(user, poster)
+       {:ok, undo_data, _meta} = Builder.undo(user, like)
+       {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true)
+       {:ok, undo_data, _meta} = Builder.undo(user, reaction)
+       {:ok, reaction_undo, _meta} = ActivityPub.persist(undo_data, local: true)
+       {:ok, undo_data, _meta} = Builder.undo(user, announce)
+       {:ok, announce_undo, _meta} = ActivityPub.persist(undo_data, local: true)
+       {:ok, undo_data, _meta} = Builder.undo(user, block)
+       {:ok, block_undo, _meta} = ActivityPub.persist(undo_data, local: true)
+       %{
+         like_undo: like_undo,
+         post: post,
+         like: like,
+         reaction_undo: reaction_undo,
+         reaction: reaction,
+         announce_undo: announce_undo,
+         announce: announce,
+         block_undo: block_undo,
+         block: block,
+         poster: poster,
+         user: user
+       }
+     end
+     test "deletes the original block", %{block_undo: block_undo, block: block} do
+       {:ok, _block_undo, _} = SideEffects.handle(block_undo)
+       refute Activity.get_by_id(block.id)
+     end
+     test "unblocks the blocked user", %{block_undo: block_undo, block: block} do
+       blocker = User.get_by_ap_id(block.data["actor"])
+       blocked = User.get_by_ap_id(block.data["object"])
+       {:ok, _block_undo, _} = SideEffects.handle(block_undo)
+       refute User.blocks?(blocker, blocked)
+     end
+     test "an announce undo removes the announce from the object", %{
+       announce_undo: announce_undo,
+       post: post
+     } do
+       {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
+       object = Object.get_by_ap_id(post.data["object"])
+       assert object.data["announcement_count"] == 0
+       assert object.data["announcements"] == []
+     end
+     test "deletes the original announce", %{announce_undo: announce_undo, announce: announce} do
+       {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
+       refute Activity.get_by_id(announce.id)
+     end
+     test "a reaction undo removes the reaction from the object", %{
+       reaction_undo: reaction_undo,
+       post: post
+     } do
+       {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
+       object = Object.get_by_ap_id(post.data["object"])
+       assert object.data["reaction_count"] == 0
+       assert object.data["reactions"] == []
+     end
+     test "deletes the original reaction", %{reaction_undo: reaction_undo, reaction: reaction} do
+       {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
+       refute Activity.get_by_id(reaction.id)
+     end
+     test "a like undo removes the like from the object", %{like_undo: like_undo, post: post} do
+       {:ok, _like_undo, _} = SideEffects.handle(like_undo)
+       object = Object.get_by_ap_id(post.data["object"])
+       assert object.data["like_count"] == 0
+       assert object.data["likes"] == []
+     end
+     test "deletes the original like", %{like_undo: like_undo, like: like} do
+       {:ok, _like_undo, _} = SideEffects.handle(like_undo)
+       refute Activity.get_by_id(like.id)
+     end
+   end
    describe "like objects" do
      setup do
        poster = insert(:user)
index 0000000000000000000000000000000000000000,6f5e61ac3f6ed3d107114bbc420acea219747d80..eaf58adf74f047e667179e08373adb1184431068
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,185 +1,185 @@@
 -    {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "👌")
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do
+   use Pleroma.DataCase
+   alias Pleroma.Activity
+   alias Pleroma.Object
+   alias Pleroma.User
+   alias Pleroma.Web.ActivityPub.Transmogrifier
+   alias Pleroma.Web.CommonAPI
+   import Pleroma.Factory
+   test "it works for incoming emoji reaction undos" do
+     user = insert(:user)
+     {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
++    {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, user, "👌")
+     data =
+       File.read!("test/fixtures/mastodon-undo-like.json")
+       |> Poison.decode!()
+       |> Map.put("object", reaction_activity.data["id"])
+       |> Map.put("actor", user.ap_id)
+     {:ok, activity} = Transmogrifier.handle_incoming(data)
+     assert activity.actor == user.ap_id
+     assert activity.data["id"] == data["id"]
+     assert activity.data["type"] == "Undo"
+   end
+   test "it returns an error for incoming unlikes wihout a like activity" do
+     user = insert(:user)
+     {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
+     data =
+       File.read!("test/fixtures/mastodon-undo-like.json")
+       |> Poison.decode!()
+       |> Map.put("object", activity.data["object"])
+     assert Transmogrifier.handle_incoming(data) == :error
+   end
+   test "it works for incoming unlikes with an existing like activity" do
+     user = insert(:user)
+     {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
+     like_data =
+       File.read!("test/fixtures/mastodon-like.json")
+       |> Poison.decode!()
+       |> Map.put("object", activity.data["object"])
+     _liker = insert(:user, ap_id: like_data["actor"], local: false)
+     {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
+     data =
+       File.read!("test/fixtures/mastodon-undo-like.json")
+       |> Poison.decode!()
+       |> Map.put("object", like_data)
+       |> Map.put("actor", like_data["actor"])
+     {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+     assert data["actor"] == "http://mastodon.example.org/users/admin"
+     assert data["type"] == "Undo"
+     assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
+     assert data["object"] == "http://mastodon.example.org/users/admin#likes/2"
+     note = Object.get_by_ap_id(like_data["object"])
+     assert note.data["like_count"] == 0
+     assert note.data["likes"] == []
+   end
+   test "it works for incoming unlikes with an existing like activity and a compact object" do
+     user = insert(:user)
+     {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
+     like_data =
+       File.read!("test/fixtures/mastodon-like.json")
+       |> Poison.decode!()
+       |> Map.put("object", activity.data["object"])
+     _liker = insert(:user, ap_id: like_data["actor"], local: false)
+     {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
+     data =
+       File.read!("test/fixtures/mastodon-undo-like.json")
+       |> Poison.decode!()
+       |> Map.put("object", like_data["id"])
+       |> Map.put("actor", like_data["actor"])
+     {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+     assert data["actor"] == "http://mastodon.example.org/users/admin"
+     assert data["type"] == "Undo"
+     assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
+     assert data["object"] == "http://mastodon.example.org/users/admin#likes/2"
+   end
+   test "it works for incoming unannounces with an existing notice" do
+     user = insert(:user)
+     {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
+     announce_data =
+       File.read!("test/fixtures/mastodon-announce.json")
+       |> Poison.decode!()
+       |> Map.put("object", activity.data["object"])
+     _announcer = insert(:user, ap_id: announce_data["actor"], local: false)
+     {:ok, %Activity{data: announce_data, local: false}} =
+       Transmogrifier.handle_incoming(announce_data)
+     data =
+       File.read!("test/fixtures/mastodon-undo-announce.json")
+       |> Poison.decode!()
+       |> Map.put("object", announce_data)
+       |> Map.put("actor", announce_data["actor"])
+     {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+     assert data["type"] == "Undo"
+     assert data["object"] ==
+              "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
+   end
+   test "it works for incomming unfollows with an existing follow" do
+     user = insert(:user)
+     follow_data =
+       File.read!("test/fixtures/mastodon-follow-activity.json")
+       |> Poison.decode!()
+       |> Map.put("object", user.ap_id)
+     _follower = insert(:user, ap_id: follow_data["actor"], local: false)
+     {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
+     data =
+       File.read!("test/fixtures/mastodon-unfollow-activity.json")
+       |> Poison.decode!()
+       |> Map.put("object", follow_data)
+     {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+     assert data["type"] == "Undo"
+     assert data["object"]["type"] == "Follow"
+     assert data["object"]["object"] == user.ap_id
+     assert data["actor"] == "http://mastodon.example.org/users/admin"
+     refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
+   end
+   test "it works for incoming unblocks with an existing block" do
+     user = insert(:user)
+     block_data =
+       File.read!("test/fixtures/mastodon-block-activity.json")
+       |> Poison.decode!()
+       |> Map.put("object", user.ap_id)
+     _blocker = insert(:user, ap_id: block_data["actor"], local: false)
+     {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
+     data =
+       File.read!("test/fixtures/mastodon-unblock-activity.json")
+       |> Poison.decode!()
+       |> Map.put("object", block_data)
+     {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+     assert data["type"] == "Undo"
+     assert data["object"] == block_data["id"]
+     blocker = User.get_cached_by_ap_id(data["actor"])
+     refute User.blocks?(blocker, user)
+   end
+ end
index 336ddb32303c5ec259016f11faec25f529828516,4fd6c8b00298aaab70267a9694110f7ddb34a85b..14c0f57ae86d98614beb8e97c438a23fd234c2bd
@@@ -325,85 -325,41 +325,122 @@@ defmodule Pleroma.Web.ActivityPub.Trans
        assert object_data["cc"] == to
      end
  
-       assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
 +    test "it works for incoming emoji reaction undos" do
 +      user = insert(:user)
 +
 +      {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
 +      {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, user, "👌")
 +
 +      data =
 +        File.read!("test/fixtures/mastodon-undo-like.json")
 +        |> Poison.decode!()
 +        |> Map.put("object", reaction_activity.data["id"])
 +        |> Map.put("actor", user.ap_id)
 +
 +      {:ok, activity} = Transmogrifier.handle_incoming(data)
 +
 +      assert activity.actor == user.ap_id
 +      assert activity.data["id"] == data["id"]
 +      assert activity.data["type"] == "Undo"
 +    end
 +
 +    test "it returns an error for incoming unlikes wihout a like activity" do
 +      user = insert(:user)
 +      {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
 +
 +      data =
 +        File.read!("test/fixtures/mastodon-undo-like.json")
 +        |> Poison.decode!()
 +        |> Map.put("object", activity.data["object"])
 +
 +      assert Transmogrifier.handle_incoming(data) == :error
 +    end
 +
 +    test "it works for incoming unlikes with an existing like activity" do
 +      user = insert(:user)
 +      {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
 +
 +      like_data =
 +        File.read!("test/fixtures/mastodon-like.json")
 +        |> Poison.decode!()
 +        |> Map.put("object", activity.data["object"])
 +
 +      {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
 +
 +      data =
 +        File.read!("test/fixtures/mastodon-undo-like.json")
 +        |> Poison.decode!()
 +        |> Map.put("object", like_data)
 +        |> Map.put("actor", like_data["actor"])
 +
 +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
 +
 +      assert data["actor"] == "http://mastodon.example.org/users/admin"
 +      assert data["type"] == "Undo"
 +      assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
-       assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
++      assert data["object"] == "http://mastodon.example.org/users/admin#likes/2"
 +    end
 +
 +    test "it works for incoming unlikes with an existing like activity and a compact object" do
 +      user = insert(:user)
 +      {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
 +
 +      like_data =
 +        File.read!("test/fixtures/mastodon-like.json")
 +        |> Poison.decode!()
 +        |> Map.put("object", activity.data["object"])
 +
 +      {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
 +
 +      data =
 +        File.read!("test/fixtures/mastodon-undo-like.json")
 +        |> Poison.decode!()
 +        |> Map.put("object", like_data["id"])
 +        |> Map.put("actor", like_data["actor"])
 +
 +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
 +
 +      assert data["actor"] == "http://mastodon.example.org/users/admin"
 +      assert data["type"] == "Undo"
 +      assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
 -      assert :error = Transmogrifier.handle_incoming(data)
++      assert data["object"] == "http://mastodon.example.org/users/admin#likes/2"
++    end
++
+     test "it works for incoming emoji reactions" do
+       user = insert(:user)
+       {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
+       data =
+         File.read!("test/fixtures/emoji-reaction.json")
+         |> Poison.decode!()
+         |> Map.put("object", activity.data["object"])
+       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+       assert data["actor"] == "http://mastodon.example.org/users/admin"
+       assert data["type"] == "EmojiReact"
+       assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
+       assert data["object"] == activity.data["object"]
+       assert data["content"] == "👌"
+     end
+     test "it reject invalid emoji reactions" do
+       user = insert(:user)
+       {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
+       data =
+         File.read!("test/fixtures/emoji-reaction-too-long.json")
+         |> Poison.decode!()
+         |> Map.put("object", activity.data["object"])
 -      assert :error = Transmogrifier.handle_incoming(data)
++      assert {:error, _} = Transmogrifier.handle_incoming(data)
+       data =
+         File.read!("test/fixtures/emoji-reaction-no-emoji.json")
+         |> Poison.decode!()
+         |> Map.put("object", activity.data["object"])
++      assert {:error, _} = Transmogrifier.handle_incoming(data)
      end
  
      test "it works for incoming announces" do
index e5f7e3ef8afeeb6649f83fb4fdcd9cc7c451de26,9a37d18875e8146a51d5ac04653ee481a92ae3df..2fd17a1b896992d785a47a03079dacc8a99096ad
@@@ -373,9 -373,9 +373,9 @@@ defmodule Pleroma.Web.CommonAPITest d
        other_user = insert(:user)
  
        {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
 -      {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍")
 +      {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
  
-       {:ok, unreaction, _} = CommonAPI.unreact_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"]
index 593aa92b253713dc28c50e68551d109eb9ebd8e5,299dbad41e8d6e5751c9cc5a4113e9aaf06822d4..43f1b154dd7dcbaf306efd346358b8bb1fa42d06
@@@ -41,7 -43,9 +43,9 @@@ defmodule Pleroma.Web.PleromaAPI.Plerom
      other_user = insert(:user)
  
      {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
-     {:ok, activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
 -    {:ok, _reaction, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
++    {:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
+     ObanHelpers.perform_all()
  
      result =
        conn