defmodule Blogex.Test.FakeBlog do @moduledoc """ A test double that implements the same interface as a `use Blogex.Blog` module, but backed by an Agent so tests can control the post data. ## Usage in tests setup do posts = [PostBuilder.build(id: "hello"), PostBuilder.build(id: "world")] Blogex.Test.FakeBlog.start(posts, blog_id: :engineering, title: "Eng Blog") :ok end Then pass `Blogex.Test.FakeBlog` anywhere a blog module is expected. """ use Agent @defaults [ blog_id: :test_blog, title: "Test Blog", description: "A blog for tests", base_path: "/blog/test" ] def start(posts \\ [], opts \\ []) do opts = Keyword.merge(@defaults, opts) state = %{ posts: posts, blog_id: opts[:blog_id], title: opts[:title], description: opts[:description], base_path: opts[:base_path] } case Agent.start(fn -> state end, name: __MODULE__) do {:ok, pid} -> {:ok, pid} {:error, {:already_started, pid}} -> Agent.update(__MODULE__, fn _ -> state end) {:ok, pid} end end def stop, do: Agent.stop(__MODULE__) defp get(key), do: Agent.get(__MODULE__, &Map.fetch!(&1, key)) def blog_id, do: get(:blog_id) def title, do: get(:title) def description, do: get(:description) def base_path, do: get(:base_path) def all_posts do get(:posts) |> Enum.filter(& &1.published) |> Enum.sort_by(& &1.date, {:desc, Date}) end def recent_posts(n \\ 5), do: Enum.take(all_posts(), n) def all_tags do all_posts() |> Enum.flat_map(& &1.tags) |> Enum.uniq() |> Enum.sort() end def posts_by_tag(tag) do Enum.filter(all_posts(), fn post -> tag in post.tags end) end def get_post!(id) do Enum.find(all_posts(), &(&1.id == id)) || raise Blogex.NotFoundError, "post #{inspect(id)} not found" end def get_post(id) do Enum.find(all_posts(), &(&1.id == id)) end def paginate(page \\ 1, per_page \\ 10) do posts = all_posts() total = length(posts) total_pages = max(ceil(total / per_page), 1) entries = posts |> Enum.drop((page - 1) * per_page) |> Enum.take(per_page) %{ entries: entries, page: page, per_page: per_page, total_entries: total, total_pages: total_pages } end end