@@ -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" | |||
@@ -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( | |||
%{ | |||
@@ -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) | |||
@@ -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), | |||
@@ -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 -> | |||
@@ -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 |
@@ -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 |
@@ -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, | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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")} | |||
@@ -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}) | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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) | |||
@@ -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: | |||
@@ -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 | |||
@@ -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 | |||
@@ -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) | |||
@@ -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) | |||
@@ -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 |
@@ -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) | |||
@@ -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) | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||