Merge branch 'feature/undo-validator-reduced' into 'develop'
authorrinpatch <rinpatch@sdf.org>
Thu, 7 May 2020 14:28:10 +0000 (14:28 +0000)
committerrinpatch <rinpatch@sdf.org>
Thu, 7 May 2020 14:28:10 +0000 (14:28 +0000)
Move Undo (except undo follow) to the common pipeline

See merge request pleroma/pleroma!2489

22 files changed:
lib/pleroma/user.ex
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/object_validators/common_validations.ex
lib/pleroma/web/activity_pub/object_validators/undo_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/controllers/status_controller.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 [new file with mode: 0644]
test/web/activity_pub/transmogrifier_test.exs
test/web/activity_pub/utils_test.exs
test/web/common_api/common_api_test.exs
test/web/pleroma_api/controllers/pleroma_api_controller_test.exs

index a6f51f0bec630f90a5391b260dc6b43a1f981a51..2a6a23fecb69c8c1938137b508266a93cb051824 100644 (file)
@@ -1557,23 +1557,13 @@ defmodule Pleroma.User do
   defp delete_activity(%{data: %{"type" => "Create", "object" => object}}, user) do
     {:ok, delete_data, _} = Builder.delete(user, object)
 
-    Pipeline.common_pipeline(delete_data, local: true)
+    Pipeline.common_pipeline(delete_data, local: user.local)
   end
 
-  defp delete_activity(%{data: %{"type" => "Like"}} = activity, _user) do
-    object = Object.normalize(activity)
-
-    activity.actor
-    |> get_cached_by_ap_id()
-    |> ActivityPub.unlike(object)
-  end
-
-  defp delete_activity(%{data: %{"type" => "Announce"}} = activity, _user) do
-    object = Object.normalize(activity)
-
-    activity.actor
-    |> get_cached_by_ap_id()
-    |> ActivityPub.unannounce(object)
+  defp delete_activity(%{data: %{"type" => type}} = activity, user)
+       when type in ["Like", "Announce"] do
+    {:ok, undo, _} = Builder.undo(user, activity)
+    Pipeline.common_pipeline(undo, local: user.local)
   end
 
   defp delete_activity(_activity, _user), do: "Doing nothing"
