From c77bd48f953d9e501d44cd316aa3209490bce392 Mon Sep 17 00:00:00 2001 From: Moises Macia Date: Fri, 24 Apr 2026 21:09:51 +0000 Subject: [PATCH] add tests --- test/feed_reader/streaming_test.exs | 160 ++++++++++++++++++++++++++++ test/support/parser_halt_format.ex | 24 +++++ test/support/parser_halt_handler.ex | 6 ++ 3 files changed, 190 insertions(+) create mode 100644 test/feed_reader/streaming_test.exs create mode 100644 test/support/parser_halt_format.ex create mode 100644 test/support/parser_halt_handler.ex diff --git a/test/feed_reader/streaming_test.exs b/test/feed_reader/streaming_test.exs new file mode 100644 index 0000000..a446ef6 --- /dev/null +++ b/test/feed_reader/streaming_test.exs @@ -0,0 +1,160 @@ +defmodule FeedReader.StreamingTest do + use ExUnit.Case, async: true + + alias FeedReader.{Feed, Item, Session} + + @xml ~s(Examplehttps://example.com/FirstoneSecondtwo) + @atom_xml ~s(Atom) + @rdf_xml ~s(RDF) + + test "emits feed metadata before items in one local pass" do + path = write_temp(@xml) + + on_exit(fn -> File.rm(path) end) + + assert {:ok, %Session{fetch: nil, events: events}} = FeedReader.stream_file(path) + + assert [ + {:feed, %Feed{title: "Example"}}, + {:item, %Item{uuid: "one"}}, + {:item, %Item{uuid: "two"}} + ] = Enum.to_list(events) + + assert File.read!(path) == @xml + end + + test "delivers a malformed tail as a terminal error after earlier events" do + xml = @xml <> " File.rm(path) end) + + assert {:ok, %{events: events}} = FeedReader.stream_file(path) + events = Enum.to_list(events) + + assert {:feed, %Feed{}} = Enum.at(events, 0) + assert {:item, %Item{uuid: "one"}} = Enum.at(events, 1) + assert {:item, %Item{uuid: "two"}} = Enum.at(events, 2) + assert {:error, _reason} = Enum.at(events, 3) + assert length(events) == 4 + assert File.read!(path) == xml + end + + test "rejects an unknown local format before creating a session" do + path = write_temp("") + on_exit(fn -> File.rm(path) end) + + assert {:error, :unknown} = FeedReader.stream_file(path) + assert File.read!(path) == "" + end + + test "validates local paths and accepts an explicitly selected format module" do + assert {:error, :invalid_path} = FeedReader.stream_file(123) + assert {:error, :enoent} = FeedReader.stream_file("/path/that/does/not/exist") + + assert {:error, :not_a_file} = FeedReader.stream_file(System.tmp_dir!()) + + path = write_temp(@xml) + on_exit(fn -> File.rm(path) end) + + assert {:ok, %{events: events}} = + FeedReader.stream_file(path, format: FeedReader.Formats.RSS) + + assert {:feed, %Feed{type: :rss}} = Enum.at(Enum.to_list(events), 0) + end + + test "accepts explicit built-in Atom and RDF format IDs" do + atom_path = write_temp(@atom_xml) + rdf_path = write_temp(@rdf_xml) + + on_exit(fn -> + File.rm(atom_path) + File.rm(rdf_path) + end) + + assert {:ok, %{events: atom_events}} = FeedReader.stream_file(atom_path, format: :atom) + assert [{:feed, %Feed{title: "Atom", type: :atom}}] = Enum.to_list(atom_events) + + assert {:ok, %{events: rdf_events}} = FeedReader.stream_file(rdf_path, format: :rdf) + assert [{:feed, %Feed{title: "RDF", type: :rdf}}] = Enum.to_list(rdf_events) + end + + test "resolves a custom format by its ID" do + path = write_temp("") + on_exit(fn -> File.rm(path) end) + + assert {:ok, %{events: events}} = + FeedReader.stream_file(path, + format: :external_test, + formats: [FeedReader.TestSupport.ExternalFormat] + ) + + assert [] = Enum.to_list(events) + end + + test "uses an explicit format even when detection is disabled" do + path = write_temp(@xml) + on_exit(fn -> File.rm(path) end) + + assert {:ok, %{events: events}} = + FeedReader.stream_file(path, format: :rss, max_detection_bytes: 0) + + assert {:feed, %Feed{type: :rss}} = Enum.at(Enum.to_list(events), 0) + end + + test "closes the parser when the consumer stops early" do + path = write_temp(@xml) + on_exit(fn -> File.rm(path) end) + + assert {:ok, %{events: events}} = FeedReader.stream_file(path) + assert [{:feed, %Feed{}}] = events |> Stream.take(1) |> Enum.to_list() + assert File.read!(path) == @xml + end + + test "returns a stable error for a non-atom format option" do + path = write_temp(@xml) + on_exit(fn -> File.rm(path) end) + + assert {:error, :unknown} = FeedReader.stream_file(path, format: "rss") + end + + test "treats an invalid detection limit as an unknown format" do + path = write_temp(@xml) + on_exit(fn -> File.rm(path) end) + + assert {:error, :unknown} = FeedReader.stream_file(path, max_detection_bytes: 0) + end + + test "delivers a parser halt as a terminal error event" do + path = write_temp(@xml) + on_exit(fn -> File.rm(path) end) + + assert {:ok, %{events: events}} = + FeedReader.stream_file( + path, + format: :parser_halt, + formats: [FeedReader.TestSupport.ParserHaltFormat] + ) + + assert [{:error, :parser_halted}] = Enum.to_list(events) + end + + test "stops cleanly when the parser process exits normally" do + path = write_temp(@xml) + on_exit(fn -> File.rm(path) end) + + assert {:ok, %{events: events}} = + FeedReader.stream_file(path, + format: :parser_halt, + formats: [FeedReader.TestSupport.ParserHaltFormat], + normal_exit: true + ) + + assert [] = Enum.to_list(events) + end + + defp write_temp(content) do + path = Path.join(System.tmp_dir!(), "feed_reader-#{System.unique_integer([:positive])}.xml") + File.write!(path, content) + path + end +end diff --git a/test/support/parser_halt_format.ex b/test/support/parser_halt_format.ex new file mode 100644 index 0000000..1058bfc --- /dev/null +++ b/test/support/parser_halt_format.ex @@ -0,0 +1,24 @@ +defmodule FeedReader.TestSupport.ParserHaltFormat do + @behaviour FeedReader.Format + + @impl true + def id, do: :parser_halt + + @impl true + def matches_prefix?(_prefix), do: true + + @impl true + def handler, do: FeedReader.TestSupport.ParserHaltHandler + + @impl true + def new_parser_state(_event_sink, opts) do + if Keyword.get(opts, :normal_exit), do: exit(:normal) + nil + end + + @impl true + def normalize_feed(_state, _opts), do: %FeedReader.Feed{} + + @impl true + def normalize_item(_raw_item, _feed, _opts), do: :skip +end diff --git a/test/support/parser_halt_handler.ex b/test/support/parser_halt_handler.ex new file mode 100644 index 0000000..12ad564 --- /dev/null +++ b/test/support/parser_halt_handler.ex @@ -0,0 +1,6 @@ +defmodule FeedReader.TestSupport.ParserHaltHandler do + @behaviour Saxy.Handler + + @impl true + def handle_event(_event, _data, _state), do: {:halt, :stopped} +end