add tests
This commit is contained in:
1 parent
2df2aedb68
commit
0cddbfc76f
3 files changed
+172
-1
No files matched your search
@@ -0,0 +1,150 @@
|
||||
defmodule FeedReader.DownloaderTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias FeedReader.Downloader
|
||||
|
||||
test "downloads decoded bytes, computes a lowercase MD5, and releases its file" do
|
||||
payload = "<rss>feed</rss>"
|
||||
response = response([payload], [{"ETag", "\"v1\""}, {"content-encoding", "identity"}])
|
||||
|
||||
assert {:ok, %{path: path, fetch: fetch}} = download(response, max_decoded_bytes: :invalid)
|
||||
assert File.read!(path) == payload
|
||||
assert fetch.checksum == Base.encode16(:crypto.hash(:md5, payload), case: :lower)
|
||||
assert fetch.etag == "\"v1\""
|
||||
assert fetch.status == 200
|
||||
assert fetch.final_url == "https://example.com/final.xml"
|
||||
assert :ok = Downloader.close(%{path: path})
|
||||
refute File.exists?(path)
|
||||
assert :ok = Downloader.close(%{path: path})
|
||||
end
|
||||
|
||||
test "decompresses gzip before writing and checksumming" do
|
||||
payload = "<rss><channel><title>feed</title></channel></rss>"
|
||||
compressed = :zlib.gzip(payload)
|
||||
midpoint = div(byte_size(compressed), 2)
|
||||
|
||||
chunks = [
|
||||
binary_part(compressed, 0, midpoint),
|
||||
binary_part(compressed, midpoint, byte_size(compressed) - midpoint)
|
||||
]
|
||||
|
||||
response = response(chunks, [{"content-encoding", "gzip"}])
|
||||
|
||||
assert {:ok, %{path: path, fetch: fetch}} = download(response)
|
||||
assert File.read!(path) == payload
|
||||
assert fetch.checksum == Base.encode16(:crypto.hash(:md5, payload), case: :lower)
|
||||
Downloader.close(%{path: path})
|
||||
end
|
||||
|
||||
test "decompresses raw deflate content" do
|
||||
payload = "<rss>deflate</rss>"
|
||||
compressed = deflate(payload)
|
||||
|
||||
assert {:ok, %{path: path, fetch: fetch}} =
|
||||
download(response([compressed], [{"content-encoding", "deflate"}]))
|
||||
|
||||
assert File.read!(path) == payload
|
||||
assert fetch.checksum == checksum(payload)
|
||||
Downloader.close(%{path: path})
|
||||
end
|
||||
|
||||
test "maps 304 without creating a temporary file" do
|
||||
before = download_files()
|
||||
assert {:ok, :not_modified} = download(response([], [], 304))
|
||||
assert download_files() == before
|
||||
end
|
||||
|
||||
test "returns not changed after checksum and removes the temporary file" do
|
||||
payload = "unchanged"
|
||||
before = download_files()
|
||||
|
||||
assert {:ok, :not_changed} =
|
||||
download(response([payload], []), previous_checksum: checksum(payload))
|
||||
|
||||
assert download_files() == before
|
||||
end
|
||||
|
||||
test "enforces compressed and decoded byte limits and cleans failed downloads" do
|
||||
before = download_files()
|
||||
|
||||
assert {:error, {:resource_limit, :max_compressed_bytes, 2}} =
|
||||
download(response(["long"], []), max_compressed_bytes: 2)
|
||||
|
||||
assert {:error, {:resource_limit, :max_decoded_bytes, 2}} =
|
||||
download(response(["long"], []), max_decoded_bytes: 2)
|
||||
|
||||
assert {:error, {:resource_limit, :max_temp_bytes, 2}} =
|
||||
download(response(["long"], []), max_temp_bytes: 2)
|
||||
|
||||
compressed = :zlib.gzip(String.duplicate("a", 100))
|
||||
|
||||
assert {:error, {:resource_limit, :max_expansion_ratio, 1}} =
|
||||
download(response([compressed], [{"content-encoding", "gzip"}]),
|
||||
max_expansion_ratio: 1
|
||||
)
|
||||
|
||||
assert download_files() == before
|
||||
end
|
||||
|
||||
test "returns stable errors for invalid URLs, statuses, transport errors, and chunks" do
|
||||
assert {:error, :invalid_url} = Downloader.download(:invalid, [])
|
||||
assert {:error, {:http_status, 418}} = download(response([], [], 418))
|
||||
assert {:error, :offline} = download(response([], []), request_result: {:error, :offline})
|
||||
assert {:error, :invalid_chunk} = download(response([:not_binary], []))
|
||||
end
|
||||
|
||||
test "rejects unknown encodings and malformed compressed bodies, cleaning temp files" do
|
||||
before = download_files()
|
||||
|
||||
assert {:error, :unsupported_content_encoding} =
|
||||
download(response(["body"], [{"content-encoding", "br, gzip"}]))
|
||||
|
||||
assert {:error, :invalid_compressed_body} =
|
||||
download(response(["not gzip"], [{"content-encoding", "gzip"}]))
|
||||
|
||||
assert download_files() == before
|
||||
end
|
||||
|
||||
test "converts exceptions while enumerating response chunks to download errors" do
|
||||
before = download_files()
|
||||
chunks = Stream.map(["chunk"], fn _chunk -> raise "chunk read failed" end)
|
||||
|
||||
assert {:error, {:download_failed, %RuntimeError{message: "chunk read failed"}}} =
|
||||
download(response(chunks, []))
|
||||
|
||||
assert download_files() == before
|
||||
end
|
||||
|
||||
defp download(response, opts \\ []) do
|
||||
Downloader.download(
|
||||
"https://example.com/feed.xml",
|
||||
[
|
||||
http_client: FeedReader.TestSupport.TransportClient,
|
||||
response: response,
|
||||
test_pid: self()
|
||||
] ++
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
defp response(body, headers, status \\ 200) do
|
||||
%{status: status, headers: headers, final_url: "https://example.com/final.xml", body: body}
|
||||
end
|
||||
|
||||
defp checksum(payload), do: Base.encode16(:crypto.hash(:md5, payload), case: :lower)
|
||||
|
||||
defp deflate(payload) do
|
||||
zlib = :zlib.open()
|
||||
:ok = :zlib.deflateInit(zlib, :default, :deflated, -15, 8, :default)
|
||||
compressed = :zlib.deflate(zlib, payload, :finish) |> IO.iodata_to_binary()
|
||||
:ok = :zlib.deflateEnd(zlib)
|
||||
:zlib.close(zlib)
|
||||
compressed
|
||||
end
|
||||
|
||||
defp download_files do
|
||||
System.tmp_dir!()
|
||||
|> Path.join("feed_reader-download-*.xml")
|
||||
|> Path.wildcard()
|
||||
end
|
||||
end
|
||||
@@ -47,6 +47,19 @@ defmodule FeedReader.TransportTest do
|
||||
assert Enum.join(response.body) == "body"
|
||||
end
|
||||
|
||||
test "sends previous ETags verbatim and exposes a 304 response" do
|
||||
Req.Test.expect(__MODULE__, fn conn ->
|
||||
assert Plug.Conn.get_req_header(conn, "if-none-match") == ["W/\"exact\""]
|
||||
Plug.Conn.send_resp(conn, 304, "")
|
||||
end)
|
||||
|
||||
assert {:ok, %{status: 304, body: []}} =
|
||||
ReqTransport.request("https://example.com/feed",
|
||||
previous_etag: "W/\"exact\"",
|
||||
http_options: [plug: {Req.Test, __MODULE__}]
|
||||
)
|
||||
end
|
||||
|
||||
test "follows redirects and reports the final URL" do
|
||||
Req.Test.expect(__MODULE__, 2, fn conn ->
|
||||
case conn.request_path do
|
||||
|
||||
@@ -6,6 +6,14 @@ defmodule FeedReader.TestSupport.TransportClient do
|
||||
@impl true
|
||||
def request(url, opts) do
|
||||
send(opts[:test_pid], {:transport_request, url})
|
||||
{:ok, %{status: 200, headers: [], final_url: url, body: ["test"]}}
|
||||
|
||||
case Keyword.get(opts, :request_result) do
|
||||
nil ->
|
||||
{:ok,
|
||||
Keyword.get(opts, :response, %{status: 200, headers: [], final_url: url, body: ["test"]})}
|
||||
|
||||
result ->
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user