aboutsummaryrefslogtreecommitdiff
path: root/test/support
diff options
context:
space:
mode:
authordcc <dcc@logografos.com>2023-09-02 00:52:52 -0700
committerdcc <dcc@logografos.com>2023-09-02 00:52:52 -0700
commit3a4773c3c2bd0bbef244eb519b07208da9108e49 (patch)
tree973567a6f3abb37bfb0f785b1cad14ed55840ef5 /test/support
downloadanni-3a4773c3c2bd0bbef244eb519b07208da9108e49.tar.gz
anni-3a4773c3c2bd0bbef244eb519b07208da9108e49.tar.bz2
anni-3a4773c3c2bd0bbef244eb519b07208da9108e49.zip
First
Diffstat (limited to 'test/support')
-rw-r--r--test/support/api_spec_helpers.ex55
-rw-r--r--test/support/builders/activity_builder.ex61
-rw-r--r--test/support/builders/user_builder.ex30
-rw-r--r--test/support/cachex_proxy.ex40
-rw-r--r--test/support/captcha/mock.ex28
-rw-r--r--test/support/channel_case.ex34
-rw-r--r--test/support/cluster.ex222
-rw-r--r--test/support/conn_case.ex125
-rw-r--r--test/support/data_case.ex144
-rw-r--r--test/support/factory.ex666
-rw-r--r--test/support/helpers.ex138
-rw-r--r--test/support/http_request_mock.ex1534
-rw-r--r--test/support/mocks.ex30
-rw-r--r--test/support/mrf_module_mock.ex13
-rw-r--r--test/support/null_cache.ex49
-rw-r--r--test/support/oban_helpers.ex49
-rw-r--r--test/support/websocket_client.ex66
17 files changed, 3284 insertions, 0 deletions
diff --git a/test/support/api_spec_helpers.ex b/test/support/api_spec_helpers.ex
new file mode 100644
index 0000000..df51323
--- /dev/null
+++ b/test/support/api_spec_helpers.ex
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Tests.ApiSpecHelpers do
+ @moduledoc """
+ OpenAPI spec test helpers
+ """
+
+ import ExUnit.Assertions
+
+ alias OpenApiSpex.Cast.Error
+ alias OpenApiSpex.Reference
+ alias OpenApiSpex.Schema
+
+ def assert_schema(value, schema) do
+ api_spec = Pleroma.Web.ApiSpec.spec()
+
+ case OpenApiSpex.cast_value(value, schema, api_spec) do
+ {:ok, data} ->
+ data
+
+ {:error, errors} ->
+ errors =
+ Enum.map(errors, fn error ->
+ message = Error.message(error)
+ path = Error.path_to_string(error)
+ "#{message} at #{path}"
+ end)
+
+ flunk(
+ "Value does not conform to schema #{schema.title}: #{Enum.join(errors, "\n")}\n#{inspect(value)}"
+ )
+ end
+ end
+
+ def resolve_schema(%Schema{} = schema), do: schema
+
+ def resolve_schema(%Reference{} = ref) do
+ schemas = Pleroma.Web.ApiSpec.spec().components.schemas
+ Reference.resolve_schema(ref, schemas)
+ end
+
+ def api_operations do
+ paths = Pleroma.Web.ApiSpec.spec().paths
+
+ Enum.flat_map(paths, fn {_, path_item} ->
+ path_item
+ |> Map.take([:delete, :get, :head, :options, :patch, :post, :put, :trace])
+ |> Map.values()
+ |> Enum.reject(&is_nil/1)
+ end)
+ |> Enum.uniq()
+ end
+end
diff --git a/test/support/builders/activity_builder.ex b/test/support/builders/activity_builder.ex
new file mode 100644
index 0000000..f1ca395
--- /dev/null
+++ b/test/support/builders/activity_builder.ex
@@ -0,0 +1,61 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Builders.ActivityBuilder do
+ alias Pleroma.Web.ActivityPub.ActivityPub
+
+ def build(data \\ %{}, opts \\ %{}) do
+ user = opts[:user] || Pleroma.Factory.insert(:user)
+
+ activity = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+ "actor" => user.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "type" => "Note",
+ "content" => "test",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"]
+ }
+ }
+
+ Map.merge(activity, data)
+ end
+
+ def insert(data \\ %{}, opts \\ %{}) do
+ activity = build(data, opts)
+
+ case ActivityPub.insert(activity) do
+ ok = {:ok, activity} ->
+ ActivityPub.notify_and_stream(activity)
+ ok
+
+ error ->
+ error
+ end
+ end
+
+ def insert_list(times, data \\ %{}, opts \\ %{}) do
+ Enum.map(1..times, fn _n ->
+ {:ok, activity} = insert(data, opts)
+ activity
+ end)
+ end
+
+ def public_and_non_public do
+ user = Pleroma.Factory.insert(:user)
+
+ public = build(%{"id" => 1}, %{user: user})
+ non_public = build(%{"id" => 2, "to" => [user.follower_address]}, %{user: user})
+
+ {:ok, public} = ActivityPub.insert(public)
+ {:ok, non_public} = ActivityPub.insert(non_public)
+
+ %{
+ public: public,
+ non_public: non_public,
+ user: user
+ }
+ end
+end
diff --git a/test/support/builders/user_builder.ex b/test/support/builders/user_builder.ex
new file mode 100644
index 0000000..d6af306
--- /dev/null
+++ b/test/support/builders/user_builder.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Builders.UserBuilder do
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ def build(data \\ %{}) do
+ user = %User{
+ email: "test@example.org",
+ name: "Test Name",
+ nickname: "testname",
+ password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("test"),
+ bio: "A tester.",
+ ap_id: "some id",
+ last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
+ multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
+ notification_settings: %Pleroma.User.NotificationSetting{}
+ }
+
+ Map.merge(user, data)
+ end
+
+ def insert(data \\ %{}) do
+ {:ok, user} = Repo.insert(build(data))
+ User.invalidate_cache(user)
+ {:ok, user}
+ end
+end
diff --git a/test/support/cachex_proxy.ex b/test/support/cachex_proxy.ex
new file mode 100644
index 0000000..83ae561
--- /dev/null
+++ b/test/support/cachex_proxy.ex
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.CachexProxy do
+ @behaviour Pleroma.Caching
+
+ @impl true
+ defdelegate get!(cache, key), to: Cachex
+
+ @impl true
+ defdelegate stream!(cache, key), to: Cachex
+
+ @impl true
+ defdelegate put(cache, key, value, options), to: Cachex
+
+ @impl true
+ defdelegate put(cache, key, value), to: Cachex
+
+ @impl true
+ defdelegate get_and_update(cache, key, func), to: Cachex
+
+ @impl true
+ defdelegate get(cache, key), to: Cachex
+
+ @impl true
+ defdelegate fetch!(cache, key, func), to: Cachex
+
+ @impl true
+ defdelegate expire_at(cache, str, num), to: Cachex
+
+ @impl true
+ defdelegate exists?(cache, key), to: Cachex
+
+ @impl true
+ defdelegate del(cache, key), to: Cachex
+
+ @impl true
+ defdelegate execute!(cache, func), to: Cachex
+end
diff --git a/test/support/captcha/mock.ex b/test/support/captcha/mock.ex
new file mode 100644
index 0000000..53e000e
--- /dev/null
+++ b/test/support/captcha/mock.ex
@@ -0,0 +1,28 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Captcha.Mock do
+ alias Pleroma.Captcha.Service
+ @behaviour Service
+
+ @solution "63615261b77f5354fb8c4e4986477555"
+
+ def solution, do: @solution
+
+ @impl Service
+ def new,
+ do: %{
+ type: :mock,
+ token: "afa1815e14e29355e6c8f6b143a39fa2",
+ answer_data: @solution,
+ url: "https://example.org/captcha.png",
+ seconds_valid: 300
+ }
+
+ @impl Service
+ def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok
+
+ def validate(_token, captcha, answer),
+ do: {:error, "Invalid CAPTCHA captcha: #{inspect(captcha)} ; answer: #{inspect(answer)}"}
+end
diff --git a/test/support/channel_case.ex b/test/support/channel_case.ex
new file mode 100644
index 0000000..8cb23f5
--- /dev/null
+++ b/test/support/channel_case.ex
@@ -0,0 +1,34 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ChannelCase do
+ @moduledoc """
+ This module defines the test case to be used by
+ channel tests.
+
+ Such tests rely on `Phoenix.ChannelTest` and also
+ import other functionality to make it easier
+ to build common datastructures and query the data layer.
+
+ Finally, if the test case interacts with the database,
+ it cannot be async. For this reason, every test runs
+ inside a transaction which is reset at the beginning
+ of the test unless the test case is marked as async.
+ """
+
+ use ExUnit.CaseTemplate
+
+ using do
+ quote do
+ # Import conveniences for testing with channels
+ import Phoenix.ChannelTest
+ use Pleroma.Tests.Helpers
+
+ # The default endpoint for testing
+ @endpoint Pleroma.Web.Endpoint
+ end
+ end
+
+ setup tags, do: Pleroma.DataCase.setup_multi_process_mode(tags)
+end
diff --git a/test/support/cluster.ex b/test/support/cluster.ex
new file mode 100644
index 0000000..1c923fb
--- /dev/null
+++ b/test/support/cluster.ex
@@ -0,0 +1,222 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Cluster do
+ @moduledoc """
+ Facilities for managing a cluster of slave VM's for federated testing.
+
+ ## Spawning the federated cluster
+
+ `spawn_cluster/1` spawns a map of slave nodes that are started
+ within the running VM. During startup, the slave node is sent all configuration
+ from the parent node, as well as all code. After receiving configuration and
+ code, the slave then starts all applications currently running on the parent.
+ The configuration passed to `spawn_cluster/1` overrides any parent application
+ configuration for the provided OTP app and key. This is useful for customizing
+ the Ecto database, Phoenix webserver ports, etc.
+
+ For example, to start a single federated VM named ":federated1", with the
+ Pleroma Endpoint running on port 4123, and with a database named
+ "pleroma_test1", you would run:
+
+ endpoint_conf = Application.fetch_env!(:pleroma, Pleroma.Web.Endpoint)
+ repo_conf = Application.fetch_env!(:pleroma, Pleroma.Repo)
+
+ Pleroma.Cluster.spawn_cluster(%{
+ :"federated1@127.0.0.1" => [
+ {:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test1")},
+ {:pleroma, Pleroma.Web.Endpoint,
+ Keyword.merge(endpoint_conf, http: [port: 4011], url: [port: 4011], server: true)}
+ ]
+ })
+
+ *Note*: application configuration for a given key is not merged,
+ so any customization requires first fetching the existing values
+ and merging yourself by providing the merged configuration,
+ such as above with the endpoint config and repo config.
+
+ ## Executing code within a remote node
+
+ Use the `within/2` macro to execute code within the context of a remote
+ federated node. The code block captures all local variable bindings from
+ the parent's context and returns the result of the expression after executing
+ it on the remote node. For example:
+
+ import Pleroma.Cluster
+
+ parent_value = 123
+
+ result =
+ within :"federated1@127.0.0.1" do
+ {node(), parent_value}
+ end
+
+ assert result == {:"federated1@127.0.0.1, 123}
+
+ *Note*: while local bindings are captured and available within the block,
+ other parent contexts like required, aliased, or imported modules are not
+ in scope. Those will need to be reimported/aliases/required within the block
+ as `within/2` is a remote procedure call.
+ """
+
+ @extra_apps Pleroma.Mixfile.application()[:extra_applications]
+
+ @doc """
+ Spawns the default Pleroma federated cluster.
+
+ Values before may be customized as needed for the test suite.
+ """
+ def spawn_default_cluster do
+ endpoint_conf = Application.fetch_env!(:pleroma, Pleroma.Web.Endpoint)
+ repo_conf = Application.fetch_env!(:pleroma, Pleroma.Repo)
+
+ spawn_cluster(%{
+ :"federated1@127.0.0.1" => [
+ {:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test_federated1")},
+ {:pleroma, Pleroma.Web.Endpoint,
+ Keyword.merge(endpoint_conf, http: [port: 4011], url: [port: 4011], server: true)}
+ ],
+ :"federated2@127.0.0.1" => [
+ {:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test_federated2")},
+ {:pleroma, Pleroma.Web.Endpoint,
+ Keyword.merge(endpoint_conf, http: [port: 4012], url: [port: 4012], server: true)}
+ ]
+ })
+ end
+
+ @doc """
+ Spawns a configured map of federated nodes.
+
+ See `Pleroma.Cluster` module documentation for details.
+ """
+ def spawn_cluster(node_configs) do
+ # Turn node into a distributed node with the given long name
+ :net_kernel.start([:"primary@127.0.0.1"])
+
+ # Allow spawned nodes to fetch all code from this node
+ {:ok, _} = :erl_boot_server.start([])
+ allow_boot("127.0.0.1")
+
+ silence_logger_warnings(fn ->
+ node_configs
+ |> Enum.map(&Task.async(fn -> start_slave(&1) end))
+ |> Enum.map(&Task.await(&1, 90_000))
+ end)
+ end
+
+ @doc """
+ Executes block of code again remote node.
+
+ See `Pleroma.Cluster` module documentation for details.
+ """
+ defmacro within(node, do: block) do
+ quote do
+ rpc(unquote(node), unquote(__MODULE__), :eval_quoted, [
+ unquote(Macro.escape(block)),
+ binding()
+ ])
+ end
+ end
+
+ @doc false
+ def eval_quoted(block, binding) do
+ {result, _binding} = Code.eval_quoted(block, binding, __ENV__)
+ result
+ end
+
+ defp start_slave({node_host, override_configs}) do
+ log(node_host, "booting federated VM")
+ {:ok, node} = :slave.start(~c"127.0.0.1", node_name(node_host), vm_args())
+ add_code_paths(node)
+ load_apps_and_transfer_configuration(node, override_configs)
+ ensure_apps_started(node)
+ {:ok, node}
+ end
+
+ def rpc(node, module, function, args) do
+ :rpc.block_call(node, module, function, args)
+ end
+
+ defp vm_args do
+ ~c"-loader inet -hosts 127.0.0.1 -setcookie #{:erlang.get_cookie()}"
+ end
+
+ defp allow_boot(host) do
+ {:ok, ipv4} = :inet.parse_ipv4_address(~c"#{host}")
+ :ok = :erl_boot_server.add_slave(ipv4)
+ end
+
+ defp add_code_paths(node) do
+ rpc(node, :code, :add_paths, [:code.get_path()])
+ end
+
+ defp load_apps_and_transfer_configuration(node, override_configs) do
+ Enum.each(Application.loaded_applications(), fn {app_name, _, _} ->
+ app_name
+ |> Application.get_all_env()
+ |> Enum.each(fn {key, primary_config} ->
+ rpc(node, Application, :put_env, [app_name, key, primary_config, [persistent: true]])
+ end)
+ end)
+
+ Enum.each(override_configs, fn {app_name, key, val} ->
+ rpc(node, Application, :put_env, [app_name, key, val, [persistent: true]])
+ end)
+ end
+
+ defp log(node, msg), do: IO.puts("[#{node}] #{msg}")
+
+ defp ensure_apps_started(node) do
+ loaded_names = Enum.map(Application.loaded_applications(), fn {name, _, _} -> name end)
+ app_names = @extra_apps ++ (loaded_names -- @extra_apps)
+
+ rpc(node, Application, :ensure_all_started, [:mix])
+ rpc(node, Mix, :env, [Mix.env()])
+ rpc(node, __MODULE__, :prepare_database, [])
+
+ log(node, "starting application")
+
+ Enum.reduce(app_names, MapSet.new(), fn app, loaded ->
+ if Enum.member?(loaded, app) do
+ loaded
+ else
+ {:ok, started} = rpc(node, Application, :ensure_all_started, [app])
+ MapSet.union(loaded, MapSet.new(started))
+ end
+ end)
+ end
+
+ @doc false
+ def prepare_database do
+ log(node(), "preparing database")
+ repo_config = Application.get_env(:pleroma, Pleroma.Repo)
+ repo_config[:adapter].storage_down(repo_config)
+ repo_config[:adapter].storage_up(repo_config)
+
+ {:ok, _, _} =
+ Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
+ Ecto.Migrator.run(repo, :up, log: false, all: true)
+ end)
+
+ Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
+ {:ok, _} = Application.ensure_all_started(:ex_machina)
+ end
+
+ defp silence_logger_warnings(func) do
+ prev_level = Logger.level()
+ Logger.configure(level: :error)
+ res = func.()
+ Logger.configure(level: prev_level)
+
+ res
+ end
+
+ defp node_name(node_host) do
+ node_host
+ |> to_string()
+ |> String.split("@")
+ |> Enum.at(0)
+ |> String.to_atom()
+ end
+end
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
new file mode 100644
index 0000000..f010fec
--- /dev/null
+++ b/test/support/conn_case.ex
@@ -0,0 +1,125 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ConnCase do
+ @moduledoc """
+ This module defines the test case to be used by
+ tests that require setting up a connection.
+
+ Such tests rely on `Phoenix.ConnTest` and also
+ import other functionality to make it easier
+ to build common datastructures and query the data layer.
+
+ Finally, if the test case interacts with the database,
+ it cannot be async. For this reason, every test runs
+ inside a transaction which is reset at the beginning
+ of the test unless the test case is marked as async.
+ """
+
+ use ExUnit.CaseTemplate
+
+ alias Pleroma.DataCase
+
+ using do
+ quote do
+ # Import conveniences for testing with connections
+ import Plug.Conn
+ import Phoenix.ConnTest
+ use Pleroma.Tests.Helpers
+ import Pleroma.Web.Router.Helpers
+
+ alias Pleroma.Config
+
+ # The default endpoint for testing
+ @endpoint Pleroma.Web.Endpoint
+
+ # Sets up OAuth access with specified scopes
+ defp oauth_access(scopes, opts \\ []) do
+ user =
+ Keyword.get_lazy(opts, :user, fn ->
+ Pleroma.Factory.insert(:user)
+ end)
+
+ token =
+ Keyword.get_lazy(opts, :oauth_token, fn ->
+ Pleroma.Factory.insert(:oauth_token, user: user, scopes: scopes)
+ end)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> assign(:token, token)
+
+ %{user: user, token: token, conn: conn}
+ end
+
+ defp request_content_type(%{conn: conn}) do
+ conn = put_req_header(conn, "content-type", "multipart/form-data")
+ [conn: conn]
+ end
+
+ defp empty_json_response(conn) do
+ body = response(conn, 204)
+ response_content_type(conn, :json)
+
+ body
+ end
+
+ defp json_response_and_validate_schema(
+ %{private: %{operation_id: op_id}} = conn,
+ status
+ ) do
+ {spec, lookup} = OpenApiSpex.Plug.PutApiSpec.get_spec_and_operation_lookup(conn)
+
+ content_type =
+ conn
+ |> Plug.Conn.get_resp_header("content-type")
+ |> List.first()
+ |> String.split(";")
+ |> List.first()
+
+ status = Plug.Conn.Status.code(status)
+
+ unless lookup[op_id].responses[status] do
+ err = "Response schema not found for #{status} #{conn.method} #{conn.request_path}"
+ flunk(err)
+ end
+
+ schema = lookup[op_id].responses[status].content[content_type].schema
+ json = if status == 204, do: empty_json_response(conn), else: json_response(conn, status)
+
+ case OpenApiSpex.cast_value(json, schema, spec) do
+ {:ok, _data} ->
+ json
+
+ {:error, errors} ->
+ errors =
+ Enum.map(errors, fn error ->
+ message = OpenApiSpex.Cast.Error.message(error)
+ path = OpenApiSpex.Cast.Error.path_to_string(error)
+ "#{message} at #{path}"
+ end)
+
+ flunk(
+ "Response does not conform to schema of #{op_id} operation: #{Enum.join(errors, "\n")}\n#{inspect(json)}"
+ )
+ end
+ end
+
+ defp json_response_and_validate_schema(conn, _status) do
+ flunk("Response schema not found for #{conn.method} #{conn.request_path} #{conn.status}")
+ end
+ end
+ end
+
+ setup tags do
+ DataCase.setup_multi_process_mode(tags)
+ DataCase.setup_streamer(tags)
+ DataCase.stub_pipeline()
+
+ Mox.verify_on_exit!()
+
+ {:ok, conn: Phoenix.ConnTest.build_conn()}
+ end
+end
diff --git a/test/support/data_case.ex b/test/support/data_case.ex
new file mode 100644
index 0000000..3c9cab0
--- /dev/null
+++ b/test/support/data_case.ex
@@ -0,0 +1,144 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.DataCase do
+ @moduledoc """
+ This module defines the setup for tests requiring
+ access to the application's data layer.
+
+ You may define functions here to be used as helpers in
+ your tests.
+
+ Finally, if the test case interacts with the database,
+ it cannot be async. For this reason, every test runs
+ inside a transaction which is reset at the beginning
+ of the test unless the test case is marked as async.
+ """
+
+ use ExUnit.CaseTemplate
+
+ import Pleroma.Tests.Helpers, only: [clear_config: 2]
+
+ using do
+ quote do
+ alias Pleroma.Repo
+
+ import Ecto
+ import Ecto.Changeset
+ import Ecto.Query
+ import Pleroma.DataCase
+ use Pleroma.Tests.Helpers
+
+ # Sets up OAuth access with specified scopes
+ defp oauth_access(scopes, opts \\ []) do
+ user =
+ Keyword.get_lazy(opts, :user, fn ->
+ Pleroma.Factory.insert(:user)
+ end)
+
+ token =
+ Keyword.get_lazy(opts, :oauth_token, fn ->
+ Pleroma.Factory.insert(:oauth_token, user: user, scopes: scopes)
+ end)
+
+ %{user: user, token: token}
+ end
+ end
+ end
+
+ def clear_cachex do
+ Pleroma.Supervisor
+ |> Supervisor.which_children()
+ |> Enum.each(fn
+ {name, _, _, [Cachex]} ->
+ name
+ |> to_string
+ |> String.trim_leading("cachex_")
+ |> Kernel.<>("_cache")
+ |> String.to_existing_atom()
+ |> Cachex.clear()
+
+ _ ->
+ nil
+ end)
+ end
+
+ def setup_multi_process_mode(tags) do
+ :ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
+
+ if tags[:async] do
+ Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache)
+ Mox.set_mox_private()
+ else
+ Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
+
+ Mox.set_mox_global()
+ Mox.stub_with(Pleroma.CachexMock, Pleroma.CachexProxy)
+ clear_cachex()
+ end
+
+ :ok
+ end
+
+ def setup_streamer(tags) do
+ if tags[:needs_streamer] do
+ start_supervised(%{
+ id: Pleroma.Web.Streamer.registry(),
+ start:
+ {Registry, :start_link, [[keys: :duplicate, name: Pleroma.Web.Streamer.registry()]]}
+ })
+ end
+
+ :ok
+ end
+
+ setup tags do
+ setup_multi_process_mode(tags)
+ setup_streamer(tags)
+ stub_pipeline()
+
+ Mox.verify_on_exit!()
+
+ :ok
+ end
+
+ def stub_pipeline do
+ Mox.stub_with(Pleroma.Web.ActivityPub.SideEffectsMock, Pleroma.Web.ActivityPub.SideEffects)
+
+ Mox.stub_with(
+ Pleroma.Web.ActivityPub.ObjectValidatorMock,
+ Pleroma.Web.ActivityPub.ObjectValidator
+ )
+
+ Mox.stub_with(Pleroma.Web.ActivityPub.MRFMock, Pleroma.Web.ActivityPub.MRF)
+ Mox.stub_with(Pleroma.Web.ActivityPub.ActivityPubMock, Pleroma.Web.ActivityPub.ActivityPub)
+ Mox.stub_with(Pleroma.Web.FederatorMock, Pleroma.Web.Federator)
+ Mox.stub_with(Pleroma.ConfigMock, Pleroma.Config)
+ end
+
+ def ensure_local_uploader(context) do
+ test_uploader = Map.get(context, :uploader) || Pleroma.Uploaders.Local
+
+ clear_config([Pleroma.Upload, :uploader], test_uploader)
+ clear_config([Pleroma.Upload, :filters], [])
+
+ :ok
+ end
+
+ @doc """
+ A helper that transform changeset errors to a map of messages.
+
+ changeset = Accounts.create_user(%{password: "short"})
+ assert "password is too short" in errors_on(changeset).password
+ assert %{password: ["password is too short"]} = errors_on(changeset)
+
+ """
+ def errors_on(changeset) do
+ Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
+ Enum.reduce(opts, message, fn {key, value}, acc ->
+ String.replace(acc, "%{#{key}}", to_string(value))
+ end)
+ end)
+ end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
new file mode 100644
index 0000000..09f0245
--- /dev/null
+++ b/test/support/factory.ex
@@ -0,0 +1,666 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Factory do
+ use ExMachina.Ecto, repo: Pleroma.Repo
+
+ require Pleroma.Constants
+
+ alias Pleroma.Object
+ alias Pleroma.User
+
+ @rsa_keys [
+ "test/fixtures/rsa_keys/key_1.pem",
+ "test/fixtures/rsa_keys/key_2.pem",
+ "test/fixtures/rsa_keys/key_3.pem",
+ "test/fixtures/rsa_keys/key_4.pem",
+ "test/fixtures/rsa_keys/key_5.pem"
+ ]
+ |> Enum.map(&File.read!/1)
+
+ def participation_factory do
+ conversation = insert(:conversation)
+ user = insert(:user)
+
+ %Pleroma.Conversation.Participation{
+ conversation: conversation,
+ user: user,
+ read: false
+ }
+ end
+
+ def conversation_factory do
+ %Pleroma.Conversation{
+ ap_id: sequence(:ap_id, &"https://some_conversation/#{&1}")
+ }
+ end
+
+ def user_factory(attrs \\ %{}) do
+ pem = Enum.random(@rsa_keys)
+
+ user = %User{
+ name: sequence(:name, &"Test テスト User #{&1}"),
+ email: sequence(:email, &"user#{&1}@example.com"),
+ nickname: sequence(:nickname, &"nick#{&1}"),
+ password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("test"),
+ bio: sequence(:bio, &"Tester Number #{&1}"),
+ is_discoverable: true,
+ last_digest_emailed_at: NaiveDateTime.utc_now(),
+ last_refreshed_at: NaiveDateTime.utc_now(),
+ notification_settings: %Pleroma.User.NotificationSetting{},
+ multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
+ ap_enabled: true,
+ keys: pem
+ }
+
+ urls =
+ if attrs[:local] == false do
+ base_domain = attrs[:domain] || Enum.random(["domain1.com", "domain2.com", "domain3.com"])
+
+ ap_id = "https://#{base_domain}/users/#{user.nickname}"
+
+ %{
+ ap_id: ap_id,
+ follower_address: ap_id <> "/followers",
+ following_address: ap_id <> "/following",
+ featured_address: ap_id <> "/collections/featured"
+ }
+ else
+ %{
+ ap_id: User.ap_id(user),
+ follower_address: User.ap_followers(user),
+ following_address: User.ap_following(user),
+ featured_address: User.ap_featured_collection(user)
+ }
+ end
+
+ attrs = Map.delete(attrs, :domain)
+
+ user
+ |> Map.put(:raw_bio, user.bio)
+ |> Map.merge(urls)
+ |> merge_attributes(attrs)
+ end
+
+ def user_relationship_factory(attrs \\ %{}) do
+ source = attrs[:source] || insert(:user)
+ target = attrs[:target] || insert(:user)
+ relationship_type = attrs[:relationship_type] || :block
+
+ %Pleroma.UserRelationship{
+ source_id: source.id,
+ target_id: target.id,
+ relationship_type: relationship_type
+ }
+ end
+
+ def note_factory(attrs \\ %{}) do
+ text = sequence(:text, &"This is :moominmamma: note #{&1}")
+
+ user = attrs[:user] || insert(:user)
+
+ data = %{
+ "type" => "Note",
+ "content" => text,
+ "source" => text,
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+ "actor" => user.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "likes" => [],
+ "like_count" => 0,
+ "context" => "2hu",
+ "summary" => "2hu",
+ "tag" => ["2hu"],
+ "emoji" => %{
+ "2hu" => "corndog.png"
+ }
+ }
+
+ %Pleroma.Object{
+ data: merge_attributes(data, Map.get(attrs, :data, %{}))
+ }
+ end
+
+ def attachment_factory(attrs \\ %{}) do
+ user = attrs[:user] || insert(:user)
+
+ data =
+ attachment_data(user.ap_id, nil)
+ |> Map.put("id", Pleroma.Web.ActivityPub.Utils.generate_object_id())
+
+ %Pleroma.Object{
+ data: merge_attributes(data, Map.get(attrs, :data, %{}))
+ }
+ end
+
+ def attachment_note_factory(attrs \\ %{}) do
+ user = attrs[:user] || insert(:user)
+ {length, attrs} = Map.pop(attrs, :length, 1)
+
+ data = %{
+ "attachment" =>
+ Stream.repeatedly(fn -> attachment_data(user.ap_id, attrs[:href]) end)
+ |> Enum.take(length)
+ }
+
+ build(:note, Map.put(attrs, :data, data))
+ end
+
+ defp attachment_data(ap_id, href) do
+ href = href || sequence(:href, &"#{Pleroma.Web.Endpoint.url()}/media/#{&1}.jpg")
+
+ %{
+ "url" => [
+ %{
+ "href" => href,
+ "type" => "Link",
+ "mediaType" => "image/jpeg"
+ }
+ ],
+ "name" => "some name",
+ "type" => "Document",
+ "actor" => ap_id,
+ "mediaType" => "image/jpeg"
+ }
+ end
+
+ def followers_only_note_factory(attrs \\ %{}) do
+ %Pleroma.Object{data: data} = note_factory(attrs)
+ %Pleroma.Object{data: Map.merge(data, %{"to" => [data["actor"] <> "/followers"]})}
+ end
+
+ def audio_factory(attrs \\ %{}) do
+ text = sequence(:text, &"lain radio episode #{&1}")
+
+ user = attrs[:user] || insert(:user)
+
+ data = %{
+ "type" => "Audio",
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+ "artist" => "lain",
+ "title" => text,
+ "album" => "lain radio",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "actor" => user.ap_id,
+ "length" => 180_000
+ }
+
+ %Pleroma.Object{
+ data: merge_attributes(data, Map.get(attrs, :data, %{}))
+ }
+ end
+
+ def listen_factory do
+ audio = insert(:audio)
+
+ data = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Listen",
+ "actor" => audio.data["actor"],
+ "to" => audio.data["to"],
+ "object" => audio.data,
+ "published" => audio.data["published"]
+ }
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"]
+ }
+ end
+
+ def direct_note_factory do
+ user2 = insert(:user)
+
+ %Pleroma.Object{data: data} = note_factory()
+ %Pleroma.Object{data: Map.merge(data, %{"to" => [user2.ap_id]})}
+ end
+
+ def article_factory do
+ %Pleroma.Object{data: data} = note_factory()
+ %Pleroma.Object{data: Map.merge(data, %{"type" => "Article"})}
+ end
+
+ def tombstone_factory do
+ data = %{
+ "type" => "Tombstone",
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+ "formerType" => "Note",
+ "deleted" => DateTime.utc_now() |> DateTime.to_iso8601()
+ }
+
+ %Pleroma.Object{
+ data: data
+ }
+ end
+
+ def question_factory(attrs \\ %{}) do
+ user = attrs[:user] || insert(:user)
+
+ data = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+ "type" => "Question",
+ "actor" => user.ap_id,
+ "attributedTo" => user.ap_id,
+ "attachment" => [],
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [user.follower_address],
+ "context" => Pleroma.Web.ActivityPub.Utils.generate_context_id(),
+ "closed" => DateTime.utc_now() |> DateTime.add(86_400) |> DateTime.to_iso8601(),
+ "oneOf" => [
+ %{
+ "type" => "Note",
+ "name" => "chocolate",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"}
+ },
+ %{
+ "type" => "Note",
+ "name" => "vanilla",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"}
+ }
+ ]
+ }
+
+ %Pleroma.Object{
+ data: merge_attributes(data, Map.get(attrs, :data, %{}))
+ }
+ end
+
+ def direct_note_activity_factory do
+ dm = insert(:direct_note)
+
+ data = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Create",
+ "actor" => dm.data["actor"],
+ "to" => dm.data["to"],
+ "object" => dm.data,
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => dm.data["context"]
+ }
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"]
+ }
+ end
+
+ def add_activity_factory(attrs \\ %{}) do
+ featured_collection_activity(attrs, "Add")
+ end
+
+ def remove_activity_factor(attrs \\ %{}) do
+ featured_collection_activity(attrs, "Remove")
+ end
+
+ defp featured_collection_activity(attrs, type) do
+ user = attrs[:user] || insert(:user)
+ note = attrs[:note] || insert(:note, user: user)
+
+ data_attrs =
+ attrs
+ |> Map.get(:data_attrs, %{})
+ |> Map.put(:type, type)
+
+ attrs = Map.drop(attrs, [:user, :note, :data_attrs])
+
+ data =
+ %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "target" => user.featured_address,
+ "object" => note.data["object"],
+ "actor" => note.data["actor"],
+ "type" => "Add",
+ "to" => [Pleroma.Constants.as_public()],
+ "cc" => [user.follower_address]
+ }
+ |> Map.merge(data_attrs)
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"]
+ }
+ |> Map.merge(attrs)
+ end
+
+ def followers_only_note_activity_factory(attrs \\ %{}) do
+ user = attrs[:user] || insert(:user)
+ note = insert(:followers_only_note, user: user)
+
+ data_attrs = attrs[:data_attrs] || %{}
+ attrs = Map.drop(attrs, [:user, :note, :data_attrs])
+
+ data =
+ %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Create",
+ "actor" => note.data["actor"],
+ "to" => note.data["to"],
+ "object" => note.data,
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => note.data["context"]
+ }
+ |> Map.merge(data_attrs)
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"]
+ }
+ |> Map.merge(attrs)
+ end
+
+ def note_activity_factory(attrs \\ %{}) do
+ user = attrs[:user] || insert(:user)
+ note = attrs[:note] || insert(:note, user: user)
+
+ data_attrs = attrs[:data_attrs] || %{}
+ attrs = Map.drop(attrs, [:user, :note, :data_attrs])
+
+ data =
+ %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Create",
+ "actor" => note.data["actor"],
+ "to" => note.data["to"],
+ "object" => note.data["id"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => note.data["context"]
+ }
+ |> Map.merge(data_attrs)
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"]
+ }
+ |> Map.merge(attrs)
+ end
+
+ def article_activity_factory do
+ article = insert(:article)
+
+ data = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Create",
+ "actor" => article.data["actor"],
+ "to" => article.data["to"],
+ "object" => article.data,
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => article.data["context"]
+ }
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"]
+ }
+ end
+
+ def announce_activity_factory(attrs \\ %{}) do
+ note_activity = attrs[:note_activity] || insert(:note_activity)
+ user = attrs[:user] || insert(:user)
+
+ data = %{
+ "type" => "Announce",
+ "actor" => note_activity.actor,
+ "object" => note_activity.data["id"],
+ "to" => [user.follower_address, note_activity.data["actor"]],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "context" => note_activity.data["context"]
+ }
+
+ %Pleroma.Activity{
+ data: data,
+ actor: user.ap_id,
+ recipients: data["to"]
+ }
+ end
+
+ def like_activity_factory(attrs \\ %{}) do
+ note_activity = attrs[:note_activity] || insert(:note_activity)
+ object = Object.normalize(note_activity, fetch: false)
+ user = insert(:user)
+
+ data =
+ %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "actor" => user.ap_id,
+ "type" => "Like",
+ "object" => object.data["id"],
+ "published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
+ }
+ |> Map.merge(attrs[:data_attrs] || %{})
+
+ %Pleroma.Activity{
+ data: data
+ }
+ end
+
+ def follow_activity_factory do
+ follower = insert(:user)
+ followed = insert(:user)
+
+ data = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "actor" => follower.ap_id,
+ "type" => "Follow",
+ "object" => followed.ap_id,
+ "published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
+ }
+
+ %Pleroma.Activity{
+ data: data,
+ actor: follower.ap_id
+ }
+ end
+
+ def report_activity_factory(attrs \\ %{}) do
+ user = attrs[:user] || insert(:user)
+ activity = attrs[:activity] || insert(:note_activity)
+ state = attrs[:state] || "open"
+
+ data = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "actor" => user.ap_id,
+ "type" => "Flag",
+ "object" => [activity.actor, activity.data["id"]],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "to" => [],
+ "cc" => [activity.actor],
+ "context" => activity.data["context"],
+ "state" => state
+ }
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"] ++ data["cc"]
+ }
+ end
+
+ def question_activity_factory(attrs \\ %{}) do
+ user = attrs[:user] || insert(:user)
+ question = attrs[:question] || insert(:question, user: user)
+
+ data_attrs = attrs[:data_attrs] || %{}
+ attrs = Map.drop(attrs, [:user, :question, :data_attrs])
+
+ data =
+ %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Create",
+ "actor" => question.data["actor"],
+ "to" => question.data["to"],
+ "object" => question.data["id"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => question.data["context"]
+ }
+ |> Map.merge(data_attrs)
+
+ %Pleroma.Activity{
+ data: data,
+ actor: data["actor"],
+ recipients: data["to"]
+ }
+ |> Map.merge(attrs)
+ end
+
+ def oauth_app_factory do
+ %Pleroma.Web.OAuth.App{
+ client_name: sequence(:client_name, &"Some client #{&1}"),
+ redirect_uris: "https://example.com/callback",
+ scopes: ["read", "write", "follow", "push", "admin"],
+ website: "https://example.com",
+ client_id: Ecto.UUID.generate(),
+ client_secret: "aaa;/&bbb"
+ }
+ end
+
+ def instance_factory do
+ %Pleroma.Instances.Instance{
+ host: "domain.com",
+ unreachable_since: nil
+ }
+ end
+
+ def oauth_token_factory(attrs \\ %{}) do
+ scopes = Map.get(attrs, :scopes, ["read"])
+ oauth_app = Map.get_lazy(attrs, :app, fn -> insert(:oauth_app, scopes: scopes) end)
+ user = Map.get_lazy(attrs, :user, fn -> build(:user) end)
+
+ valid_until =
+ Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10))
+
+ %Pleroma.Web.OAuth.Token{
+ token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(),
+ refresh_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(),
+ scopes: scopes,
+ user: user,
+ app: oauth_app,
+ valid_until: valid_until
+ }
+ end
+
+ def oauth_admin_token_factory(attrs \\ %{}) do
+ user = Map.get_lazy(attrs, :user, fn -> build(:user, is_admin: true) end)
+
+ scopes =
+ attrs
+ |> Map.get(:scopes, ["admin"])
+ |> Kernel.++(["admin"])
+ |> Enum.uniq()
+
+ attrs = Map.merge(attrs, %{user: user, scopes: scopes})
+ oauth_token_factory(attrs)
+ end
+
+ def oauth_authorization_factory do
+ %Pleroma.Web.OAuth.Authorization{
+ token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false),
+ scopes: ["read", "write", "follow", "push"],
+ valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10),
+ user: build(:user),
+ app: build(:oauth_app)
+ }
+ end
+
+ def push_subscription_factory do
+ %Pleroma.Web.Push.Subscription{
+ user: build(:user),
+ token: build(:oauth_token),
+ endpoint: "https://example.com/example/1234",
+ key_auth: "8eDyX_uCN0XRhSbY5hs7Hg==",
+ key_p256dh:
+ "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA=",
+ data: %{}
+ }
+ end
+
+ def notification_factory do
+ %Pleroma.Notification{
+ user: build(:user)
+ }
+ end
+
+ def scheduled_activity_factory do
+ %Pleroma.ScheduledActivity{
+ user: build(:user),
+ scheduled_at: NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(60), :millisecond),
+ params: build(:note) |> Map.from_struct() |> Map.get(:data)
+ }
+ end
+
+ def registration_factory do
+ user = insert(:user)
+
+ %Pleroma.Registration{
+ user: user,
+ provider: "twitter",
+ uid: "171799000",
+ info: %{
+ "name" => "John Doe",
+ "email" => "john@doe.com",
+ "nickname" => "johndoe",
+ "description" => "My bio"
+ }
+ }
+ end
+
+ def config_factory(attrs \\ %{}) do
+ %Pleroma.ConfigDB{
+ key: sequence(:key, &String.to_atom("some_key_#{&1}")),
+ group: :pleroma,
+ value:
+ sequence(
+ :value,
+ &%{another_key: "#{&1}somevalue", another: "#{&1}somevalue"}
+ )
+ }
+ |> merge_attributes(attrs)
+ end
+
+ def marker_factory do
+ %Pleroma.Marker{
+ user: build(:user),
+ timeline: "notifications",
+ lock_version: 0,
+ last_read_id: "1"
+ }
+ end
+
+ def mfa_token_factory do
+ %Pleroma.MFA.Token{
+ token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false),
+ authorization: build(:oauth_authorization),
+ valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10),
+ user: build(:user)
+ }
+ end
+
+ def filter_factory do
+ %Pleroma.Filter{
+ user: build(:user),
+ filter_id: sequence(:filter_id, & &1),
+ phrase: "cofe",
+ context: ["home"]
+ }
+ end
+
+ def announcement_factory(params \\ %{}) do
+ data = Map.get(params, :data, %{})
+
+ {_, params} = Map.pop(params, :data)
+
+ %Pleroma.Announcement{
+ data: Map.merge(%{"content" => "test announcement", "all_day" => false}, data)
+ }
+ |> Map.merge(params)
+ |> Pleroma.Announcement.add_rendered_properties()
+ end
+end
diff --git a/test/support/helpers.ex b/test/support/helpers.ex
new file mode 100644
index 0000000..0bd487f
--- /dev/null
+++ b/test/support/helpers.ex
@@ -0,0 +1,138 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Tests.Helpers do
+ @moduledoc """
+ Helpers for use in tests.
+ """
+ alias Pleroma.Config
+
+ require Logger
+
+ defmacro clear_config(config_path) do
+ quote do
+ clear_config(unquote(config_path)) do
+ end
+ end
+ end
+
+ defmacro clear_config(config_path, do: yield) do
+ quote do
+ initial_setting = Config.fetch(unquote(config_path))
+
+ unquote(yield)
+
+ on_exit(fn ->
+ case initial_setting do
+ :error ->
+ Config.delete(unquote(config_path))
+
+ {:ok, value} ->
+ Config.put(unquote(config_path), value)
+ end
+ end)
+
+ :ok
+ end
+ end
+
+ defmacro clear_config(config_path, temp_setting) do
+ # NOTE: `clear_config([section, key], value)` != `clear_config([section], key: value)` (!)
+ # Displaying a warning to prevent unintentional clearing of all but one keys in section
+ if Keyword.keyword?(temp_setting) and length(temp_setting) == 1 do
+ Logger.warn(
+ "Please change `clear_config([section], key: value)` to `clear_config([section, key], value)`"
+ )
+ end
+
+ quote do
+ clear_config(unquote(config_path)) do
+ Config.put(unquote(config_path), unquote(temp_setting))
+ end
+ end
+ end
+
+ def require_migration(migration_name) do
+ [{module, _}] = Code.require_file("#{migration_name}.exs", "priv/repo/migrations")
+ {:ok, %{migration: module}}
+ end
+
+ defmacro __using__(_opts) do
+ quote do
+ import Pleroma.Tests.Helpers,
+ only: [
+ clear_config: 1,
+ clear_config: 2
+ ]
+
+ def time_travel(entity, seconds) do
+ new_time = NaiveDateTime.add(entity.inserted_at, seconds)
+
+ entity
+ |> Ecto.Changeset.change(%{inserted_at: new_time, updated_at: new_time})
+ |> Pleroma.Repo.update()
+ end
+
+ def to_datetime(%NaiveDateTime{} = naive_datetime) do
+ naive_datetime
+ |> DateTime.from_naive!("Etc/UTC")
+ |> DateTime.truncate(:second)
+ end
+
+ def to_datetime(datetime) when is_binary(datetime) do
+ datetime
+ |> NaiveDateTime.from_iso8601!()
+ |> to_datetime()
+ end
+
+ def collect_ids(collection) do
+ collection
+ |> Enum.map(& &1.id)
+ |> Enum.sort()
+ end
+
+ def refresh_record(%{id: id, __struct__: model} = _),
+ do: refresh_record(model, %{id: id})
+
+ def refresh_record(model, %{id: id} = _) do
+ Pleroma.Repo.get_by(model, id: id)
+ end
+
+ # Used for comparing json rendering during tests.
+ def render_json(view, template, assigns) do
+ assigns = Map.new(assigns)
+
+ view.render(template, assigns)
+ |> Jason.encode!()
+ |> Jason.decode!()
+ end
+
+ def stringify_keys(nil), do: nil
+
+ def stringify_keys(key) when key in [true, false], do: key
+ def stringify_keys(key) when is_atom(key), do: Atom.to_string(key)
+
+ def stringify_keys(map) when is_map(map) do
+ map
+ |> Enum.map(fn {k, v} -> {stringify_keys(k), stringify_keys(v)} end)
+ |> Enum.into(%{})
+ end
+
+ def stringify_keys([head | rest] = list) when is_list(list) do
+ [stringify_keys(head) | stringify_keys(rest)]
+ end
+
+ def stringify_keys(key), do: key
+
+ defmacro guards_config(config_path) do
+ quote do
+ initial_setting = Config.get(config_path)
+
+ Config.put(config_path, true)
+ on_exit(fn -> Config.put(config_path, initial_setting) end)
+ end
+ end
+ end
+ end
+end
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
new file mode 100644
index 0000000..b0cf613
--- /dev/null
+++ b/test/support/http_request_mock.ex
@@ -0,0 +1,1534 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule HttpRequestMock do
+ require Logger
+
+ def activitypub_object_headers, do: [{"content-type", "application/activity+json"}]
+
+ def request(
+ %Tesla.Env{
+ url: url,
+ method: method,
+ headers: headers,
+ query: query,
+ body: body
+ } = _env
+ ) do
+ with {:ok, res} <- apply(__MODULE__, method, [url, query, body, headers]) do
+ res
+ else
+ error ->
+ with {:error, message} <- error do
+ Logger.warn(to_string(message))
+ end
+
+ {_, _r} = error
+ end
+ end
+
+ # GET Requests
+ #
+ def get(url, query \\ [], body \\ [], headers \\ [])
+
+ def get("https://osada.macgirvin.com/channel/mike", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://shitposter.club/users/moonman", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/moonman@shitposter.club.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mastodon.social/users/emelie/statuses/101849165031453009", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/status.emelie.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mastodon.social/users/emelie/statuses/101849165031453404", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
+ def get("https://mastodon.social/users/emelie", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/emelie.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mastodon.social/users/not_found", _, _, _) do
+ {:ok, %Tesla.Env{status: 404}}
+ end
+
+ def get("https://mastodon.sdf.org/users/rinpatch", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/rinpatch.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mastodon.sdf.org/users/rinpatch/collections/featured", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/users_mock/masto_featured.json")
+ |> String.replace("{{domain}}", "mastodon.sdf.org")
+ |> String.replace("{{nickname}}", "rinpatch"),
+ headers: [{"content-type", "application/activity+json"}]
+ }}
+ end
+
+ def get("https://patch.cx/objects/tesla_mock/poll_attachment", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/poll_attachment.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get(
+ "https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/webfinger_emelie.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get(
+ "https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mike@osada.macgirvin.com.json"),
+ headers: [{"content-type", "application/jrd+json"}]
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/29191",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___social.heldscal.la_user_29191.xml")
+ }}
+ end
+
+ def get(
+ "https://pawoo.net/.well-known/webfinger?resource=acct:https://pawoo.net/users/pekorino",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.xml")
+ }}
+ end
+
+ def get(
+ "https://social.stopwatchingus-heidelberg.de/.well-known/webfinger?resource=acct:https://social.stopwatchingus-heidelberg.de/user/18330",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/atarifrosch_webfinger.xml")
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=nonexistant@social.heldscal.la",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/nonexistant@social.heldscal.la.xml")
+ }}
+ end
+
+ def get(
+ "https://squeet.me/xrd/?uri=acct:lain@squeet.me",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml"),
+ headers: [{"content-type", "application/xrd+xml"}]
+ }}
+ end
+
+ def get(
+ "https://mst3k.interlinked.me/users/luciferMysticus",
+ _,
+ _,
+ [{"accept", "application/activity+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/lucifermysticus.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://prismo.news/@mxb", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get(
+ "https://hubzilla.example.org/channel/kaniini",
+ _,
+ _,
+ [{"accept", "application/activity+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://niu.moe/users/rye", _, _, [{"accept", "application/activity+json"}]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/rye.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://n1u.moe/users/rye", _, _, [{"accept", "application/activity+json"}]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/rye.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://mastodon.example.org/users/admin/statuses/100787282858396771", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!(
+ "test/fixtures/tesla_mock/http___mastodon.example.org_users_admin_status_1234.json"
+ )
+ }}
+ end
+
+ def get("https://puckipedia.com/", _, _, [{"accept", "application/activity+json"}]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/puckipedia.com.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://peertube.moe/accounts/7even", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/7even.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://peertube.stream/accounts/createurs", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/peertube/actor-person.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/peertube.moe-vid.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://framatube.org/accounts/framasoft", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___framatube.org_accounts_framasoft.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/framatube.org-video.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://peertube.social/accounts/craigmaloney", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/craigmaloney.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/peertube-social.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mobilizon.org-event.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mobilizon.org/@tcit", _, _, [{"accept", "application/activity+json"}]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mobilizon.org-user.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://baptiste.gelez.xyz/@/BaptisteGelez", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://wedistribute.org/wp-json/pterotype/v1/object/85810", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/wedistribute-article.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://wedistribute.org/wp-json/pterotype/v1/actor/-blog", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/wedistribute-user.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://mastodon.example.org/users/admin", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://mastodon.example.org/users/relay", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/relay@mastdon.example.org.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://mastodon.example.org/users/gargron", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
+ {:error, :nxdomain}
+ end
+
+ def get("https://osada.macgirvin.com/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
+ def get("http://mastodon.sdf.org/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/sdf.org_host_meta")
+ }}
+ end
+
+ def get("https://mastodon.sdf.org/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/sdf.org_host_meta")
+ }}
+ end
+
+ def get(
+ "https://mastodon.sdf.org/.well-known/webfinger?resource=https://mastodon.sdf.org/users/snowdusk",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/snowdusk@sdf.org_host_meta.json")
+ }}
+ end
+
+ def get("http://mstdn.jp/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mstdn.jp_host_meta")
+ }}
+ end
+
+ def get("https://mstdn.jp/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mstdn.jp_host_meta")
+ }}
+ end
+
+ def get("https://mstdn.jp/.well-known/webfinger?resource=kpherox@mstdn.jp", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/kpherox@mstdn.jp.xml")
+ }}
+ end
+
+ def get("http://mamot.fr/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mamot.fr_host_meta")
+ }}
+ end
+
+ def get("https://mamot.fr/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mamot.fr_host_meta")
+ }}
+ end
+
+ def get("http://pawoo.net/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/pawoo.net_host_meta")
+ }}
+ end
+
+ def get("https://pawoo.net/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/pawoo.net_host_meta")
+ }}
+ end
+
+ def get(
+ "https://pawoo.net/.well-known/webfinger?resource=https://pawoo.net/users/pekorino",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/pekorino@pawoo.net_host_meta.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://pleroma.soykaf.com/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/soykaf.com_host_meta")
+ }}
+ end
+
+ def get("https://pleroma.soykaf.com/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/soykaf.com_host_meta")
+ }}
+ end
+
+ def get("http://social.stopwatchingus-heidelberg.de/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/stopwatchingus-heidelberg.de_host_meta")
+ }}
+ end
+
+ def get("https://social.stopwatchingus-heidelberg.de/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/stopwatchingus-heidelberg.de_host_meta")
+ }}
+ end
+
+ def get(
+ "http://mastodon.example.org/@admin/99541947525187367",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/mastodon-note-object.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://mastodon.example.org/@admin/99541947525187368", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
+ def get("https://shitposter.club/notice/7369654", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/7369654.html")
+ }}
+ end
+
+ def get("https://mstdn.io/users/mayuutann", _, _, [{"accept", "application/activity+json"}]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mayumayu.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get(
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394",
+ _,
+ _,
+ [{"accept", "application/activity+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mayumayupost.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get(url, _, _, [{"accept", "application/xrd+xml,application/jrd+json"}])
+ when url in [
+ "https://pleroma.soykaf.com/.well-known/webfinger?resource=acct:https://pleroma.soykaf.com/users/lain",
+ "https://pleroma.soykaf.com/.well-known/webfinger?resource=https://pleroma.soykaf.com/users/lain"
+ ] do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain.xml")
+ }}
+ end
+
+ def get(
+ "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/1",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___shitposter.club_user_1.xml")
+ }}
+ end
+
+ def get("https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b", _, _, _) do
+ {:ok, %Tesla.Env{status: 200}}
+ end
+
+ def get(
+ "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/spc_5381_xrd.xml")
+ }}
+ end
+
+ def get("http://shitposter.club/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/shitposter.club_host_meta")
+ }}
+ end
+
+ def get("https://shitposter.club/notice/4027863", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/7369654.html")
+ }}
+ end
+
+ def get("http://social.sakamoto.gq/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/social.sakamoto.gq_host_meta")
+ }}
+ end
+
+ def get(
+ "https://social.sakamoto.gq/.well-known/webfinger?resource=https://social.sakamoto.gq/users/eal",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/eal_sakamoto.xml")
+ }}
+ end
+
+ def get("http://mastodon.social/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mastodon.social_host_meta")
+ }}
+ end
+
+ def get(
+ "https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/lambadalambda",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.xml")
+ }}
+ end
+
+ def get(
+ "https://mastodon.social/.well-known/webfinger?resource=acct:not_found@mastodon.social",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok, %Tesla.Env{status: 404}}
+ end
+
+ def get("http://gs.example.org/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/gs.example.org_host_meta")
+ }}
+ end
+
+ def get(
+ "http://gs.example.org/.well-known/webfinger?resource=http://gs.example.org:4040/index.php/user/1",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/tesla_mock/http___gs.example.org_4040_index.php_user_1.xml")
+ }}
+ end
+
+ def get(
+ "http://gs.example.org:4040/index.php/user/1",
+ _,
+ _,
+ [{"accept", "application/activity+json"}]
+ ) do
+ {:ok, %Tesla.Env{status: 406, body: ""}}
+ end
+
+ def get("https://squeet.me/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/squeet.me_host_meta")}}
+ end
+
+ def get(
+ "https://squeet.me/xrd?uri=lain@squeet.me",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml")
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=acct:shp@social.heldscal.la",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/shp@social.heldscal.la.xml"),
+ headers: [{"content-type", "application/xrd+xml"}]
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=acct:invalid_content@social.heldscal.la",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok, %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/jrd+json"}]}}
+ end
+
+ def get("https://framatube.org/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/framatube.org_host_meta")
+ }}
+ end
+
+ def get(
+ "https://framatube.org/main/xrd?uri=acct:framasoft@framatube.org",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-type", "application/jrd+json"}],
+ body: File.read!("test/fixtures/tesla_mock/framasoft@framatube.org.json")
+ }}
+ end
+
+ def get("http://gnusocial.de/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/gnusocial.de_host_meta")
+ }}
+ end
+
+ def get(
+ "http://gnusocial.de/main/xrd?uri=winterdienst@gnusocial.de",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/winterdienst_webfinger.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://status.alpicola.com/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/status.alpicola.com_host_meta")
+ }}
+ end
+
+ def get("https://macgirvin.com/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/macgirvin.com_host_meta")
+ }}
+ end
+
+ def get("https://gerzilla.de/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/gerzilla.de_host_meta")
+ }}
+ end
+
+ def get(
+ "https://gerzilla.de/xrd/?uri=acct:kaniini@gerzilla.de",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-type", "application/jrd+json"}],
+ body: File.read!("test/fixtures/tesla_mock/kaniini@gerzilla.de.json")
+ }}
+ end
+
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/23211",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___social.heldscal.la_user_23211.xml")
+ }}
+ end
+
+ def get("http://social.heldscal.la/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/social.heldscal.la_host_meta")
+ }}
+ end
+
+ def get("https://social.heldscal.la/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/social.heldscal.la_host_meta")
+ }}
+ end
+
+ def get("https://mastodon.social/users/lambadalambda", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/lambadalambda.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/users_mock/masto_featured.json")
+ |> String.replace("{{domain}}", "mastodon.social")
+ |> String.replace("{{nickname}}", "lambadalambda"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://apfed.club/channel/indio", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/osada-user-indio.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://social.heldscal.la/user/23211", _, _, [{"accept", "application/activity+json"}]) do
+ {:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)}
+ end
+
+ def get("http://example.com/ogp", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
+ end
+
+ def get("https://example.com/ogp", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
+ end
+
+ def get("https://pleroma.local/notice/9kCP7V", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
+ end
+
+ def get("http://localhost:4001/users/masto_closed/followers", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/masto_closed_followers.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://localhost:4001/users/masto_closed/followers?page=1", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/masto_closed_followers_page.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://localhost:4001/users/masto_closed/following", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/masto_closed_following.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://localhost:4001/users/masto_closed/following?page=1", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/masto_closed_following_page.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://localhost:8080/followers/fuser3", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/friendica_followers.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://localhost:8080/following/fuser3", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/friendica_following.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://localhost:4001/users/fuser2/followers", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/pleroma_followers.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://localhost:4001/users/fuser2/following", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/pleroma_following.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://domain-with-errors:4001/users/fuser1/followers", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 504,
+ body: ""
+ }}
+ end
+
+ def get("http://domain-with-errors:4001/users/fuser1/following", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 504,
+ body: ""
+ }}
+ end
+
+ def get("http://example.com/ogp-missing-data", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/rich_media/ogp-missing-data.html")
+ }}
+ end
+
+ def get("https://example.com/ogp-missing-data", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/rich_media/ogp-missing-data.html")
+ }}
+ end
+
+ def get("http://example.com/malformed", _, _, _) do
+ {:ok,
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}}
+ end
+
+ def get("http://example.com/empty", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: "hello"}}
+ end
+
+ def get("http://404.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
+ def get("https://404.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
+ def get(
+ "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=acct:lain@zetsubou.xn--q9jyb4c",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/lain.xml"),
+ headers: [{"content-type", "application/xrd+xml"}]
+ }}
+ end
+
+ def get(
+ "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=acct:https://zetsubou.xn--q9jyb4c/users/lain",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/lain.xml"),
+ headers: [{"content-type", "application/xrd+xml"}]
+ }}
+ end
+
+ def get("http://zetsubou.xn--q9jyb4c/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/host-meta-zetsubou.xn--q9jyb4c.xml")
+ }}
+ end
+
+ def get(
+ "https://zetsubou.xn--q9jyb4c/.well-known/host-meta",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/host-meta-zetsubou.xn--q9jyb4c.xml")
+ }}
+ end
+
+ def get("http://lm.kazv.moe/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/lm.kazv.moe_host_meta")
+ }}
+ end
+
+ def get("https://lm.kazv.moe/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/lm.kazv.moe_host_meta")
+ }}
+ end
+
+ def get(
+ "https://lm.kazv.moe/.well-known/webfinger?resource=acct:mewmew@lm.kazv.moe",
+ _,
+ _,
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___lm.kazv.moe_users_mewmew.xml"),
+ headers: [{"content-type", "application/xrd+xml"}]
+ }}
+ end
+
+ def get("https://lm.kazv.moe/users/mewmew", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mewmew@lm.kazv.moe.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://lm.kazv.moe/users/mewmew/collections/featured", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/users_mock/masto_featured.json")
+ |> String.replace("{{domain}}", "lm.kazv.moe")
+ |> String.replace("{{nickname}}", "mewmew"),
+ headers: [{"content-type", "application/activity+json"}]
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity.json", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity.json", _, _, _) do
+ {:ok, %Tesla.Env{status: 404, body: ""}}
+ end
+
+ def get("https://info.pleroma.site/activity2.json", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity2.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity2.json", _, _, _) do
+ {:ok, %Tesla.Env{status: 404, body: ""}}
+ end
+
+ def get("https://info.pleroma.site/activity3.json", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity3.json", _, _, _) do
+ {:ok, %Tesla.Env{status: 404, body: ""}}
+ end
+
+ def get("https://mstdn.jp/.well-known/webfinger?resource=acct:kpherox@mstdn.jp", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/kpherox@mstdn.jp.xml"),
+ headers: [{"content-type", "application/xrd+xml"}]
+ }}
+ end
+
+ def get("https://10.111.10.1/notice/9kCP7V", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end
+
+ def get("https://172.16.32.40/notice/9kCP7V", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end
+
+ def get("https://192.168.10.40/notice/9kCP7V", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end
+
+ def get("https://www.patreon.com/posts/mastodon-2-9-and-28121681", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end
+
+ def get("http://mastodon.example.org/@admin/99541947525187367", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/mastodon-post-activity.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity4.json", _, _, _) do
+ {:ok, %Tesla.Env{status: 500, body: "Error occurred"}}
+ end
+
+ def get("http://example.com/rel_me/anchor", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor.html")}}
+ end
+
+ def get("http://example.com/rel_me/anchor_nofollow", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor_nofollow.html")}}
+ end
+
+ def get("http://example.com/rel_me/link", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_link.html")}}
+ end
+
+ def get("http://example.com/rel_me/null", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")}}
+ end
+
+ def get("https://skippers-bin.com/notes/7x9tmrp97i", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/misskey_poll_no_end_date.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://example.org/emoji/firedfox.png", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}}
+ end
+
+ def get("https://skippers-bin.com/users/7v1w1r8ce6", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/sjw.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://patch.cx/users/rin", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/rin.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get(
+ "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/funkwhale_audio.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://channels.tests.funkwhale.audio/federation/actors/compositions", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/funkwhale_channel.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://example.com/rel_me/error", _, _, _) do
+ {:ok, %Tesla.Env{status: 404, body: ""}}
+ end
+
+ def get("https://relay.mastodon.host/actor", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/relay/relay.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("http://localhost:4001/", _, "", [{"accept", "text/html"}]) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
+ end
+
+ def get("https://osada.macgirvin.com/", _, "", [{"accept", "text/html"}]) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___osada.macgirvin.com.html")
+ }}
+ end
+
+ def get("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/emoji-in-summary.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://gleasonator.com/objects/102eb097-a18b-4cd5-abfc-f952efcb70bb", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/gleasonator-AG3RzWfwEKKrY63qj2.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://gleasonator.com/users/macgirvin", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/macgirvin@gleasonator.com.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://gleasonator.com/users/macgirvin/collections/featured", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/users_mock/masto_featured.json")
+ |> String.replace("{{domain}}", "gleasonator.com")
+ |> String.replace("{{nickname}}", "macgirvin"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mk.absturztau.be/users/8ozbzjs3o8", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mametsuko@mk.absturztau.be.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://p.helene.moe/users/helene", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/helene@p.helene.moe.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mk.absturztau.be/notes/93e7nm8wqg", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mk.absturztau.be/notes/93e7nm8wqg/activity", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://p.helene.moe/objects/fd5910ac-d9dc-412e-8d1d-914b203296c4", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/p.helene.moe-AM7S6vZQmL6pI9TgPY.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get(url, query, body, headers) do
+ {:error,
+ "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
+ end
+
+ # POST Requests
+ #
+
+ def post(url, query \\ [], body \\ [], headers \\ [])
+
+ def post("https://relay.mastodon.host/inbox", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end
+
+ def post("http://example.org/needs_refresh", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("http://mastodon.example.org/inbox", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("https://hubzilla.example.org/inbox", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("http://gs.example.org/index.php/main/salmon/user/1", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("http://200.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("http://connrefused.site" <> _, _, _, _) do
+ {:error, :connrefused}
+ end
+
+ def post("http://404.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
+ def post(url, query, body, headers) do
+ {:error,
+ "Mock response not implemented for POST #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
+ end
+
+ # Most of the rich media mocks are missing HEAD requests, so we just return 404.
+ @rich_media_mocks [
+ "https://example.com/ogp",
+ "https://example.com/ogp-missing-data",
+ "https://example.com/twitter-card"
+ ]
+ def head(url, _query, _body, _headers) when url in @rich_media_mocks do
+ {:ok, %Tesla.Env{status: 404, body: ""}}
+ end
+
+ def head(url, query, body, headers) do
+ {:error,
+ "Mock response not implemented for HEAD #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
+ end
+end
diff --git a/test/support/mocks.ex b/test/support/mocks.ex
new file mode 100644
index 0000000..d167996
--- /dev/null
+++ b/test/support/mocks.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# 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,
+ Pleroma.Web.ActivityPub.ActivityPub.Streaming
+ ]
+)
+
+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)
+
+Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging)
diff --git a/test/support/mrf_module_mock.ex b/test/support/mrf_module_mock.ex
new file mode 100644
index 0000000..616fc72
--- /dev/null
+++ b/test/support/mrf_module_mock.ex
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule MRFModuleMock do
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ @impl true
+ def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{mrf_module_mock: "some config data"}}
+end
diff --git a/test/support/null_cache.ex b/test/support/null_cache.ex
new file mode 100644
index 0000000..9f1d45f
--- /dev/null
+++ b/test/support/null_cache.ex
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.NullCache do
+ @moduledoc """
+ A module simulating a permanently empty cache.
+ """
+ @behaviour Pleroma.Caching
+
+ @impl true
+ def get!(_, _), do: nil
+
+ @impl true
+ def put(_, _, _, _ \\ nil), do: {:ok, true}
+
+ @impl true
+ def stream!(_, _), do: []
+
+ @impl true
+ def get(_, _), do: {:ok, nil}
+
+ @impl true
+ def fetch!(_, key, func) do
+ case func.(key) do
+ {_, res} -> res
+ res -> res
+ end
+ end
+
+ @impl true
+ def get_and_update(_, _, func) do
+ func.(nil)
+ end
+
+ @impl true
+ def expire_at(_, _, _), do: {:ok, true}
+
+ @impl true
+ def exists?(_, _), do: {:ok, false}
+
+ @impl true
+ def execute!(_, func) do
+ func.(:nothing)
+ end
+
+ @impl true
+ def del(_, _), do: {:ok, true}
+end
diff --git a/test/support/oban_helpers.ex b/test/support/oban_helpers.ex
new file mode 100644
index 0000000..94170a0
--- /dev/null
+++ b/test/support/oban_helpers.ex
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Tests.ObanHelpers do
+ @moduledoc """
+ Oban test helpers.
+ """
+
+ require Ecto.Query
+
+ alias Pleroma.Repo
+
+ def wipe_all do
+ Repo.delete_all(Oban.Job)
+ end
+
+ def perform_all do
+ Oban.Job
+ |> Ecto.Query.where(state: "available")
+ |> Repo.all()
+ |> perform()
+ end
+
+ def perform(%Oban.Job{} = job) do
+ res = apply(String.to_existing_atom("Elixir." <> job.worker), :perform, [job])
+ Repo.delete(job)
+ res
+ end
+
+ def perform(jobs) when is_list(jobs) do
+ for job <- jobs, do: perform(job)
+ end
+
+ def member?(%{} = job_args, jobs) when is_list(jobs) do
+ Enum.any?(jobs, fn job ->
+ member?(job_args, job.args)
+ end)
+ end
+
+ def member?(%{} = test_attrs, %{} = attrs) do
+ Enum.all?(
+ test_attrs,
+ fn {k, _v} -> member?(test_attrs[k], attrs[k]) end
+ )
+ end
+
+ def member?(x, y), do: x == y
+end
diff --git a/test/support/websocket_client.ex b/test/support/websocket_client.ex
new file mode 100644
index 0000000..7163bbd
--- /dev/null
+++ b/test/support/websocket_client.ex
@@ -0,0 +1,66 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Integration.WebsocketClient do
+ # https://github.com/phoenixframework/phoenix/blob/master/test/support/websocket_client.exs
+
+ use WebSockex
+
+ @doc """
+ Starts the WebSocket server for given ws URL. Received Socket.Message's
+ are forwarded to the sender pid
+ """
+ def start_link(sender, url, headers \\ []) do
+ WebSockex.start_link(
+ url,
+ __MODULE__,
+ %{sender: sender},
+ extra_headers: headers
+ )
+ end
+
+ @doc """
+ Closes the socket
+ """
+ def close(socket) do
+ send(socket, :close)
+ end
+
+ @doc """
+ Sends a low-level text message to the client.
+ """
+ def send_text(server_pid, msg) do
+ send(server_pid, {:text, msg})
+ end
+
+ @doc false
+ @impl true
+ def handle_frame(frame, state) do
+ send(state.sender, frame)
+ {:ok, state}
+ end
+
+ @impl true
+ def handle_disconnect(conn_status, state) do
+ send(state.sender, {:close, conn_status})
+ {:ok, state}
+ end
+
+ @doc false
+ @impl true
+ def handle_info({:text, msg}, state) do
+ {:reply, {:text, msg}, state}
+ end
+
+ @impl true
+ def handle_info(:close, _state) do
+ {:close, <<>>, "done"}
+ end
+
+ @doc false
+ @impl true
+ def terminate(_reason, _state) do
+ :ok
+ end
+end