diff --git a/mix.exs b/mix.exs index e10732f..7261ce6 100644 --- a/mix.exs +++ b/mix.exs @@ -6,6 +6,7 @@ defmodule FeedReader.MixProject do app: :feed_reader, version: "0.1.0", elixir: "~> 1.20.4", + elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps() @@ -20,6 +21,9 @@ defmodule FeedReader.MixProject do [preferred_envs: [precommit: :test]] end + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + defp deps do [ {:credo, "~> 1.7.19", only: :dev}, diff --git a/test/feed_reader/format_contract_test.exs b/test/feed_reader/format_contract_test.exs new file mode 100644 index 0000000..4816e4b --- /dev/null +++ b/test/feed_reader/format_contract_test.exs @@ -0,0 +1,38 @@ +defmodule FeedReader.FormatContractTest do + use ExUnit.Case, async: true + + alias FeedReader.{Feed, FormatRegistry, Item} + alias FeedReader.TestSupport.ExternalFormat + + test "built-in registry has a fixed format priority" do + assert FormatRegistry.builtins() == [ + FeedReader.Formats.RSS, + FeedReader.Formats.Atom, + FeedReader.Formats.RDF + ] + end + + test "per-call formats append after built-ins without duplicate modules" do + formats = FormatRegistry.formats([ExternalFormat, FeedReader.Formats.RSS]) + + assert formats == FormatRegistry.builtins() ++ [ExternalFormat] + end + + test "external formats implement detection, parser state, and normalization callbacks" do + sink = fn event -> send(self(), {:format_event, event}) end + state = ExternalFormat.new_parser_state(sink, mode: :test) + + assert ExternalFormat.id() == :external_test + assert ExternalFormat.matches_prefix?("") + refute ExternalFormat.matches_prefix?("") + assert ExternalFormat.handler() == FeedReader.TestSupport.ExternalFormatHandler + assert state.event_sink == sink + assert state.opts == [mode: :test] + + feed = ExternalFormat.normalize_feed(state, []) + assert %Feed{title: "External", type: :rss} = feed + + assert ExternalFormat.normalize_item(%{title: "Entry"}, feed, []) == + {:ok, %Item{title: "Entry"}} + end +end diff --git a/test/support/external_format.ex b/test/support/external_format.ex new file mode 100644 index 0000000..f3f1651 --- /dev/null +++ b/test/support/external_format.ex @@ -0,0 +1,25 @@ +defmodule FeedReader.TestSupport.ExternalFormat do + @moduledoc false + @behaviour FeedReader.Format + + alias FeedReader.{Feed, Item} + + @impl true + def id, do: :external_test + + @impl true + def matches_prefix?(" _rest), do: true + def matches_prefix?(_prefix), do: false + + @impl true + def handler, do: FeedReader.TestSupport.ExternalFormatHandler + + @impl true + def new_parser_state(event_sink, opts), do: %{event_sink: event_sink, opts: opts} + + @impl true + def normalize_feed(_state, _opts), do: %Feed{title: "External", type: :rss} + + @impl true + def normalize_item(%{title: title}, _feed, _opts), do: {:ok, %Item{title: title}} +end diff --git a/test/support/external_format_handler.ex b/test/support/external_format_handler.ex new file mode 100644 index 0000000..1d70649 --- /dev/null +++ b/test/support/external_format_handler.ex @@ -0,0 +1,7 @@ +defmodule FeedReader.TestSupport.ExternalFormatHandler do + @moduledoc false + @behaviour Saxy.Handler + + @impl true + def handle_event(_event_type, _data, state), do: {:ok, state} +end