add tests

This commit is contained in:
mmacia committed 2026-06-04 20:23:58 +00:00
1 parent 2df2aedb68
commit 0cddbfc76f
3 files changed
+172 -1

No files matched your search

+150
View File
@@ -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
+13
View File
@@ -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
+9 -1
View File
@@ -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