index 8baaf97ac822348975ae8ec7fb2a36b880cf93ee..5abeb94c601e9f8785be7b073e00509d2a736c0e 100644 (file)
@@ -381,56 +381,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  @spec unreact_with_emoji(User.t(), String.t(), keyword()) ::
-          {:ok, Activity.t(), Object.t()} | {:error, any()}
-  def unreact_with_emoji(user, reaction_id, options \\ []) do
-    with {:ok, result} <-
-           Repo.transaction(fn -> do_unreact_with_emoji(user, reaction_id, options) end) do
-      result
-    end
-  end
-
-  defp do_unreact_with_emoji(user, reaction_id, 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),
-         _ <- 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}
-      {: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(
@@ -461,35 +411,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  @spec unannounce(User.t(), Object.t(), String.t() | nil, boolean()) ::
-          {:ok, Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
-  def unannounce(
-        %User{} = actor,
-        %Object{} = object,
-        activity_id \\ nil,
-        local \\ true
-      ) do
-    with {:ok, result} <-
-           Repo.transaction(fn -> do_unannounce(actor, object, activity_id, local) end) do
-      result
-    end
-  end
-
-  defp do_unannounce(actor, object, activity_id, local) do
-    with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
-         unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
-         {:ok, unannounce_activity} <- insert(unannounce_data, local),
-         _ <- notify_and_stream(unannounce_activity),
-         :ok <- maybe_federate(unannounce_activity),
-         {:ok, _activity} <- Repo.delete(announce_activity),
-         {:ok, object} <- remove_announce_from_object(announce_activity, object) do
-      {:ok, unannounce_activity, object}
-    else
-      nil -> {:ok, object}
-      {:error, error} -> Repo.rollback(error)
-    end
-  end
-
   @spec follow(User.t(), User.t(), String.t() | nil, boolean()) ::
           {:ok, Activity.t()} | {:error, any()}
   def follow(follower, followed, activity_id \\ nil, local \\ true) do
@@ -562,28 +483,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  @spec unblock(User.t(), User.t(), String.t() | nil, boolean()) ::
-          {:ok, Activity.t()} | {:error, any()} | nil
-  def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
-    with {:ok, result} <-
-           Repo.transaction(fn -> do_unblock(blocker, blocked, activity_id, local) end) do
-      result
-    end
-  end
-
-  defp do_unblock(blocker, blocked, activity_id, local) do
-    with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
-         unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
-         {:ok, activity} <- insert(unblock_data, local),
-         _ <- notify_and_stream(activity),
-         :ok <- maybe_federate(activity) do
-      {:ok, activity}
-    else
-      nil -> nil
-      {:error, error} -> Repo.rollback(error)
-    end
-  end
-
   @spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
   def flag(
         %{
index 1345a3a3e2cf2430e42990f0d8b758f1d7a20266..b0f447e28417a9deb34190084932af70bc8ae9d5 100644 (file)
@@ -10,6 +10,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
 
+  @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 479f922f51296a5a55e8bf0fb4c8c2937f9320d3..4782cd8f3e3c37b196219c00e948f501e38698a8 100644 (file)
@@ -14,10 +14,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
   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)
 
+  def validate(%{"type" => "Undo"} = object, meta) do
+    with {:ok, object} <-
+           object
+           |> UndoValidator.cast_and_validate()
+           |> Ecto.Changeset.apply_action(:insert) do
+      object = stringify_keys(object)
+      {:ok, object, meta}
+    end
+  end
+
   def validate(%{"type" => "Delete"} = object, meta) do
     with cng <- DeleteValidator.cast_and_validate(object),
          do_not_federate <- DeleteValidator.do_not_federate?(cng),
index 4e6ee2034168eb359e455bb1a4438d0d283dea4f..aeef31945dab440ec462952a35cac3be8126bbe6 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
   import Ecto.Changeset
 
+  alias Pleroma.Activity
   alias Pleroma.Object
   alias Pleroma.User
 
@@ -47,7 +48,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
 
     cng
     |> validate_change(field_name, fn field_name, object_id ->
-      object = Object.get_cached_by_ap_id(object_id)
+      object = Object.get_cached_by_ap_id(object_id) || Activity.get_by_ap_id(object_id)
 
       cond do
         !object ->
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
new file mode 100644 (file)
index 0000000..d0ba418
--- /dev/null
@@ -0,0 +1,62 @@
+# 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.ObjectValidators.UndoValidator do
+  use Ecto.Schema
+
+  alias Pleroma.Activity
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+  import Ecto.Changeset
+  import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+  @primary_key false
+
+  embedded_schema do
+    field(:id, Types.ObjectID, primary_key: true)
+    field(:type, :string)
+    field(:object, Types.ObjectID)
+    field(:actor, Types.ObjectID)
+    field(:to, {:array, :string}, default: [])
+    field(:cc, {:array, :string}, default: [])
+  end
+
+  def cast_and_validate(data) do
+    data
+    |> cast_data()
+    |> validate_data()
+  end
+
+  def cast_data(data) do
+    %__MODULE__{}
+    |> changeset(data)
+  end
+
+  def changeset(struct, data) do
+    struct
+    |> cast(data, __schema__(:fields))
+  end
+
+  def validate_data(data_cng) do
+    data_cng
+    |> validate_inclusion(:type, ["Undo"])
+    |> validate_required([:id, :type, :object, :actor, :to, :cc])
+    |> validate_actor_presence()
+    |> validate_object_presence()
+    |> validate_undo_rights()
+  end
+
+  def validate_undo_rights(cng) do
+    actor = get_field(cng, :actor)
+    object = get_field(cng, :object)
+
+    with %Activity{data: %{"actor" => object_actor}} <- Activity.get_by_ap_id(object),
+         true <- object_actor != actor do
+      cng
+      |> add_error(:actor, "not the same as object actor")
+    else
+      _ -> cng
+    end
+  end
+end
index 7b53abeafc61de0f69bfef89b81c14f8265e8aaf..5049cb54ea2804ce21fd11854c366c3f2965f7d5 100644 (file)
@@ -5,8 +5,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
   liked object, a `Follow` activity will add the user to the follower
   collection, and so on.
   """
+  alias Pleroma.Activity
   alias Pleroma.Notification
   alias Pleroma.Object
+  alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
@@ -25,6 +27,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
     {: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:
   # - Delete and unpins the create activity
   # - Replace object with Tombstone
@@ -72,4 +81,41 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
   def handle(object, meta) do
     {:ok, object, meta}
   end
+
+  def handle_undoing(%{data: %{"type" => "Like"}} = object) do
+    with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
+         {:ok, _} <- Utils.remove_like_from_object(object, liked_object),
+         {:ok, _} <- Repo.delete(object) do
+      :ok
+    end
+  end
+
+  def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
+    with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
+         {:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
+         {:ok, _} <- Repo.delete(object) do
+      :ok
+    end
+  end
+
+  def handle_undoing(%{data: %{"type" => "Announce"}} = object) do
+    with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
+         {:ok, _} <- Utils.remove_announce_from_object(object, liked_object),
+         {:ok, _} <- Repo.delete(object) do
+      :ok
+    end
+  end
+
+  def handle_undoing(
+        %{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = object
+      ) do
+    with %User{} = blocker <- User.get_cached_by_ap_id(blocker),
+         %User{} = blocked <- User.get_cached_by_ap_id(blocked),
+         {:ok, _} <- User.unblock(blocker, blocked),
+         {:ok, _} <- Repo.delete(object) do
+      :ok
+    end
+  end
+
+  def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
 end
index 0e4e7261b1177caebb5b2dc630fbe02ea8a1b8f3..26b5bda0e026f0e4fd71bbec3c911bf2627f2c5d 100644 (file)
@@ -744,25 +744,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  def handle_incoming(
-        %{
-          "type" => "Undo",
-          "object" => %{"type" => "Announce", "object" => object_id},
-          "actor" => _actor,
-          "id" => id
-        } = data,
-        _options
-      ) do
-    with actor <- Containment.get_actor(data),
-         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
-         {:ok, object} <- get_obj_helper(object_id),
-         {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
-      {:ok, activity}
-    else
-      _e -> :error
-    end
-  end
-
   def handle_incoming(
         %{
           "type" => "Undo",
@@ -785,39 +766,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(
         %{
           "type" => "Undo",
-          "object" => %{"type" => "EmojiReact", "id" => reaction_activity_id},
-          "actor" => _actor,
-          "id" => id
+          "object" => %{"type" => type}
         } = data,
         _options
-      ) do
-    with actor <- Containment.get_actor(data),
-         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
-         {:ok, activity, _} <-
-           ActivityPub.unreact_with_emoji(actor, reaction_activity_id,
-             activity_id: id,
-             local: false
-           ) do
+      )
+      when type in ["Like", "EmojiReact", "Announce", "Block"] do
+    with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
       {:ok, activity}
-    else
-      _e -> :error
     end
   end
 
+  # For Undos that don't have the complete object attached, try to find it in our database.
   def handle_incoming(
         %{
           "type" => "Undo",
-          "object" => %{"type" => "Block", "object" => blocked},
-          "actor" => blocker,
-          "id" => id
-        } = _data,
-        _options
-      ) do
-    with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
-         {:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
-         {:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
-      User.unblock(blocker, blocked)
-      {:ok, activity}
+          "object" => object
+        } = activity,
+        options
+      )
+      when is_binary(object) do
+    with %Activity{data: data} <- Activity.get_by_ap_id(object) do
+      activity
+      |> Map.put("object", data)
+      |> handle_incoming(options)
     else
       _e -> :error
     end
@@ -838,43 +809,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  def handle_incoming(
-        %{
-          "type" => "Undo",
-          "object" => %{"type" => "Like", "object" => object_id},
-          "actor" => _actor,
-          "id" => id
-        } = data,
-        _options
-      ) do
-    with actor <- Containment.get_actor(data),
-         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
-         {:ok, object} <- get_obj_helper(object_id),
-         {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
-      {:ok, activity}
-    else
-      _e -> :error
-    end
-  end
-
-  # For Undos that don't have the complete object attached, try to find it in our database.
-  def handle_incoming(
-        %{
-          "type" => "Undo",
-          "object" => object
-        } = activity,
-        options
-      )
-      when is_binary(object) do
-    with %Activity{data: data} <- Activity.get_by_ap_id(object) do
-      activity
-      |> Map.put("object", data)
-      |> handle_incoming(options)
-    else
-      _e -> :error
-    end
-  end
-
   def handle_incoming(
         %{
           "type" => "Move",
index 1a3b0b3c12d03bf52e2afc86521b35830d444971..09b80fa576520823ed75cd84618675622a2cced0 100644 (file)
@@ -562,45 +562,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     |> maybe_put("id", activity_id)
   end
 
-  @doc """
-  Make unannounce activity data for the given actor and object
-  """
-  def make_unannounce_data(
-        %User{ap_id: ap_id} = user,
-        %Activity{data: %{"context" => context, "object" => object}} = activity,
-        activity_id
-      ) do
-    object = Object.normalize(object)
-
-    %{
-      "type" => "Undo",
-      "actor" => ap_id,
-      "object" => activity.data,
-      "to" => [user.follower_address, object.data["actor"]],
-      "cc" => [Pleroma.Constants.as_public()],
-      "context" => context
-    }
-    |> maybe_put("id", activity_id)
-  end
-
-  def make_unlike_data(
-        %User{ap_id: ap_id} = user,
-        %Activity{data: %{"context" => context, "object" => object}} = activity,
-        activity_id
-      ) do
-    object = Object.normalize(object)
-
-    %{
-      "type" => "Undo",
-      "actor" => ap_id,
-      "object" => activity.data,
-      "to" => [user.follower_address, object.data["actor"]],
-      "cc" => [Pleroma.Constants.as_public()],
-      "context" => context
-    }
-    |> maybe_put("id", activity_id)
-  end
-
   def make_undo_data(
         %User{ap_id: actor, follower_address: follower_address},
         %Activity{
@@ -688,16 +649,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     |> maybe_put("id", activity_id)
   end
 
-  def make_unblock_data(blocker, blocked, block_activity, activity_id) do
-    %{
-      "type" => "Undo",
-      "actor" => blocker.ap_id,
-      "to" => [blocked.ap_id],
-      "object" => block_activity.data
-    }
-    |> maybe_put("id", activity_id)
-  end
-
   #### Create-related helpers
 
   def make_create_data(params, additional) do
index 986e8d3f8bb8a6cc8ac6d3ca2f8a8a1553ea200c..1e6bbbab76a145ddc4fdaf4a88cd236fa594878b 100644 (file)
@@ -24,6 +24,14 @@ defmodule Pleroma.Web.CommonAPI do
   require Pleroma.Constants
   require Logger
 
+  def unblock(blocker, blocked) do
+    with %Activity{} = block <- Utils.fetch_latest_block(blocker, blocked),
+         {:ok, unblock_data, _} <- Builder.undo(blocker, block),
+         {:ok, unblock, _} <- Pipeline.common_pipeline(unblock_data, local: true) do
+      {:ok, unblock}
+    end
+  end
+
   def follow(follower, followed) do
     timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
 
@@ -107,9 +115,12 @@ defmodule Pleroma.Web.CommonAPI do
 
   def unrepeat(id, user) do
     with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
-           {:find_activity, Activity.get_by_id(id)} do
-      object = Object.normalize(activity)
-      ActivityPub.unannounce(user, object)
+           {:find_activity, Activity.get_by_id(id)},
+         %Object{} = note <- Object.normalize(activity, false),
+         %Activity{} = announce <- Utils.get_existing_announce(user.ap_id, note),
+         {:ok, undo, _} <- Builder.undo(user, announce),
+         {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
+      {:ok, activity}
     else
       {:find_activity, _} -> {:error, :not_found}
       _ -> {:error, dgettext("errors", "Could not unrepeat")}
@@ -166,9 +177,12 @@ defmodule Pleroma.Web.CommonAPI do
 
   def unfavorite(id, user) do
     with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
-           {:find_activity, Activity.get_by_id(id)} do
-      object = Object.normalize(activity)
-      ActivityPub.unlike(user, object)
+           {:find_activity, Activity.get_by_id(id)},
+         %Object{} = note <- Object.normalize(activity, false),
+         %Activity{} = like <- Utils.get_existing_like(user.ap_id, note),
+         {:ok, undo, _} <- Builder.undo(user, like),
+         {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
+      {:ok, activity}
     else
       {:find_activity, _} -> {:error, :not_found}
       _ -> {:error, dgettext("errors", "Could not unfavorite")}
@@ -186,8 +200,10 @@ defmodule Pleroma.Web.CommonAPI do
   end
 
   def unreact_with_emoji(id, user, emoji) do
-    with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji) do
-      ActivityPub.unreact_with_emoji(user, reaction_activity.data["id"])
+    with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
+         {:ok, undo, _} <- Builder.undo(user, reaction_activity),
+         {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
+      {:ok, activity}
     else
       _ ->
         {:error, dgettext("errors", "Could not remove reaction emoji")}
index 8458cbdd5f39f5af27cc7eced2da8a928af95ccd..b9ed2d7b27e3ef34160c26503277b46f898e2d66 100644 (file)
@@ -356,8 +356,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "POST /api/v1/accounts/:id/unblock"
   def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
-    with {:ok, _user_block} <- User.unblock(blocker, blocked),
-         {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
+    with {:ok, _activity} <- CommonAPI.unblock(blocker, blocked) do
       render(conn, "relationship.json", user: blocker, target: blocked)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
index 9eea2e9eb1b4fd65cec5ceb5787d4317ca00f800..12e3ba15e22a1755a7a4428e41d66f4cb05c22a0 100644 (file)
@@ -206,9 +206,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "POST /api/v1/statuses/:id/unreblog"
-  def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
+  def unreblog(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
+    with {:ok, _unannounce} <- CommonAPI.unrepeat(activity_id, user),
+         %Activity{} = activity <- Activity.get_by_id(activity_id) do
       try_render(conn, "show.json", %{activity: activity, for: user, as: :activity})
     end
   end
@@ -222,9 +222,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "POST /api/v1/statuses/:id/unfavourite"
-  def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
+  def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
+    with {:ok, _unfav} <- CommonAPI.unfavorite(activity_id, user),
+         %Activity{} = activity <- Activity.get_by_id(activity_id) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
     end
   end
index 1bdb3aa4dcc531c00a24164f388af579a34a5411..4aa5c1dd866c0906414eedbd8bb8a06510571d34 100644 (file)
@@ -98,7 +98,8 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
         "id" => activity_id,
         "emoji" => emoji
       }) do
-    with {:ok, _activity, _object} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji),
+    with {:ok, _activity} <-
+           CommonAPI.unreact_with_emoji(activity_id, user, emoji),
          activity <- Activity.get_by_id(activity_id) do
       conn
       |> put_view(StatusView)
index 5c85f3368ba05376ecdecb20ead42b2e6128864a..7f359711f940a84799a7cdcca95b6bb8f17b234e 100644 (file)
@@ -728,7 +728,7 @@ defmodule Pleroma.NotificationTest do
 
       assert length(Notification.for_user(user)) == 1
 
-      {:ok, _, _, _} = CommonAPI.unfavorite(activity.id, other_user)
+      {:ok, _} = CommonAPI.unfavorite(activity.id, other_user)
 
       assert Enum.empty?(Notification.for_user(user))
     end
@@ -762,7 +762,7 @@ defmodule Pleroma.NotificationTest do
 
       assert length(Notification.for_user(user)) == 1
 
-      {:ok, _, _} = CommonAPI.unrepeat(activity.id, other_user)
+      {:ok, _} = CommonAPI.unrepeat(activity.id, other_user)
 
       assert Enum.empty?(Notification.for_user(user))
     end
index 4dc9c0f0a8b72ab81ac5153f34c1a276bc09dbdf..0a8a7119d16438d4acaa6b88de520d89ddc62228 100644 (file)
@@ -939,122 +939,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     end
   end
 
-  describe "unreacting 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, _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
-      user = insert(:user)
-      reactor = insert(:user)
-      {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"})
-      assert object = Object.normalize(activity)
-
-      {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥")
-
-      {: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, _object} = ActivityPub.react_with_emoji(reactor, object, "😀")
-
-      with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
-        assert {:error, :reverted} =
-                 ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"])
-      end
-
-      object = Object.get_by_ap_id(object.data["id"])
-
-      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)
-
-      note_activity = insert(:note_activity)
-      object = Object.normalize(note_activity)
-      user = insert(:user)
-
-      {:ok, object} = ActivityPub.unlike(user, object)
-      refute called(Federator.publish())
-
-      {: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 called(Federator.publish(unlike_activity))
-    end
-
-    test "reverts unliking on error" do
-      note_activity = insert(:note_activity)
-      user = insert(:user)
-
-      {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
-      object = Object.normalize(note_activity)
-      assert object.data["like_count"] == 1
-
-      with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
-        assert {:error, :reverted} = ActivityPub.unlike(user, 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
-    end
-  end
-
   describe "announcing an object" do
     test "adds an announce activity to the db" do
       note_activity = insert(:note_activity)
@@ -1124,52 +1008,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     end
   end
 
-  describe "unannouncing an object" do
-    test "unannouncing a previously announced object" do
-      note_activity = insert(:note_activity)
-      object = Object.normalize(note_activity)
-      user = insert(:user)
-
-      # Unannouncing an object that is not announced does nothing
-      {:ok, object} = ActivityPub.unannounce(user, object)
-      refute object.data["announcement_count"]
-
-      {:ok, announce_activity, object} = ActivityPub.announce(user, object)
-      assert object.data["announcement_count"] == 1
-
-      {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
-      assert object.data["announcement_count"] == 0
-
-      assert unannounce_activity.data["to"] == [
-               User.ap_followers(user),
-               object.data["actor"]
-             ]
-
-      assert unannounce_activity.data["type"] == "Undo"
-      assert unannounce_activity.data["object"] == announce_activity.data
-      assert unannounce_activity.data["actor"] == user.ap_id
-      assert unannounce_activity.data["context"] == announce_activity.data["context"]
-
-      assert Activity.get_by_id(announce_activity.id) == nil
-    end
-
-    test "reverts unannouncing on error" do
-      note_activity = insert(:note_activity)
-      object = Object.normalize(note_activity)
-      user = insert(:user)
-
-      {:ok, _announce_activity, object} = ActivityPub.announce(user, object)
-      assert object.data["announcement_count"] == 1
-
-      with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
-        assert {:error, :reverted} = ActivityPub.unannounce(user, object)
-      end
-
-      object = Object.get_by_ap_id(object.data["id"])
-      assert object.data["announcement_count"] == 1
-    end
-  end
-
   describe "uploading files" do
     test "copies the file to the configured folder" do
       file = %Plug.Upload{
@@ -1276,7 +1114,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     end
   end
 
-  describe "blocking / unblocking" do
+  describe "blocking" do
     test "reverts block activity on error" do
       [blocker, blocked] = insert_list(2, :user)
 
@@ -1298,38 +1136,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert activity.data["actor"] == blocker.ap_id
       assert activity.data["object"] == blocked.ap_id
     end
-
-    test "reverts unblock activity on error" do
-      [blocker, blocked] = insert_list(2, :user)
-      {:ok, block_activity} = ActivityPub.block(blocker, blocked)
-
-      with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
-        assert {:error, :reverted} = ActivityPub.unblock(blocker, blocked)
-      end
-
-      assert block_activity.data["type"] == "Block"
-      assert block_activity.data["actor"] == blocker.ap_id
-
-      assert Repo.aggregate(Activity, :count, :id) == 1
-      assert Repo.aggregate(Object, :count, :id) == 1
-    end
-
-    test "creates an undo activity for the last block" do
-      blocker = insert(:user)
-      blocked = insert(:user)
-
-      {:ok, block_activity} = ActivityPub.block(blocker, blocked)
-      {:ok, activity} = ActivityPub.unblock(blocker, blocked)
-
-      assert activity.data["type"] == "Undo"
-      assert activity.data["actor"] == blocker.ap_id
-
-      embedded_object = activity.data["object"]
-      assert is_map(embedded_object)
-      assert embedded_object["type"] == "Block"
-      assert embedded_object["object"] == blocked.ap_id
-      assert embedded_object["id"] == block_activity.data["id"]
-    end
   end
 
   describe "timeline post-processing" do
index 744c46781d33dab03549624f1b3db52873eba764..174be5ec61c2b137c6852e7878164a27e422d435 100644 (file)
@@ -10,6 +10,46 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
 
   import Pleroma.Factory
 
+  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 a9598d7b36ecbdc2c21507c177fd7aa2a4b52710..aafc450d30879744dc69e12dea305e02d98fb1ef 100644 (file)
@@ -72,6 +72,106 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
     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)
diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/web/activity_pub/transmogrifier/undo_handling_test.exs
new file mode 100644 (file)
index 0000000..6f5e61a
--- /dev/null
@@ -0,0 +1,185 @@
+# 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, _object} = 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 6d43c3365e49daa4d596c4635c486fad66d3364e..4fd6c8b00298aaab70267a9694110f7ddb34a85b 100644 (file)
@@ -362,87 +362,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert :error = Transmogrifier.handle_incoming(data)
     end
 
-    test "it works for incoming emoji reaction undos" do
-      user = insert(:user)
-
-      {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
-      {:ok, reaction_activity, _object} = 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"
-    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 data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
-    end
-
     test "it works for incoming announces" do
       data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
 
@@ -766,35 +685,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert user.locked == true
     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"])
-
-      {: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 object_data = data["object"]
-      assert object_data["type"] == "Announce"
-      assert object_data["object"] == activity.data["object"]
-
-      assert object_data["id"] ==
-               "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
-    end
-
     test "it works for incomming unfollows with an existing follow" do
       user = insert(:user)
 
@@ -889,32 +779,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       refute User.following?(blocked, blocker)
     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)
-
-      {: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"]["type"] == "Block"
-      assert data["object"]["object"] == user.ap_id
-      assert data["actor"] == "http://mastodon.example.org/users/admin"
-
-      blocker = User.get_cached_by_ap_id(data["actor"])
-
-      refute User.blocks?(blocker, user)
-    end
-
     test "it works for incoming accepts which were pre-accepted" do
       follower = insert(:user)
       followed = insert(:user)
index b0bfed9178a53cba7a5f034cbe0cc1060d4f487e..b8d811c73d96fc1e02bc740dcd1f29d6433e0107 100644 (file)
@@ -102,34 +102,6 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
     end
   end
 
-  describe "make_unlike_data/3" do
-    test "returns data for unlike activity" do
-      user = insert(:user)
-      like_activity = insert(:like_activity, data_attrs: %{"context" => "test context"})
-
-      object = Object.normalize(like_activity.data["object"])
-
-      assert Utils.make_unlike_data(user, like_activity, nil) == %{
-               "type" => "Undo",
-               "actor" => user.ap_id,
-               "object" => like_activity.data,
-               "to" => [user.follower_address, object.data["actor"]],
-               "cc" => [Pleroma.Constants.as_public()],
-               "context" => like_activity.data["context"]
-             }
-
-      assert Utils.make_unlike_data(user, like_activity, "9mJEZK0tky1w2xD2vY") == %{
-               "type" => "Undo",
-               "actor" => user.ap_id,
-               "object" => like_activity.data,
-               "to" => [user.follower_address, object.data["actor"]],
-               "cc" => [Pleroma.Constants.as_public()],
-               "context" => like_activity.data["context"],
-               "id" => "9mJEZK0tky1w2xD2vY"
-             }
-    end
-  end
-
   describe "make_like_data" do
     setup do
       user = insert(:user)
index 62a2665b639682841c2ee78731c84723001dc234..9a37d18875e8146a51d5ac04653ee481a92ae3df 100644 (file)
@@ -375,10 +375,11 @@ defmodule Pleroma.Web.CommonAPITest do
       {: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, "👍")
+      {: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
index 61a1689b991583f27a69f620a7d8576c69a18efb..299dbad41e8d6e5751c9cc5a4113e9aaf06822d4 100644 (file)
@@ -3,12 +3,14 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
+  use Oban.Testing, repo: Pleroma.Repo
   use Pleroma.Web.ConnCase
 
   alias Pleroma.Conversation.Participation
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Repo
+  alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
 
@@ -41,7 +43,9 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     other_user = insert(:user)
 
     {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
-    {:ok, activity, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
+    {:ok, _reaction, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
+
+    ObanHelpers.perform_all()
 
     result =
       conn
@@ -52,7 +56,9 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     assert %{"id" => id} = json_response(result, 200)
     assert to_string(activity.id) == id
 
-    object = Object.normalize(activity)
+    ObanHelpers.perform_all()
+
+    object = Object.get_by_ap_id(activity.data["object"])
 
     assert object.data["reaction_count"] == 0
   end