Speeds up the test and makes it possible to run async.
config :pleroma, :mrf, policies: []
+config :pleroma, :pipeline,
+ object_validator: Pleroma.Web.ActivityPub.ObjectValidatorMock,
+ mrf: Pleroma.Web.ActivityPub.MRFMock,
+ activity_pub: Pleroma.Web.ActivityPub.ActivityPubMock,
+ side_effects: Pleroma.Web.ActivityPub.SideEffectsMock,
+ federator: Pleroma.Web.FederatorMock,
+ config: Pleroma.ConfigMock
+
config :pleroma, :cachex, provider: Pleroma.CachexMock
if File.exists?("./config/test.secret.exs") do
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Config.Getting do
+ @callback get(any()) :: any()
+ @callback get(any(), any()) :: any()
+end
+
defmodule Pleroma.Config do
+ @behaviour Pleroma.Config.Getting
defmodule Error do
defexception [:message]
end
+ @impl true
def get(key), do: get(key, nil)
+ @impl true
def get([key], default), do: get(key, default)
+ @impl true
def get([_ | _] = path, default) do
case fetch(path) do
{:ok, value} -> value
end
end
+ @impl true
def get(key, default) do
Application.get_env(:pleroma, key, default)
end
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.ActivityPub.ActivityPub.Persisting do
+ @callback persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
+end
+
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
require Logger
require Pleroma.Constants
+ @behaviour Pleroma.Web.ActivityPub.ActivityPub.Persisting
+
defp get_recipients(%{"type" => "Create"} = data) do
to = Map.get(data, "to", [])
cc = Map.get(data, "cc", [])
defp increase_replies_count_if_reply(_create_data), do: :noop
@object_types ~w[ChatMessage Question Answer Audio Video Event Article]
- @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
+ @impl true
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
{:ok, object, meta}
end
end
+ @impl true
def persist(object, meta) do
with local <- Keyword.fetch!(meta, :local),
{recipients, _, _} <- get_recipients(object),
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.ActivityPub.MRF.PipelineFiltering do
+ @callback pipeline_filter(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
+end
+
defmodule Pleroma.Web.ActivityPub.MRF do
require Logger
+ @behaviour Pleroma.Web.ActivityPub.MRF.PipelineFiltering
+
@mrf_config_descriptions [
%{
group: :pleroma,
def filter(%{} = object), do: get_policies() |> filter(object)
+ @impl true
def pipeline_filter(%{} = message, meta) do
object = meta[:object_data]
ap_id = message["object"]
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.ActivityPub.ObjectValidator.Validating do
+ @callback validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
+end
+
defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@moduledoc """
This module is responsible for validating an object (which can be an activity)
the system.
"""
+ @behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
+
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
- @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
+ @impl true
def validate(object, meta)
def validate(%{"type" => type} = object, meta)
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
+ @side_effects Config.get([:pipeline, :side_effects], SideEffects)
+ @federator Config.get([:pipeline, :federator], Federator)
+ @object_validator Config.get([:pipeline, :object_validator], ObjectValidator)
+ @mrf Config.get([:pipeline, :mrf], MRF)
+ @activity_pub Config.get([:pipeline, :activity_pub], ActivityPub)
+ @config Config.get([:pipeline, :config], Config)
+
@spec common_pipeline(map(), keyword()) ::
{:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
def common_pipeline(object, meta) do
case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
{:ok, {:ok, activity, meta}} ->
- SideEffects.handle_after_transaction(meta)
+ @side_effects.handle_after_transaction(meta)
{:ok, activity, meta}
{:ok, value} ->
def do_common_pipeline(object, meta) do
with {_, {:ok, validated_object, meta}} <-
- {:validate_object, ObjectValidator.validate(object, meta)},
+ {:validate_object, @object_validator.validate(object, meta)},
{_, {:ok, mrfd_object, meta}} <-
- {:mrf_object, MRF.pipeline_filter(validated_object, meta)},
+ {:mrf_object, @mrf.pipeline_filter(validated_object, meta)},
{_, {:ok, activity, meta}} <-
- {:persist_object, ActivityPub.persist(mrfd_object, meta)},
+ {:persist_object, @activity_pub.persist(mrfd_object, meta)},
{_, {:ok, activity, meta}} <-
- {:execute_side_effects, SideEffects.handle(activity, meta)},
+ {:execute_side_effects, @side_effects.handle(activity, meta)},
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
{:ok, activity, meta}
else
defp maybe_federate(%Activity{} = activity, meta) do
with {:ok, local} <- Keyword.fetch(meta, :local) do
- do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
+ do_not_federate = meta[:do_not_federate] || !@config.get([:instance, :federating])
if !do_not_federate and local and not Visibility.is_local_public?(activity) do
activity =
activity
end
- Federator.publish(activity)
+ @federator.publish(activity)
{:ok, :federated}
else
{:ok, :not_federated}
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.ActivityPub.SideEffects.Handling do
+ @callback handle(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
+ @callback handle_after_transaction(map()) :: map()
+end
+
defmodule Pleroma.Web.ActivityPub.SideEffects do
@moduledoc """
This module looks at an inserted object and executes the side effects that it
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+ @behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
+
+ @impl true
def handle(object, meta \\ [])
# Task this handles
# - Follows
# - Sends a notification
+ @impl true
def handle(
%{
data: %{
# - Rejects all existing follow activities for this person
# - Updates the follow state
# - Dismisses notification
+ @impl true
def handle(
%{
data: %{
# - Follows if possible
# - Sends a notification
# - Generates accept or reject if appropriate
+ @impl true
def handle(
%{
data: %{
# Tasks this handles:
# - Unfollow and block
+ @impl true
def handle(
%{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
object,
#
# For a local user, we also get a changeset with the full information, so we
# can update non-federating, non-activitypub settings as well.
+ @impl true
def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
if changeset = Keyword.get(meta, :user_update_changeset) do
changeset
# Tasks this handles:
# - Add like to object
# - Set up notification
+ @impl true
def handle(%{data: %{"type" => "Like"}} = object, meta) do
liked_object = Object.get_by_ap_id(object.data["object"])
Utils.add_like_to_object(object, liked_object)
# - Increase replies count
# - Set up ActivityExpiration
# - Set up notifications
+ @impl true
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
# - Add announce to object
# - Set up notification
# - Stream out the announce
+ @impl true
def handle(%{data: %{"type" => "Announce"}} = object, meta) do
announced_object = Object.get_by_ap_id(object.data["object"])
user = User.get_cached_by_ap_id(object.data["actor"])
{:ok, object, meta}
end
+ @impl true
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
# Tasks this handles:
# - Add reaction to object
# - Set up notification
+ @impl true
def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
reacted_object = Object.get_by_ap_id(object.data["object"])
Utils.add_emoji_reaction_to_object(object, reacted_object)
# - Reduce the user note count
# - Reduce the reply count
# - Stream out the activity
+ @impl true
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
deleted_object =
Object.normalize(deleted_object, false) ||
end
# Nothing to do
+ @impl true
def handle(object, meta) do
{:ok, object, meta}
end
|> Keyword.put(:notifications, notifications ++ existing)
end
+ @impl true
def handle_after_transaction(meta) do
meta
|> send_notifications()
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.Federator.Publishing do
+ @callback publish(map()) :: any()
+end
+
defmodule Pleroma.Web.Federator do
alias Pleroma.Activity
alias Pleroma.Object.Containment
require Logger
+ @behaviour Pleroma.Web.Federator.Publishing
+
@doc """
Returns `true` if the distance to target object does not exceed max configured value.
Serves to prevent fetching of very long threads, especially useful on smaller instances.
ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params})
end
+ @impl true
def publish(%{id: "pleroma:fakeid"} = activity) do
perform(:publish, activity)
end
+ @impl true
def publish(activity) do
PublisherWorker.enqueue("publish", %{"activity_id" => activity.id})
end
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.PipelineTest do
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: true
- import Mock
+ import Mox
import Pleroma.Factory
+ alias Pleroma.Web.ActivityPub.ActivityPubMock
+ alias Pleroma.Web.ActivityPub.MRFMock
+ alias Pleroma.Web.ActivityPub.ObjectValidatorMock
+ alias Pleroma.Web.ActivityPub.SideEffectsMock
+ alias Pleroma.Web.FederatorMock
+ alias Pleroma.ConfigMock
+
+ setup :verify_on_exit!
+
describe "common_pipeline/2" do
setup do
- clear_config([:instance, :federating], true)
+ ObjectValidatorMock
+ |> expect(:validate, fn o, m -> {:ok, o, m} end)
+
+ MRFMock
+ |> expect(:pipeline_filter, fn o, m -> {:ok, o, m} end)
+
+ ActivityPubMock
+ |> expect(:persist, fn o, m -> {:ok, o, m} end)
+
+ SideEffectsMock
+ |> expect(:handle, fn o, m -> {:ok, o, m} end)
+ |> expect(:handle_after_transaction, fn m -> m end)
+
:ok
end
activity_with_object = %{activity | data: Map.put(activity.data, "object", object)}
- with_mocks([
- {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
- {
- Pleroma.Web.ActivityPub.MRF,
- [],
- [pipeline_filter: fn o, m -> {:ok, o, m} end]
- },
- {
- Pleroma.Web.ActivityPub.ActivityPub,
- [],
- [persist: fn o, m -> {:ok, o, m} end]
- },
- {
- Pleroma.Web.ActivityPub.SideEffects,
- [],
- [
- handle: fn o, m -> {:ok, o, m} end,
- handle_after_transaction: fn m -> m end
- ]
- },
- {
- Pleroma.Web.Federator,
- [],
- [publish: fn _o -> :ok end]
- }
- ]) do
- assert {:ok, ^activity, ^meta} =
- Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
-
- assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.MRF.pipeline_filter(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
- refute called(Pleroma.Web.Federator.publish(activity))
- assert_called(Pleroma.Web.Federator.publish(activity_with_object))
- end
+ FederatorMock
+ |> expect(:publish, fn ^activity_with_object -> :ok end)
+
+ ConfigMock
+ |> expect(:get, fn [:instance, :federating] -> true end)
+
+ assert {:ok, ^activity, ^meta} =
+ Pleroma.Web.ActivityPub.Pipeline.common_pipeline(
+ activity,
+ meta
+ )
end
test "it goes through validation, filtering, persisting, side effects and federation for local activities" do
activity = insert(:note_activity)
meta = [local: true]
- with_mocks([
- {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
- {
- Pleroma.Web.ActivityPub.MRF,
- [],
- [pipeline_filter: fn o, m -> {:ok, o, m} end]
- },
- {
- Pleroma.Web.ActivityPub.ActivityPub,
- [],
- [persist: fn o, m -> {:ok, o, m} end]
- },
- {
- Pleroma.Web.ActivityPub.SideEffects,
- [],
- [
- handle: fn o, m -> {:ok, o, m} end,
- handle_after_transaction: fn m -> m end
- ]
- },
- {
- Pleroma.Web.Federator,
- [],
- [publish: fn _o -> :ok end]
- }
- ]) do
- assert {:ok, ^activity, ^meta} =
- Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
-
- assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.MRF.pipeline_filter(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
- assert_called(Pleroma.Web.Federator.publish(activity))
- end
+ FederatorMock
+ |> expect(:publish, fn ^activity -> :ok end)
+
+ ConfigMock
+ |> expect(:get, fn [:instance, :federating] -> true end)
+
+ assert {:ok, ^activity, ^meta} =
+ Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
end
test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do
activity = insert(:note_activity)
meta = [local: false]
- with_mocks([
- {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
- {
- Pleroma.Web.ActivityPub.MRF,
- [],
- [pipeline_filter: fn o, m -> {:ok, o, m} end]
- },
- {
- Pleroma.Web.ActivityPub.ActivityPub,
- [],
- [persist: fn o, m -> {:ok, o, m} end]
- },
- {
- Pleroma.Web.ActivityPub.SideEffects,
- [],
- [handle: fn o, m -> {:ok, o, m} end, handle_after_transaction: fn m -> m end]
- },
- {
- Pleroma.Web.Federator,
- [],
- []
- }
- ]) do
- assert {:ok, ^activity, ^meta} =
- Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
-
- assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.MRF.pipeline_filter(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
- end
+ ConfigMock
+ |> expect(:get, fn [:instance, :federating] -> true end)
+
+ assert {:ok, ^activity, ^meta} =
+ Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
end
test "it goes through validation, filtering, persisting, side effects without federation for local activities if federation is deactivated" do
- clear_config([:instance, :federating], false)
-
activity = insert(:note_activity)
meta = [local: true]
- with_mocks([
- {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
- {
- Pleroma.Web.ActivityPub.MRF,
- [],
- [pipeline_filter: fn o, m -> {:ok, o, m} end]
- },
- {
- Pleroma.Web.ActivityPub.ActivityPub,
- [],
- [persist: fn o, m -> {:ok, o, m} end]
- },
- {
- Pleroma.Web.ActivityPub.SideEffects,
- [],
- [handle: fn o, m -> {:ok, o, m} end, handle_after_transaction: fn m -> m end]
- },
- {
- Pleroma.Web.Federator,
- [],
- []
- }
- ]) do
- assert {:ok, ^activity, ^meta} =
- Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
-
- assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.MRF.pipeline_filter(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
- assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
- end
+ ConfigMock
+ |> expect(:get, fn [:instance, :federating] -> false end)
+
+ assert {:ok, ^activity, ^meta} =
+ Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
end
end
end
# SPDX-License-Identifier: AGPL-3.0-only
Mox.defmock(Pleroma.CachexMock, for: Pleroma.Caching)
+
+Mox.defmock(Pleroma.Web.ActivityPub.ObjectValidatorMock,
+ for: Pleroma.Web.ActivityPub.ObjectValidator.Validating
+)
+
+Mox.defmock(Pleroma.Web.ActivityPub.MRFMock,
+ for: Pleroma.Web.ActivityPub.MRF.PipelineFiltering
+)
+
+Mox.defmock(Pleroma.Web.ActivityPub.ActivityPubMock,
+ for: Pleroma.Web.ActivityPub.ActivityPub.Persisting
+)
+
+Mox.defmock(Pleroma.Web.ActivityPub.SideEffectsMock,
+ for: Pleroma.Web.ActivityPub.SideEffects.Handling
+)
+
+Mox.defmock(Pleroma.Web.FederatorMock, for: Pleroma.Web.Federator.Publishing)
+
+Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting)