reverse proxy tests
authorAlexander Strizhakov <alex.strizhakov@gmail.com>
Tue, 9 Jul 2019 16:54:13 +0000 (16:54 +0000)
committerkaniini <nenolod@gmail.com>
Tue, 9 Jul 2019 16:54:13 +0000 (16:54 +0000)
12 files changed:
config/test.exs
lib/mix/tasks/pleroma/ecto/ecto.ex
lib/pleroma/reverse_proxy/client.ex [new file with mode: 0644]
lib/pleroma/reverse_proxy/reverse_proxy.ex [moved from lib/pleroma/reverse_proxy.ex with 97% similarity]
mix.exs
mix.lock
test/http/request_builder_test.exs [new file with mode: 0644]
test/reverse_proxy_test.exs [new file with mode: 0644]
test/tasks/ecto/ecto_test.exs [new file with mode: 0644]
test/tasks/pleroma_test.exs [new file with mode: 0644]
test/tasks/robots_txt_test.exs [new file with mode: 0644]
test/test_helper.exs

index 012021f2a137269feb71b08624388a825f3946cd..63443dde0115a1127df6133a645b329a01810be4 100644 (file)
@@ -75,6 +75,8 @@ rum_enabled = System.get_env("RUM_ENABLED") == "true"
 config :pleroma, :database, rum_enabled: rum_enabled
 IO.puts("RUM enabled: #{rum_enabled}")
 
+config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
+
 try do
   import_config "test.secret.exs"
 rescue
index 324f57fdd789be719ca6ea3fd8b386f922c59e3b..b66f6337643b011cb20c43c489f46c4fdc2a187b 100644 (file)
@@ -1,6 +1,7 @@
 # Pleroma: A lightweight social networking server
 # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-onl
