diff options
Diffstat (limited to 'test/support')
| -rw-r--r-- | test/support/api_spec_helpers.ex | 55 | ||||
| -rw-r--r-- | test/support/builders/activity_builder.ex | 61 | ||||
| -rw-r--r-- | test/support/builders/user_builder.ex | 30 | ||||
| -rw-r--r-- | test/support/cachex_proxy.ex | 40 | ||||
| -rw-r--r-- | test/support/captcha/mock.ex | 28 | ||||
| -rw-r--r-- | test/support/channel_case.ex | 34 | ||||
| -rw-r--r-- | test/support/cluster.ex | 222 | ||||
| -rw-r--r-- | test/support/conn_case.ex | 125 | ||||
| -rw-r--r-- | test/support/data_case.ex | 144 | ||||
| -rw-r--r-- | test/support/factory.ex | 666 | ||||
| -rw-r--r-- | test/support/helpers.ex | 138 | ||||
| -rw-r--r-- | test/support/http_request_mock.ex | 1534 | ||||
| -rw-r--r-- | test/support/mocks.ex | 30 | ||||
| -rw-r--r-- | test/support/mrf_module_mock.ex | 13 | ||||
| -rw-r--r-- | test/support/null_cache.ex | 49 | ||||
| -rw-r--r-- | test/support/oban_helpers.ex | 49 | ||||
| -rw-r--r-- | test/support/websocket_client.ex | 66 |
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 |
