X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=test%2Fpleroma%2Freverse_proxy_test.exs;h=9cbe06f2192af5c0c563e4b3c779f27c37bfc193;hb=1f863f0a36ebb0648c3d39ecb7ea9e3d01deab60;hp=0a2c169cef8969119d69182e090df62fb988203b;hpb=e4f1d8f48c85b8a388d6c3945db157de5ce588c5;p=akkoma diff --git a/test/pleroma/reverse_proxy_test.exs b/test/pleroma/reverse_proxy_test.exs index 0a2c169ce..9cbe06f21 100644 --- a/test/pleroma/reverse_proxy_test.exs +++ b/test/pleroma/reverse_proxy_test.exs @@ -1,120 +1,61 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors +# Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.ReverseProxyTest do use Pleroma.Web.ConnCase import ExUnit.CaptureLog - import Mox alias Pleroma.ReverseProxy - alias Pleroma.ReverseProxy.ClientMock alias Plug.Conn - setup_all do - {:ok, _} = Registry.start_link(keys: :unique, name: 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(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} = client -> - case Registry.lookup(ClientMock, url) do - [{_, 0}] -> - Registry.update_value(ClientMock, url, &(&1 + 1)) - {:ok, json, client} - - [{_, 1}] -> - Registry.unregister(ClientMock, url) - :done - end - end) - end - describe "reverse proxy" do test "do not track successful request", %{conn: conn} do - user_agent_mock("hackney/1.15.1", 2) url = "/success" + Tesla.Mock.mock(fn %{url: ^url} -> + %Tesla.Env{ + status: 200, + body: "" + } + end) + conn = ReverseProxy.call(conn, url) - assert conn.status == 200 + assert response(conn, 200) assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, nil} 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 "use Pleroma's user agent in the request; don't pass the client's", %{conn: conn} do + clear_config([:http, :send_user_agent], true) + # Mock will fail if the client's user agent isn't filtered + wanted_headers = [{"user-agent", Pleroma.Application.user_agent()}] - 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 - - defp stream_mock(invokes, with_close? \\ false) do - ClientMock - |> expect(:request, fn :get, "/stream-bytes/" <> length, _, _, _ -> - Registry.register(ClientMock, "/stream-bytes/" <> length, 0) + Tesla.Mock.mock(fn %{url: "/user-agent", headers: ^wanted_headers} -> + %Tesla.Env{ + status: 200, + body: "" + } + end) - {:ok, 200, [{"content-type", "application/octet-stream"}], - %{url: "/stream-bytes/" <> length}} - end) - |> expect(:stream_body, invokes, fn %{url: "/stream-bytes/" <> length} = client -> - max = String.to_integer(length) - - case Registry.lookup(ClientMock, "/stream-bytes/" <> length) do - [{_, current}] when current < max -> - Registry.update_value( - ClientMock, - "/stream-bytes/" <> length, - &(&1 + 10) - ) - - {:ok, "0123456789", client} - - [{_, ^max}] -> - Registry.unregister(ClientMock, "/stream-bytes/" <> length) - :done - end - end) + conn = + conn + |> Plug.Conn.put_req_header("user-agent", "fake/1.0") + |> ReverseProxy.call("/user-agent") - if with_close? do - expect(ClientMock, :close, fn _ -> :ok end) + assert response(conn, 200) end 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) + Tesla.Mock.mock(fn %{url: "/huge-file"} -> + %Tesla.Env{ + status: 200, + headers: [{"content-length", "100"}], + body: "This body is too large." + } + end) assert capture_log(fn -> ReverseProxy.call(conn, "/huge-file", max_body_length: 4) @@ -127,22 +68,16 @@ defmodule Pleroma.ReverseProxyTest do ReverseProxy.call(conn, "/huge-file", max_body_length: 4) end) == "" end - - test "max_body_length 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_length: 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"}]} + Tesla.Mock.mock(fn %{method: :head, url: "/head"} -> + %Tesla.Env{ + status: 200, + headers: [{"content-type", "text/html; charset=utf-8"}], + body: "" + } end) conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head") @@ -150,18 +85,17 @@ defmodule Pleroma.ReverseProxyTest do 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) url = "/status/500" + Tesla.Mock.mock(fn %{url: ^url} -> + %Tesla.Env{ + status: 500, + body: "" + } + end) + capture_log(fn -> ReverseProxy.call(conn, url) end) =~ "[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500" @@ -172,9 +106,15 @@ defmodule Pleroma.ReverseProxyTest do end test "400", %{conn: conn} do - error_mock(400) url = "/status/400" + Tesla.Mock.mock(fn %{url: ^url} -> + %Tesla.Env{ + status: 400, + body: "" + } + end) + capture_log(fn -> ReverseProxy.call(conn, url) end) =~ "[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400" @@ -183,9 +123,15 @@ defmodule Pleroma.ReverseProxyTest do end test "403", %{conn: conn} do - error_mock(403) url = "/status/403" + Tesla.Mock.mock(fn %{url: ^url} -> + %Tesla.Env{ + status: 403, + body: "" + } + end) + capture_log(fn -> ReverseProxy.call(conn, url, failed_request_ttl: :timer.seconds(120)) end) =~ @@ -194,57 +140,17 @@ defmodule Pleroma.ReverseProxyTest do {:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url) assert ttl > 100_000 end - - test "204", %{conn: conn} do - url = "/status/204" - expect(ClientMock, :request, fn :get, _url, _, _, _ -> {:ok, 204, [], %{}} end) - - capture_log(fn -> - conn = ReverseProxy.call(conn, url) - 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" - - assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true} - assert Cachex.ttl(:failed_proxy_url_cache, url) == {:ok, nil} - 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 Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"] - end - - defp headers_mock(_) do - ClientMock - |> expect(:request, fn :get, "/headers", headers, _, _ -> - Registry.register(ClientMock, "/headers", 0) - {:ok, 200, [{"content-type", "application/json"}], %{url: "/headers", headers: headers}} - end) - |> expect(:stream_body, 2, fn %{url: url, headers: headers} = client -> - case Registry.lookup(ClientMock, url) do - [{_, 0}] -> - Registry.update_value(ClientMock, url, &(&1 + 1)) - headers = for {k, v} <- headers, into: %{}, do: {String.capitalize(k), v} - {:ok, Jason.encode!(%{headers: headers}), client} - - [{_, 1}] -> - Registry.unregister(ClientMock, url) - :done - end - end) - - :ok end describe "keep request headers" do - setup [:headers_mock] - test "header passes", %{conn: conn} do + Tesla.Mock.mock(fn %{url: "/headers"} -> + %Tesla.Env{ + status: 200, + body: "" + } + end) + conn = Conn.put_req_header( conn, @@ -253,68 +159,76 @@ defmodule Pleroma.ReverseProxyTest do ) |> ReverseProxy.call("/headers") - %{"headers" => headers} = json_response(conn, 200) - assert headers["Accept"] == "text/html" + assert response(conn, 200) + assert {"accept", "text/html"} in conn.req_headers end test "header is filtered", %{conn: conn} do + # Mock will fail if the accept-language header isn't filtered + wanted_headers = [{"accept-encoding", "*"}] + + Tesla.Mock.mock(fn %{url: "/headers", headers: ^wanted_headers} -> + %Tesla.Env{ + status: 200, + body: "" + } + end) + conn = - Conn.put_req_header( - conn, - "accept-language", - "en-US" - ) + conn + |> Conn.put_req_header("accept-language", "en-US") + |> Conn.put_req_header("accept-encoding", "*") |> ReverseProxy.call("/headers") - %{"headers" => headers} = json_response(conn, 200) - refute headers["Accept-Language"] + assert response(conn, 200) end end test "returns 400 on non GET, HEAD requests", %{conn: conn} do + Tesla.Mock.mock(fn %{url: "/ip"} -> + %Tesla.Env{ + status: 200, + body: "" + } + end) + conn = ReverseProxy.call(Map.put(conn, :method, "POST"), "/ip") - assert conn.status == 400 + assert response(conn, 400) end - describe "cache resp headers" do + describe "cache resp headers not filtered" do test "add cache-control", %{conn: conn} do - ClientMock - |> expect(:request, fn :get, "/cache", _, _, _ -> - {:ok, 200, [{"ETag", "some ETag"}], %{}} + Tesla.Mock.mock(fn %{url: "/cache"} -> + %Tesla.Env{ + status: 200, + headers: [ + {"cache-control", "public, max-age=1209600"}, + {"etag", "some ETag"}, + {"expires", "Wed, 21 Oct 2015 07:28:00 GMT"} + ], + body: "" + } end) - |> expect(:stream_body, fn _ -> :done end) conn = ReverseProxy.call(conn, "/cache") assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers + assert {"etag", "some ETag"} in conn.resp_headers + assert {"expires", "Wed, 21 Oct 2015 07:28:00 GMT"} in conn.resp_headers end end - defp disposition_headers_mock(headers) do - ClientMock - |> expect(:request, fn :get, "/disposition", _, _, _ -> - Registry.register(ClientMock, "/disposition", 0) - - {:ok, 200, headers, %{url: "/disposition"}} - end) - |> expect(:stream_body, 2, fn %{url: "/disposition"} = client -> - case Registry.lookup(ClientMock, "/disposition") do - [{_, 0}] -> - Registry.update_value(ClientMock, "/disposition", &(&1 + 1)) - {:ok, "", client} - - [{_, 1}] -> - Registry.unregister(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"} - ]) + test "not attachment", %{conn: conn} do + Tesla.Mock.mock(fn %{url: "/disposition"} -> + %Tesla.Env{ + status: 200, + headers: [ + {"content-type", "image/gif"}, + {"content-length", "0"} + ], + body: "" + } + end) conn = ReverseProxy.call(conn, "/disposition") @@ -322,10 +236,16 @@ defmodule Pleroma.ReverseProxyTest do end test "with content-disposition header", %{conn: conn} do - disposition_headers_mock([ - {"content-disposition", "attachment; filename=\"filename.jpg\""}, - {"content-length", "0"} - ]) + Tesla.Mock.mock(fn %{url: "/disposition"} -> + %Tesla.Env{ + status: 200, + headers: [ + {"content-disposition", "attachment; filename=\"filename.jpg\""}, + {"content-length", "0"} + ], + body: "" + } + end) conn = ReverseProxy.call(conn, "/disposition")