diff options
30 files changed, 858 insertions, 611 deletions
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index a6f51f0be..2a6a23fec 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -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" diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index fcc3ce728..4955243ab 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -356,56 +356,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( @@ -436,35 +386,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 @@ -537,28 +458,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(          %{ diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index d130176cf..f6544d3f5 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -22,6 +22,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do      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) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index e51a8e0a8..549e5e761 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -15,10 +15,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do    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) +  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), diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index 4e6ee2034..aeef31945 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -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 index 000000000..d0ba418e8 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 8e5586e88..bfc2ab845 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -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:    # - Add reaction to object    # - Set up notification @@ -84,4 +93,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 diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index ee6fc31ce..be7b57f13 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -726,25 +726,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    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",            "object" => %{"type" => "Follow", "object" => followed},            "actor" => follower,            "id" => id @@ -764,39 +745,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 @@ -819,43 +790,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    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",            "actor" => origin_actor,            "object" => origin_actor, diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 1a3b0b3c1..09b80fa57 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 470fc0215..70069d6f9 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -556,11 +556,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do      }    end -  defp array_of_accounts do +  def array_of_accounts do      %Schema{        title: "ArrayOfAccounts",        type: :array, -      items: Account +      items: Account, +      example: [Account.schema().example]      }    end diff --git a/lib/pleroma/web/api_spec/operations/search_operation.ex b/lib/pleroma/web/api_spec/operations/search_operation.ex new file mode 100644 index 000000000..6ea00a9a8 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/search_operation.ex @@ -0,0 +1,207 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.SearchOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.AccountOperation +  alias Pleroma.Web.ApiSpec.Schemas.Account +  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike +  alias Pleroma.Web.ApiSpec.Schemas.FlakeID +  alias Pleroma.Web.ApiSpec.Schemas.Status +  alias Pleroma.Web.ApiSpec.Schemas.Tag + +  import Pleroma.Web.ApiSpec.Helpers + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def account_search_operation do +    %Operation{ +      tags: ["Search"], +      summary: "Search for matching accounts by username or display name", +      operationId: "SearchController.account_search", +      parameters: [ +        Operation.parameter(:q, :query, %Schema{type: :string}, "What to search for", +          required: true +        ), +        Operation.parameter( +          :limit, +          :query, +          %Schema{type: :integer, default: 40}, +          "Maximum number of results" +        ), +        Operation.parameter( +          :resolve, +          :query, +          %Schema{allOf: [BooleanLike], default: false}, +          "Attempt WebFinger lookup. Use this when `q` is an exact address." +        ), +        Operation.parameter( +          :following, +          :query, +          %Schema{allOf: [BooleanLike], default: false}, +          "Only include accounts that the user is following" +        ) +      ], +      responses: %{ +        200 => +          Operation.response( +            "Array of Account", +            "application/json", +            AccountOperation.array_of_accounts() +          ) +      } +    } +  end + +  def search_operation do +    %Operation{ +      tags: ["Search"], +      summary: "Search results", +      security: [%{"oAuth" => ["read:search"]}], +      operationId: "SearchController.search", +      deprecated: true, +      parameters: [ +        Operation.parameter( +          :account_id, +          :query, +          FlakeID, +          "If provided, statuses returned will be authored only by this account" +        ), +        Operation.parameter( +          :type, +          :query, +          %Schema{type: :string, enum: ["accounts", "hashtags", "statuses"]}, +          "Search type" +        ), +        Operation.parameter(:q, :query, %Schema{type: :string}, "The search query", required: true), +        Operation.parameter( +          :resolve, +          :query, +          %Schema{allOf: [BooleanLike], default: false}, +          "Attempt WebFinger lookup" +        ), +        Operation.parameter( +          :following, +          :query, +          %Schema{allOf: [BooleanLike], default: false}, +          "Only include accounts that the user is following" +        ), +        Operation.parameter( +          :offset, +          :query, +          %Schema{type: :integer}, +          "Offset" +        ) +        | pagination_params() +      ], +      responses: %{ +        200 => Operation.response("Results", "application/json", results()) +      } +    } +  end + +  def search2_operation do +    %Operation{ +      tags: ["Search"], +      summary: "Search results", +      security: [%{"oAuth" => ["read:search"]}], +      operationId: "SearchController.search2", +      parameters: [ +        Operation.parameter( +          :account_id, +          :query, +          FlakeID, +          "If provided, statuses returned will be authored only by this account" +        ), +        Operation.parameter( +          :type, +          :query, +          %Schema{type: :string, enum: ["accounts", "hashtags", "statuses"]}, +          "Search type" +        ), +        Operation.parameter(:q, :query, %Schema{type: :string}, "What to search for", +          required: true +        ), +        Operation.parameter( +          :resolve, +          :query, +          %Schema{allOf: [BooleanLike], default: false}, +          "Attempt WebFinger lookup" +        ), +        Operation.parameter( +          :following, +          :query, +          %Schema{allOf: [BooleanLike], default: false}, +          "Only include accounts that the user is following" +        ) +        | pagination_params() +      ], +      responses: %{ +        200 => Operation.response("Results", "application/json", results2()) +      } +    } +  end + +  defp results2 do +    %Schema{ +      title: "SearchResults", +      type: :object, +      properties: %{ +        accounts: %Schema{ +          type: :array, +          items: Account, +          description: "Accounts which match the given query" +        }, +        statuses: %Schema{ +          type: :array, +          items: Status, +          description: "Statuses which match the given query" +        }, +        hashtags: %Schema{ +          type: :array, +          items: Tag, +          description: "Hashtags which match the given query" +        } +      }, +      example: %{ +        "accounts" => [Account.schema().example], +        "statuses" => [Status.schema().example], +        "hashtags" => [Tag.schema().example] +      } +    } +  end + +  defp results do +    %Schema{ +      title: "SearchResults", +      type: :object, +      properties: %{ +        accounts: %Schema{ +          type: :array, +          items: Account, +          description: "Accounts which match the given query" +        }, +        statuses: %Schema{ +          type: :array, +          items: Status, +          description: "Statuses which match the given query" +        }, +        hashtags: %Schema{ +          type: :array, +          items: %Schema{type: :string}, +          description: "Hashtags which match the given query" +        } +      }, +      example: %{ +        "accounts" => [Account.schema().example], +        "statuses" => [Status.schema().example], +        "hashtags" => ["cofe"] +      } +    } +  end +end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 7a804461f..2572c9641 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do    alias Pleroma.Web.ApiSpec.Schemas.Emoji    alias Pleroma.Web.ApiSpec.Schemas.FlakeID    alias Pleroma.Web.ApiSpec.Schemas.Poll +  alias Pleroma.Web.ApiSpec.Schemas.Tag    alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope    require OpenApiSpex @@ -106,16 +107,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do        replies_count: %Schema{type: :integer},        sensitive: %Schema{type: :boolean},        spoiler_text: %Schema{type: :string}, -      tags: %Schema{ -        type: :array, -        items: %Schema{ -          type: :object, -          properties: %{ -            name: %Schema{type: :string}, -            url: %Schema{type: :string, format: :uri} -          } -        } -      }, +      tags: %Schema{type: :array, items: Tag},        uri: %Schema{type: :string, format: :uri},        url: %Schema{type: :string, nullable: true, format: :uri},        visibility: VisibilityScope diff --git a/lib/pleroma/web/api_spec/schemas/tag.ex b/lib/pleroma/web/api_spec/schemas/tag.ex new file mode 100644 index 000000000..e693fb83e --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/tag.ex @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Tag do +  alias OpenApiSpex.Schema + +  require OpenApiSpex + +  OpenApiSpex.schema(%{ +    title: "Tag", +    description: "Represents a hashtag used within the content of a status", +    type: :object, +    properties: %{ +      name: %Schema{type: :string, description: "The value of the hashtag after the # sign"}, +      url: %Schema{ +        type: :string, +        format: :uri, +        description: "A link to the hashtag on the instance" +      } +    }, +    example: %{ +      name: "cofe", +      url: "https://lain.com/tag/cofe" +    } +  }) +end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index b23de2bff..c538a634f 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -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")} @@ -188,8 +202,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")} diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 8458cbdd5..b9ed2d7b2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -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}) diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index cd49da6ad..0e0d54ba4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.MastodonAPI.SearchController do    use Pleroma.Web, :controller -  import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1] +  import Pleroma.Web.ControllerHelper, only: [skip_relationships?: 1]    alias Pleroma.Activity    alias Pleroma.Plugs.OAuthScopesPlug @@ -18,6 +18,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    require Logger +  plug(Pleroma.Web.ApiSpec.CastAndValidate) +    # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)    plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated}) @@ -25,7 +27,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search]) -  def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation + +  def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do      accounts = User.search(query, search_options(params, user))      conn @@ -36,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    def search2(conn, params), do: do_search(:v2, conn, params)    def search(conn, params), do: do_search(:v1, conn, params) -  defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do +  defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do      options = search_options(params, user)      timeout = Keyword.get(Repo.config(), :timeout, 15_000)      default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []} @@ -44,7 +48,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do      result =        default_values        |> Enum.map(fn {resource, default_value} -> -        if params["type"] in [nil, resource] do +        if params[:type] in [nil, resource] do            {resource, fn -> resource_search(version, resource, query, options) end}          else            {resource, fn -> default_value end} @@ -68,11 +72,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    defp search_options(params, user) do      [        skip_relationships: skip_relationships?(params), -      resolve: params["resolve"] == "true", -      following: params["following"] == "true", -      limit: fetch_integer_param(params, "limit"), -      offset: fetch_integer_param(params, "offset"), -      type: params["type"], +      resolve: params[:resolve], +      following: params[:following], +      limit: params[:limit], +      offset: params[:offset], +      type: params[:type],        author: get_author(params),        for_user: user      ] @@ -135,7 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do      end    end -  defp get_author(%{"account_id" => account_id}) when is_binary(account_id), +  defp get_author(%{account_id: account_id}) when is_binary(account_id),      do: User.get_cached_by_id(account_id)    defp get_author(_params), do: nil diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 9eea2e9eb..12e3ba15e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -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 diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 6ef3fe2dd..e2ffd02d0 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -78,7 +78,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do      user = %User{} = User.get_cached_by_ap_id(state.user.ap_id)      unless Streamer.filtered_by_user?(user, item) do -      websocket_info({:text, view.render(template, user, item)}, %{state | user: user}) +      websocket_info({:text, view.render(template, item, user)}, %{state | user: user})      else        {:ok, state}      end diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 6688d7caf..8bc77b75e 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -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) diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index 443868878..237b29ded 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -25,7 +25,7 @@ defmodule Pleroma.Web.StreamerView do      |> Jason.encode!()    end -  def render("notification.json", %User{} = user, %Notification{} = notify) do +  def render("notification.json", %Notification{} = notify, %User{} = user) do      %{        event: "notification",        payload: diff --git a/test/notification_test.exs b/test/notification_test.exs index 5b514e9db..e05fe2293 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -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 diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 4b70af5a6..0739cbfef 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -16,7 +16,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.AdminAPI.AccountView    alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.Federator    import ExUnit.CaptureLog    import Mock @@ -874,122 +873,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} = CommonAPI.react_with_emoji(activity.id, reactor, "🔥") - -      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} = 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 - -      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) @@ -1059,52 +942,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{ @@ -1211,7 +1048,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) @@ -1233,38 +1070,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 diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 4cae52077..f382adf3e 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -50,6 +50,46 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do      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) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 404b129ea..b29a7a7be 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -99,6 +99,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 index 000000000..eaf58adf7 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -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} = 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 diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 336ddb323..14c0f57ae 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -378,7 +378,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        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 @@ -403,7 +403,44 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        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 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) + +      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 @@ -729,35 +766,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) @@ -852,32 +860,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) diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index b0bfed917..b8d811c73 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -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) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index e5f7e3ef8..2fd17a1b8 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -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 diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs index 11133ff66..02476acb6 100644 --- a/test/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/web/mastodon_api/controllers/search_controller_test.exs @@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do          capture_log(fn ->            results =              conn -            |> get("/api/v2/search", %{"q" => "2hu"}) -            |> json_response(200) +            |> get("/api/v2/search?q=2hu") +            |> json_response_and_validate_schema(200)            assert results["accounts"] == []            assert results["statuses"] == [] @@ -54,8 +54,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        results =          conn -        |> get("/api/v2/search", %{"q" => "2hu #private"}) -        |> json_response(200) +        |> get("/api/v2/search?#{URI.encode_query(%{q: "2hu #private"})}") +        |> json_response_and_validate_schema(200)        [account | _] = results["accounts"]        assert account["id"] == to_string(user_three.id) @@ -68,8 +68,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        assert status["id"] == to_string(activity.id)        results = -        get(conn, "/api/v2/search", %{"q" => "天子"}) -        |> json_response(200) +        get(conn, "/api/v2/search?q=天子") +        |> json_response_and_validate_schema(200)        [status] = results["statuses"]        assert status["id"] == to_string(activity.id) @@ -89,8 +89,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do          conn          |> assign(:user, user)          |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) -        |> get("/api/v2/search", %{"q" => "Agent"}) -        |> json_response(200) +        |> get("/api/v2/search?q=Agent") +        |> json_response_and_validate_schema(200)        status_ids = Enum.map(results["statuses"], fn g -> g["id"] end) @@ -107,8 +107,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        results =          conn -        |> get("/api/v1/accounts/search", %{"q" => "shp"}) -        |> json_response(200) +        |> get("/api/v1/accounts/search?q=shp") +        |> json_response_and_validate_schema(200)        result_ids = for result <- results, do: result["acct"] @@ -117,8 +117,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        results =          conn -        |> get("/api/v1/accounts/search", %{"q" => "2hu"}) -        |> json_response(200) +        |> get("/api/v1/accounts/search?q=2hu") +        |> json_response_and_validate_schema(200)        result_ids = for result <- results, do: result["acct"] @@ -130,8 +130,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        results =          conn -        |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "}) -        |> json_response(200) +        |> get("/api/v1/accounts/search?q=shp@shitposter.club xxx") +        |> json_response_and_validate_schema(200)        assert length(results) == 1      end @@ -146,8 +146,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do          capture_log(fn ->            results =              conn -            |> get("/api/v1/search", %{"q" => "2hu"}) -            |> json_response(200) +            |> get("/api/v1/search?q=2hu") +            |> json_response_and_validate_schema(200)            assert results["accounts"] == []            assert results["statuses"] == [] @@ -173,8 +173,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        results =          conn -        |> get("/api/v1/search", %{"q" => "2hu"}) -        |> json_response(200) +        |> get("/api/v1/search?q=2hu") +        |> json_response_and_validate_schema(200)        [account | _] = results["accounts"]        assert account["id"] == to_string(user_three.id) @@ -194,8 +194,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do          results =            conn -          |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"}) -          |> json_response(200) +          |> get("/api/v1/search?q=https://shitposter.club/notice/2827873") +          |> json_response_and_validate_schema(200)          [status, %{"id" => ^activity_id}] = results["statuses"] @@ -212,10 +212,12 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do          })        capture_log(fn -> +        q = Object.normalize(activity).data["id"] +          results =            conn -          |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]}) -          |> json_response(200) +          |> get("/api/v1/search?q=#{q}") +          |> json_response_and_validate_schema(200)          [] = results["statuses"]        end) @@ -228,8 +230,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do          conn          |> assign(:user, user)          |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) -        |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"}) -        |> json_response(200) +        |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=true") +        |> json_response_and_validate_schema(200)        [account] = results["accounts"]        assert account["acct"] == "mike@osada.macgirvin.com" @@ -238,8 +240,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do      test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do        results =          conn -        |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "false"}) -        |> json_response(200) +        |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=false") +        |> json_response_and_validate_schema(200)        assert [] == results["accounts"]      end @@ -254,16 +256,16 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        result =          conn -        |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1}) +        |> get("/api/v1/search?q=2hu&limit=1") -      assert results = json_response(result, 200) +      assert results = json_response_and_validate_schema(result, 200)        assert [%{"id" => activity_id1}] = results["statuses"]        assert [_] = results["accounts"]        results =          conn -        |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1, "offset" => 1}) -        |> json_response(200) +        |> get("/api/v1/search?q=2hu&limit=1&offset=1") +        |> json_response_and_validate_schema(200)        assert [%{"id" => activity_id2}] = results["statuses"]        assert [] = results["accounts"] @@ -279,13 +281,13 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} =                 conn -               |> get("/api/v1/search", %{"q" => "2hu", "type" => "statuses"}) -               |> json_response(200) +               |> get("/api/v1/search?q=2hu&type=statuses") +               |> json_response_and_validate_schema(200)        assert %{"statuses" => [], "accounts" => [_user_two], "hashtags" => []} =                 conn -               |> get("/api/v1/search", %{"q" => "2hu", "type" => "accounts"}) -               |> json_response(200) +               |> get("/api/v1/search?q=2hu&type=accounts") +               |> json_response_and_validate_schema(200)      end      test "search uses account_id to filter statuses by the author", %{conn: conn} do @@ -297,8 +299,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        results =          conn -        |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user.id}) -        |> json_response(200) +        |> get("/api/v1/search?q=2hu&account_id=#{user.id}") +        |> json_response_and_validate_schema(200)        assert [%{"id" => activity_id1}] = results["statuses"]        assert activity_id1 == activity1.id @@ -306,8 +308,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        results =          conn -        |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user_two.id}) -        |> json_response(200) +        |> get("/api/v1/search?q=2hu&account_id=#{user_two.id}") +        |> json_response_and_validate_schema(200)        assert [%{"id" => activity_id2}] = results["statuses"]        assert activity_id2 == activity2.id diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs index 593aa92b2..43f1b154d 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -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} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") +    {:ok, _reaction_activity} = 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  | 