+
 defmodule Mix.Tasks.Pleroma.Ecto do
   @doc """
   Ensures the given repository's migrations path exists on the file system.
diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex
new file mode 100644 (file)
index 0000000..57c2d2c
--- /dev/null
@@ -0,0 +1,24 @@
+defmodule Pleroma.ReverseProxy.Client do
+  @callback request(atom(), String.t(), [tuple()], String.t(), list()) ::
+              {:ok, pos_integer(), [tuple()], reference() | map()}
+              | {:ok, pos_integer(), [tuple()]}
+              | {:ok, reference()}
+              | {:error, term()}
+
+  @callback stream_body(reference() | pid() | map()) ::
+              {:ok, binary()} | :done | {:error, String.t()}
+
+  @callback close(reference() | pid() | map()) :: :ok
+
+  def request(method, url, headers, "", opts \\ []) do
+    client().request(method, url, headers, "", opts)
+  end
+
+  def stream_body(ref), do: client().stream_body(ref)
+
+  def close(ref), do: client().close(ref)
+
+  defp client do
+    Pleroma.Config.get([Pleroma.ReverseProxy.Client], :hackney)
+  end
+end
similarity index 97%
rename from lib/pleroma/reverse_proxy.ex
rename to lib/pleroma/reverse_proxy/reverse_proxy.ex
index de0f6e1bc6ed73caec4b481bd81565745f89c367..bf31e9cba26a1b2919732b75f3237fe444b9a622 100644 (file)
@@ -146,7 +146,7 @@ defmodule Pleroma.ReverseProxy do
     Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
     method = method |> String.downcase() |> String.to_existing_atom()
 
-    case hackney().request(method, url, headers, "", hackney_opts) do
+    case client().request(method, url, headers, "", hackney_opts) do
       {:ok, code, headers, client} when code in @valid_resp_codes ->
         {:ok, code, downcase_headers(headers), client}
 
@@ -173,7 +173,7 @@ defmodule Pleroma.ReverseProxy do
         halt(conn)
 
       {:error, :closed, conn} ->
-        :hackney.close(client)
+        client().close(client)
         halt(conn)
 
       {:error, error, conn} ->
@@ -181,7 +181,7 @@ defmodule Pleroma.ReverseProxy do
           "#{__MODULE__} request to #{url} failed while reading/chunking: #{inspect(error)}"
         )
 
-        :hackney.close(client)
+        client().close(client)
         halt(conn)
     end
   end
@@ -196,7 +196,7 @@ defmodule Pleroma.ReverseProxy do
              duration,
              Keyword.get(opts, :max_read_duration, @max_read_duration)
            ),
-         {:ok, data} <- hackney().stream_body(client),
+         {:ok, data} <- client().stream_body(client),
          {:ok, duration} <- increase_read_duration(duration),
          sent_so_far = sent_so_far + byte_size(data),
          :ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
@@ -378,5 +378,5 @@ defmodule Pleroma.ReverseProxy do
     {:ok, :no_duration_limit, :no_duration_limit}
   end
 
-  defp hackney, do: Pleroma.Config.get(:hackney, :hackney)
+  defp client, do: Pleroma.ReverseProxy.Client
 end
diff --git a/mix.exs b/mix.exs
index 22d3d50dfbb15e9e9d120bb125fa012e411d582c..48a43fccb44e4c11c73a62d2db95c9bb10aea5f1 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -151,7 +151,8 @@ defmodule Pleroma.Mixfile do
       {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
       {:ex_rated, "~> 1.3"},
       {:plug_static_index_html, "~> 1.0.0"},
-      {:excoveralls, "~> 0.11.1", only: :test}
+      {:excoveralls, "~> 0.11.1", only: :test},
+      {:mox, "~> 0.5", only: :test}
     ] ++ oauth_deps()
   end
 
index cae8d7d8459713857e8e6df57b865d3df31cfa92..e711be635b801af7690f4ba9b4a181bae9aee6ca 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -52,6 +52,7 @@
   "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
   "mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
   "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
+  "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
   "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
   "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
   "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
-  "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
   "postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
   "prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
   "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
   "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
   "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
   "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
-  "prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
   "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
   "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
   "recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
diff --git a/test/http/request_builder_test.exs b/test/http/request_builder_test.exs
new file mode 100644 (file)
index 0000000..a368999
--- /dev/null
@@ -0,0 +1,91 @@
+defmodule Pleroma.HTTP.RequestBuilderTest do
+  use ExUnit.Case, async: true
+  alias Pleroma.HTTP.RequestBuilder
+
+  describe "headers/2" do
+    test "don't send pleroma user agent" do
+      assert RequestBuilder.headers(%{}, []) == %{headers: []}
+    end
+
+    test "send pleroma user agent" do
+      send = Pleroma.Config.get([:http, :send_user_agent])
+      Pleroma.Config.put([:http, :send_user_agent], true)
+
+      on_exit(fn ->
+        Pleroma.Config.put([:http, :send_user_agent], send)
+      end)
+
+      assert RequestBuilder.headers(%{}, []) == %{
+               headers: [{"User-Agent", Pleroma.Application.user_agent()}]
+             }
+    end
+  end
+
+  describe "add_optional_params/3" do
+    test "don't add if keyword is empty" do
+      assert RequestBuilder.add_optional_params(%{}, %{}, []) == %{}
+    end
+
+    test "add query parameter" do
+      assert RequestBuilder.add_optional_params(
+               %{},
+               %{query: :query, body: :body, another: :val},
+               [
+                 {:query, "param1=val1&param2=val2"},
+                 {:body, "some body"}
+               ]
+             ) == %{query: "param1=val1&param2=val2", body: "some body"}
+    end
+  end
+
+  describe "add_param/4" do
+    test "add file parameter" do
+      %{
+        body: %Tesla.Multipart{
+          boundary: _,
+          content_type_params: [],
+          parts: [
+            %Tesla.Multipart.Part{
+              body: %File.Stream{
+                line_or_bytes: 2048,
+                modes: [:raw, :read_ahead, :read, :binary],
+                path: "some-path/filename.png",
+                raw: true
+              },
+              dispositions: [name: "filename.png", filename: "filename.png"],
+              headers: []
+            }
+          ]
+        }
+      } = RequestBuilder.add_param(%{}, :file, "filename.png", "some-path/filename.png")
+    end
+
+    test "add key to body" do
+      %{
+        body: %Tesla.Multipart{
+          boundary: _,
+          content_type_params: [],
+          parts: [
+            %Tesla.Multipart.Part{
+              body: "\"someval\"",
+              dispositions: [name: "somekey"],
+              headers: ["Content-Type": "application/json"]
+            }
+          ]
+        }
+      } = RequestBuilder.add_param(%{}, :body, "somekey", "someval")
+    end
+
+    test "add form parameter" do
+      assert RequestBuilder.add_param(%{}, :form, "somename", "someval") == %{
+               body: %{"somename" => "someval"}
+             }
+    end
+
+    test "add for location" do
+      assert RequestBuilder.add_param(%{}, :some_location, "somekey", "someval") == %{
+               some_location: [{"somekey", "someval"}]
+             }
+    end
+  end
+end
diff --git a/test/reverse_proxy_test.exs b/test/reverse_proxy_test.exs
new file mode 100644 (file)
index 0000000..75a6144
--- /dev/null
@@ -0,0 +1,297 @@
+defmodule Pleroma.ReverseProxyTest do
+  use Pleroma.Web.ConnCase, async: true
+  import ExUnit.CaptureLog
+  import ExUnit.CaptureLog
+  import Mox
+  alias Pleroma.ReverseProxy
+  alias Pleroma.ReverseProxy.ClientMock
+
+  setup_all do
+    {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.ReverseProxy.ClientMock)
+    :ok
+  end
+
+  setup :verify_on_exit!
+
+  defp user_agent_mock(user_agent, invokes) do
+    json = Jason.encode!(%{"user-agent": user_agent})
+
+    ClientMock
+    |> expect(:request, fn :get, url, _, _, _ ->
+      Registry.register(Pleroma.ReverseProxy.ClientMock, url, 0)
+
+      {:ok, 200,
+       [
+         {"content-type", "application/json"},
+         {"content-length", byte_size(json) |> to_string()}
+       ], %{url: url}}
+    end)
+    |> expect(:stream_body, invokes, fn %{url: url} ->
+      case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
+        [{_, 0}] ->
+          Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
+          {:ok, json}
+
+        [{_, 1}] ->
+          Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
+          :done
+      end
+    end)
+  end
+
+  describe "user-agent" do
+    test "don't keep", %{conn: conn} do
+      user_agent_mock("hackney/1.15.1", 2)
+      conn = ReverseProxy.call(conn, "/user-agent")
+      assert json_response(conn, 200) == %{"user-agent" => "hackney/1.15.1"}
+    end
+
+    test "keep", %{conn: conn} do
+      user_agent_mock(Pleroma.Application.user_agent(), 2)
+      conn = ReverseProxy.call(conn, "/user-agent-keep", keep_user_agent: true)
+      assert json_response(conn, 200) == %{"user-agent" => Pleroma.Application.user_agent()}
+    end
+  end
+
+  test "closed connection", %{conn: conn} do
+    ClientMock
+    |> expect(:request, fn :get, "/closed", _, _, _ -> {:ok, 200, [], %{}} end)
+    |> expect(:stream_body, fn _ -> {:error, :closed} end)
+    |> expect(:close, fn _ -> :ok end)
+
+    conn = ReverseProxy.call(conn, "/closed")
+    assert conn.halted
+  end
+
+  describe "max_body " do
+    test "length returns error if content-length more than option", %{conn: conn} do
+      user_agent_mock("hackney/1.15.1", 0)
+
+      assert capture_log(fn ->
+               ReverseProxy.call(conn, "/user-agent", max_body_length: 4)
+             end) =~
+               "[error] Elixir.Pleroma.ReverseProxy: request to \"/user-agent\" failed: :body_too_large"
+    end
+
+    defp stream_mock(invokes, with_close? \\ false) do
+      ClientMock
+      |> expect(:request, fn :get, "/stream-bytes/" <> length, _, _, _ ->
+        Registry.register(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length, 0)
+
+        {:ok, 200, [{"content-type", "application/octet-stream"}],
+         %{url: "/stream-bytes/" <> length}}
+      end)
+      |> expect(:stream_body, invokes, fn %{url: "/stream-bytes/" <> length} ->
+        max = String.to_integer(length)
+
+        case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length) do
+          [{_, current}] when current < max ->
+            Registry.update_value(
+              Pleroma.ReverseProxy.ClientMock,
+              "/stream-bytes/" <> length,
+              &(&1 + 10)
+            )
+
+            {:ok, "0123456789"}
+
+          [{_, ^max}] ->
+            Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length)
+            :done
+        end
+      end)
+
+      if with_close? do
+        expect(ClientMock, :close, fn _ -> :ok end)
+      end
+    end
+
+    test "max_body_size returns error if streaming body more than that option", %{conn: conn} do
+      stream_mock(3, true)
+
+      assert capture_log(fn ->
+               ReverseProxy.call(conn, "/stream-bytes/50", max_body_size: 30)
+             end) =~
+               "[warn] Elixir.Pleroma.ReverseProxy request to /stream-bytes/50 failed while reading/chunking: :body_too_large"
+    end
+  end
+
+  describe "HEAD requests" do
+    test "common", %{conn: conn} do
+      ClientMock
+      |> expect(:request, fn :head, "/head", _, _, _ ->
+        {:ok, 200, [{"content-type", "text/html; charset=utf-8"}]}
+      end)
+
+      conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head")
+      assert html_response(conn, 200) == ""
+    end
+  end
+
+  defp error_mock(status) when is_integer(status) do
+    ClientMock
+    |> expect(:request, fn :get, "/status/" <> _, _, _, _ ->
+      {:error, status}
+    end)
+  end
+
+  describe "returns error on" do
+    test "500", %{conn: conn} do
+      error_mock(500)
+
+      capture_log(fn -> ReverseProxy.call(conn, "/status/500") end) =~
+        "[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500"
+    end
+
+    test "400", %{conn: conn} do
+      error_mock(400)
+
+      capture_log(fn -> ReverseProxy.call(conn, "/status/400") end) =~
+        "[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400"
+    end
+
+    test "204", %{conn: conn} do
+      ClientMock
+      |> expect(:request, fn :get, "/status/204", _, _, _ -> {:ok, 204, [], %{}} end)
+
+      capture_log(fn ->
+        conn = ReverseProxy.call(conn, "/status/204")
+        assert conn.resp_body == "Request failed: No Content"
+        assert conn.halted
+      end) =~
+        "[error] Elixir.Pleroma.ReverseProxy: request to \"/status/204\" failed with HTTP status 204"
+    end
+  end
+
+  test "streaming", %{conn: conn} do
+    stream_mock(21)
+    conn = ReverseProxy.call(conn, "/stream-bytes/200")
+    assert conn.state == :chunked
+    assert byte_size(conn.resp_body) == 200
+    assert Plug.Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"]
+  end
+
+  defp headers_mock(_) do
+    ClientMock
+    |> expect(:request, fn :get, "/headers", headers, _, _ ->
+      Registry.register(Pleroma.ReverseProxy.ClientMock, "/headers", 0)
+      {:ok, 200, [{"content-type", "application/json"}], %{url: "/headers", headers: headers}}
+    end)
+    |> expect(:stream_body, 2, fn %{url: url, headers: headers} ->
+      case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
+        [{_, 0}] ->
+          Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
+          headers = for {k, v} <- headers, into: %{}, do: {String.capitalize(k), v}
+          {:ok, Jason.encode!(%{headers: headers})}
+
+        [{_, 1}] ->
+          Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
+          :done
+      end
+    end)
+
+    :ok
+  end
+
+  describe "keep request headers" do
+    setup [:headers_mock]
+
+    test "header passes", %{conn: conn} do
+      conn =
+        Plug.Conn.put_req_header(
+          conn,
+          "accept",
+          "text/html"
+        )
+        |> ReverseProxy.call("/headers")
+
+      %{"headers" => headers} = json_response(conn, 200)
+      assert headers["Accept"] == "text/html"
+    end
+
+    test "header is filtered", %{conn: conn} do
+      conn =
+        Plug.Conn.put_req_header(
+          conn,
+          "accept-language",
+          "en-US"
+        )
+        |> ReverseProxy.call("/headers")
+
+      %{"headers" => headers} = json_response(conn, 200)
+      refute headers["Accept-Language"]
+    end
+  end
+
+  test "returns 400 on non GET, HEAD requests", %{conn: conn} do
+    conn = ReverseProxy.call(Map.put(conn, :method, "POST"), "/ip")
+    assert conn.status == 400
+  end
+
+  describe "cache resp headers" do
+    test "returns headers", %{conn: conn} do
+      ClientMock
+      |> expect(:request, fn :get, "/cache/" <> ttl, _, _, _ ->
+        {:ok, 200, [{"cache-control", "public, max-age=" <> ttl}], %{}}
+      end)
+      |> expect(:stream_body, fn _ -> :done end)
+
+      conn = ReverseProxy.call(conn, "/cache/10")
+      assert {"cache-control", "public, max-age=10"} in conn.resp_headers
+    end
+
+    test "add cache-control", %{conn: conn} do
+      ClientMock
+      |> expect(:request, fn :get, "/cache", _, _, _ ->
+        {:ok, 200, [{"ETag", "some ETag"}], %{}}
+      end)
+      |> expect(:stream_body, fn _ -> :done end)
+
+      conn = ReverseProxy.call(conn, "/cache")
+      assert {"cache-control", "public"} in conn.resp_headers
+    end
+  end
+
+  defp disposition_headers_mock(headers) do
+    ClientMock
+    |> expect(:request, fn :get, "/disposition", _, _, _ ->
+      Registry.register(Pleroma.ReverseProxy.ClientMock, "/disposition", 0)
+
+      {:ok, 200, headers, %{url: "/disposition"}}
+    end)
+    |> expect(:stream_body, 2, fn %{url: "/disposition"} ->
+      case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/disposition") do
+        [{_, 0}] ->
+          Registry.update_value(Pleroma.ReverseProxy.ClientMock, "/disposition", &(&1 + 1))
+          {:ok, ""}
+
+        [{_, 1}] ->
+          Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/disposition")
+          :done
+      end
+    end)
+  end
+
+  describe "response content disposition header" do
+    test "not atachment", %{conn: conn} do
+      disposition_headers_mock([
+        {"content-type", "image/gif"},
+        {"content-length", 0}
+      ])
+
+      conn = ReverseProxy.call(conn, "/disposition")
+
+      assert {"content-type", "image/gif"} in conn.resp_headers
+    end
+
+    test "with content-disposition header", %{conn: conn} do
+      disposition_headers_mock([
+        {"content-disposition", "attachment; filename=\"filename.jpg\""},
+        {"content-length", 0}
+      ])
+
+      conn = ReverseProxy.call(conn, "/disposition")
+
+      assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers
+    end
+  end
+end
diff --git a/test/tasks/ecto/ecto_test.exs b/test/tasks/ecto/ecto_test.exs
new file mode 100644 (file)
index 0000000..b48662c
--- /dev/null
@@ -0,0 +1,11 @@
+defmodule Mix.Tasks.Pleroma.EctoTest do
+  use ExUnit.Case, async: true
+
+  test "raise on bad path" do
+    assert_raise RuntimeError, ~r/Could not find migrations directory/, fn ->
+      Mix.Tasks.Pleroma.Ecto.ensure_migrations_path(Pleroma.Repo,
+        migrations_path: "some-path"
+      )
+    end
+  end
+end
diff --git a/test/tasks/pleroma_test.exs b/test/tasks/pleroma_test.exs
new file mode 100644 (file)
index 0000000..e236ccb
--- /dev/null
@@ -0,0 +1,46 @@
+defmodule Mix.PleromaTest do
+  use ExUnit.Case, async: true
+  import Mix.Pleroma
+
+  setup_all do
+    Mix.shell(Mix.Shell.Process)
+
+    on_exit(fn ->
+      Mix.shell(Mix.Shell.IO)
+    end)
+
+    :ok
+  end
+
+  describe "shell_prompt/1" do
+    test "input" do
+      send(self(), {:mix_shell_input, :prompt, "Yes"})
+
+      answer = shell_prompt("Do you want this?")
+      assert_received {:mix_shell, :prompt, [message]}
+      assert message =~ "Do you want this?"
+      assert answer == "Yes"
+    end
+
+    test "with defval" do
+      send(self(), {:mix_shell_input, :prompt, "\n"})
+
+      answer = shell_prompt("Do you want this?", "defval")
+
+      assert_received {:mix_shell, :prompt, [message]}
+      assert message =~ "Do you want this? [defval]"
+      assert answer == "defval"
+    end
+  end
+
+  describe "get_option/3" do
+    test "get from options" do
+      assert get_option([domain: "some-domain.com"], :domain, "Promt") == "some-domain.com"
+    end
+
+    test "get from prompt" do
+      send(self(), {:mix_shell_input, :prompt, "another-domain.com"})
+      assert get_option([], :domain, "Prompt") == "another-domain.com"
+    end
+  end
+end
diff --git a/test/tasks/robots_txt_test.exs b/test/tasks/robots_txt_test.exs
new file mode 100644 (file)
index 0000000..539193f
--- /dev/null
@@ -0,0 +1,43 @@
+defmodule Mix.Tasks.Pleroma.RobotsTxtTest do
+  use ExUnit.Case, async: true
+  alias Mix.Tasks.Pleroma.RobotsTxt
+
+  test "creates new dir" do
+    path = "test/fixtures/new_dir/"
+    file_path = path <> "robots.txt"
+
+    static_dir = Pleroma.Config.get([:instance, :static_dir])
+    Pleroma.Config.put([:instance, :static_dir], path)
+
+    on_exit(fn ->
+      Pleroma.Config.put([:instance, :static_dir], static_dir)
+      {:ok, ["test/fixtures/new_dir/", "test/fixtures/new_dir/robots.txt"]} = File.rm_rf(path)
+    end)
+
+    RobotsTxt.run(["disallow_all"])
+
+    assert File.exists?(file_path)
+    {:ok, file} = File.read(file_path)
+
+    assert file == "User-Agent: *\nDisallow: /\n"
+  end
+
+  test "to existance folder" do
+    path = "test/fixtures/"
+    file_path = path <> "robots.txt"
+    static_dir = Pleroma.Config.get([:instance, :static_dir])
+    Pleroma.Config.put([:instance, :static_dir], path)
+
+    on_exit(fn ->
+      Pleroma.Config.put([:instance, :static_dir], static_dir)
+      :ok = File.rm(file_path)
+    end)
+
+    RobotsTxt.run(["disallow_all"])
+
+    assert File.exists?(file_path)
+    {:ok, file} = File.read(file_path)
+
+    assert file == "User-Agent: *\nDisallow: /\n"
+  end
+end
index f604ba63d2c13fa138b1d530559f8a86be6f44d2..3e33f0335a89c03237346c421c134624591c92c2 100644 (file)
@@ -3,6 +3,6 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 ExUnit.start()
-
 Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
+Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)
 {:ok, _} = Application.ensure_all_started(:ex_machina)