add tests
This commit is contained in:
1 parent
1640417cb5
commit
8908a6adf0
6 files changed
+194
No files matched your search
@@ -31,6 +31,7 @@ defmodule FeedReader.MixProject do
|
|||||||
{:date_time_parser, "~> 1.3.0"},
|
{:date_time_parser, "~> 1.3.0"},
|
||||||
{:ex_slop, "~> 0.4", only: :dev},
|
{:ex_slop, "~> 0.4", only: :dev},
|
||||||
{:html_sanitize_ex, "~> 1.5.5"},
|
{:html_sanitize_ex, "~> 1.5.5"},
|
||||||
|
{:plug, "~> 1.0", only: :test},
|
||||||
{:req, "~> 0.7.5"},
|
{:req, "~> 0.7.5"},
|
||||||
{:saxy, "~> 1.6"},
|
{:saxy, "~> 1.6"},
|
||||||
{:tongue, "~> 2.3.0"}
|
{:tongue, "~> 2.3.0"}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
"mochiweb": {:hex, :mochiweb, "3.5.0", "a6de5babf2832b5cc8ba3f9e70417f54a99bc4cec5ac9aff4b783a643d36d804", [:rebar3], [], "hexpm", "c35df2787fffd009ab2f2470768ad1cdc993eab2c68cd7ddf75171c1462265d6"},
|
"mochiweb": {:hex, :mochiweb, "3.5.0", "a6de5babf2832b5cc8ba3f9e70417f54a99bc4cec5ac9aff4b783a643d36d804", [:rebar3], [], "hexpm", "c35df2787fffd009ab2f2470768ad1cdc993eab2c68cd7ddf75171c1462265d6"},
|
||||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||||
|
"plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"},
|
||||||
|
"plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"},
|
||||||
"req": {:hex, :req, "0.7.5", "0c3293cbf7717aba20b1b1b3ea13fe8d3fdf6f1557490f390c3f1ef6dd9db955", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "576853c4bf403faa3c1fa5d093e0759227083b78f8a6864c67d62c0a9c45514d"},
|
"req": {:hex, :req, "0.7.5", "0c3293cbf7717aba20b1b1b3ea13fe8d3fdf6f1557490f390c3f1ef6dd9db955", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "576853c4bf403faa3c1fa5d093e0759227083b78f8a6864c67d62c0a9c45514d"},
|
||||||
"saxy": {:hex, :saxy, "1.6.1", "742eff28f553c066d0b54e84662dbf384a1d1f38595472ed15f6e0a33038bbe1", [:mix], [], "hexpm", "8989d504424ba29460a61950f8968380651413fa05e63b6118084db057da1a6b"},
|
"saxy": {:hex, :saxy, "1.6.1", "742eff28f553c066d0b54e84662dbf384a1d1f38595472ed15f6e0a33038bbe1", [:mix], [], "hexpm", "8989d504424ba29460a61950f8968380651413fa05e63b6118084db057da1a6b"},
|
||||||
"telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},
|
"telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
defmodule FeedReader.TransportTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
|
||||||
|
alias FeedReader.{ReqTransport, Transport}
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Req.Test.stub(__MODULE__, fn conn -> Plug.Conn.send_resp(conn, 200, "body") end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "dispatches through an injectable client" do
|
||||||
|
assert {:ok, %{body: ["test"]}} =
|
||||||
|
Transport.request("https://example.com/feed",
|
||||||
|
http_client: FeedReader.TestSupport.TransportClient,
|
||||||
|
test_pid: self()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_receive {:transport_request, "https://example.com/feed"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "uses the default Req client when no client is injected" do
|
||||||
|
assert {:error, :invalid_url} = Transport.request(123)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "passes an invalid option list through to the default client" do
|
||||||
|
assert {:error, :invalid_url} = Transport.request(:invalid, invalid: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects an invalid URL through the direct default client" do
|
||||||
|
assert {:error, :invalid_url} = ReqTransport.request(123)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns a lazy raw response with headers and the final URL" do
|
||||||
|
Req.Test.stub(__MODULE__, fn conn ->
|
||||||
|
assert Plug.Conn.get_req_header(conn, "user-agent") == ["feedex/1"]
|
||||||
|
assert Plug.Conn.get_req_header(conn, "accept-encoding") == ["gzip, deflate, identity"]
|
||||||
|
Plug.Conn.send_resp(conn, 200, "body")
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, response} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.final_url == "https://example.com/feed"
|
||||||
|
assert Enum.join(response.body) == "body"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "follows redirects and reports the final URL" do
|
||||||
|
Req.Test.expect(__MODULE__, 2, fn conn ->
|
||||||
|
case conn.request_path do
|
||||||
|
"/redirect" ->
|
||||||
|
Plug.Conn.put_resp_header(conn, "location", "/final") |> Plug.Conn.send_resp(302, "")
|
||||||
|
|
||||||
|
"/final" ->
|
||||||
|
Plug.Conn.send_resp(conn, 200, "body")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, %{final_url: "https://example.com/final"}} =
|
||||||
|
ReqTransport.request("https://example.com/redirect",
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "retries status 500 and maps the terminal status" do
|
||||||
|
Req.Test.expect(__MODULE__, 3, fn conn -> Plug.Conn.send_resp(conn, 500, "error") end)
|
||||||
|
|
||||||
|
assert {:error, :internal_server_error} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_retry_attempts: 2,
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "maps typed status errors without retrying" do
|
||||||
|
for {status, reason} <- [{403, :forbidden}, {404, :not_found}, {429, :too_many_requests}] do
|
||||||
|
Req.Test.expect(__MODULE__, fn conn -> Plug.Conn.send_resp(conn, status, "error") end)
|
||||||
|
|
||||||
|
assert {:error, ^reason} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns an error tuple for an unmapped status" do
|
||||||
|
Req.Test.expect(__MODULE__, fn conn -> Plug.Conn.send_resp(conn, 418, "teapot") end)
|
||||||
|
|
||||||
|
assert {:error, {:http_status, 418}} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "retries transport failures" do
|
||||||
|
Req.Test.expect(__MODULE__, 2, &Req.Test.transport_error(&1, :econnrefused))
|
||||||
|
|
||||||
|
assert {:error, :econnrefused} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_retry_attempts: 1,
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not retry TLS alerts and returns the alert reason" do
|
||||||
|
Req.Test.expect(__MODULE__, fn conn ->
|
||||||
|
Req.Test.transport_error(conn, {:tls_alert, {:handshake_failure, "bad certificate"}})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:error, :handshake_failure} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not retry after the redirect limit" do
|
||||||
|
Req.Test.expect(__MODULE__, 6, fn conn ->
|
||||||
|
conn
|
||||||
|
|> Plug.Conn.put_resp_header("location", "/loop")
|
||||||
|
|> Plug.Conn.send_resp(302, "")
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:error, %Req.TooManyRedirectsError{max_redirects: 5}} =
|
||||||
|
ReqTransport.request("https://example.com/loop",
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "uses fallback values for invalid retry options" do
|
||||||
|
Req.Test.expect(__MODULE__, 6, fn conn -> Plug.Conn.send_resp(conn, 500, "error") end)
|
||||||
|
|
||||||
|
assert {:error, :internal_server_error} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_retry_attempts: :invalid,
|
||||||
|
retry_delay: :invalid,
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "accepts a retry delay function" do
|
||||||
|
Req.Test.expect(__MODULE__, 2, fn conn -> Plug.Conn.send_resp(conn, 500, "error") end)
|
||||||
|
|
||||||
|
assert {:error, :internal_server_error} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_retry_attempts: 1,
|
||||||
|
retry_delay: fn _attempt -> 0 end,
|
||||||
|
http_options: [plug: {Req.Test, __MODULE__}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns generic Req errors without retrying" do
|
||||||
|
assert {:error, %Req.HTTPError{reason: :unexpected}} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_options: [adapter: FeedReader.TestSupport.ReqErrorAdapter]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns redirect errors without retrying" do
|
||||||
|
assert {:error, %Req.TooManyRedirectsError{max_redirects: 5}} =
|
||||||
|
ReqTransport.request("https://example.com/feed",
|
||||||
|
http_options: [adapter: FeedReader.TestSupport.RedirectErrorAdapter]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
defmodule FeedReader.TestSupport.RedirectErrorAdapter do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
def run(request) do
|
||||||
|
{request, %Req.TooManyRedirectsError{max_redirects: 5}}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
defmodule FeedReader.TestSupport.ReqErrorAdapter do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
def run(request) do
|
||||||
|
{request, %Req.HTTPError{protocol: :http1, reason: :unexpected}}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
defmodule FeedReader.TestSupport.TransportClient do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
@behaviour FeedReader.Transport
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def request(url, opts) do
|
||||||
|
send(opts[:test_pid], {:transport_request, url})
|
||||||
|
{:ok, %{status: 200, headers: [], final_url: url, body: ["test"]}}
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user