test: cover repo_listener
10 characterization tests for PostgreSQL NOTIFY → PubSub relay: contact_status_changed channel → db:contact_status topic, oban_job_changed → db:oban_jobs, channel isolation, invalid JSON silent-swallow, catch-all handling of unknown channels and info messages.
This commit is contained in:
parent
4f956e4606
commit
cc480652aa
1 changed files with 137 additions and 0 deletions
137
test/microwaveprop/repo_listener_test.exs
Normal file
137
test/microwaveprop/repo_listener_test.exs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
defmodule Microwaveprop.RepoListenerTest do
|
||||
@moduledoc """
|
||||
Characterization tests for `Microwaveprop.RepoListener`.
|
||||
|
||||
The module is a GenServer that subscribes to two PostgreSQL `NOTIFY`
|
||||
channels via `Postgrex.Notifications` and rebroadcasts decoded
|
||||
payloads via `Phoenix.PubSub`. These tests exercise the `handle_info/2`
|
||||
seam directly using the exact message shape `Postgrex.Notifications`
|
||||
delivers — no database connection required.
|
||||
"""
|
||||
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias Microwaveprop.RepoListener
|
||||
|
||||
# Shape of the message that `Postgrex.Notifications` delivers to a
|
||||
# listening process. Ref. https://hexdocs.pm/postgrex/Postgrex.Notifications.html
|
||||
defp notification(channel, payload) do
|
||||
{:notification, self(), make_ref(), channel, payload}
|
||||
end
|
||||
|
||||
describe "handle_info/2 — contact_status_changed" do
|
||||
setup do
|
||||
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
|
||||
on_exit(fn -> Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:contact_status") end)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "broadcasts decoded payload on db:contact_status topic" do
|
||||
payload = Jason.encode!(%{"contact_id" => "abc-123", "status" => "queued"})
|
||||
msg = notification("contact_status_changed", payload)
|
||||
|
||||
assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})
|
||||
|
||||
assert_receive {:contact_status_changed, %{"contact_id" => "abc-123", "status" => "queued"}}
|
||||
end
|
||||
|
||||
test "preserves full decoded payload structure (nested maps/lists)" do
|
||||
data = %{
|
||||
"contact_id" => "xyz",
|
||||
"fields" => ["weather", "hrrr"],
|
||||
"meta" => %{"source" => "notify"}
|
||||
}
|
||||
|
||||
msg = notification("contact_status_changed", Jason.encode!(data))
|
||||
|
||||
assert {:noreply, _} = RepoListener.handle_info(msg, %{pid: nil})
|
||||
assert_receive {:contact_status_changed, ^data}
|
||||
end
|
||||
|
||||
test "silently ignores invalid JSON payload" do
|
||||
msg = notification("contact_status_changed", "not-json-at-all{")
|
||||
|
||||
assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})
|
||||
refute_receive {:contact_status_changed, _}, 50
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle_info/2 — oban_job_changed" do
|
||||
setup do
|
||||
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
|
||||
on_exit(fn -> Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:oban_jobs") end)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "broadcasts decoded payload on db:oban_jobs topic" do
|
||||
payload = Jason.encode!(%{"id" => 42, "state" => "completed", "queue" => "weather"})
|
||||
msg = notification("oban_job_changed", payload)
|
||||
|
||||
assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})
|
||||
|
||||
assert_receive {:oban_job_changed, %{"id" => 42, "state" => "completed", "queue" => "weather"}}
|
||||
end
|
||||
|
||||
test "silently ignores invalid JSON payload" do
|
||||
msg = notification("oban_job_changed", "{\"broken\":")
|
||||
|
||||
assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})
|
||||
refute_receive {:oban_job_changed, _}, 50
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle_info/2 — channel isolation" do
|
||||
test "contact_status_changed does not broadcast on db:oban_jobs" do
|
||||
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
|
||||
on_exit(fn -> Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:oban_jobs") end)
|
||||
|
||||
msg = notification("contact_status_changed", Jason.encode!(%{"contact_id" => "abc"}))
|
||||
|
||||
assert {:noreply, _} = RepoListener.handle_info(msg, %{pid: nil})
|
||||
refute_receive {:oban_job_changed, _}, 50
|
||||
refute_receive {:contact_status_changed, _}, 50
|
||||
end
|
||||
|
||||
test "oban_job_changed does not broadcast on db:contact_status" do
|
||||
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
|
||||
on_exit(fn -> Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:contact_status") end)
|
||||
|
||||
msg = notification("oban_job_changed", Jason.encode!(%{"id" => 1}))
|
||||
|
||||
assert {:noreply, _} = RepoListener.handle_info(msg, %{pid: nil})
|
||||
refute_receive {:contact_status_changed, _}, 50
|
||||
refute_receive {:oban_job_changed, _}, 50
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle_info/2 — unknown / unrelated messages" do
|
||||
test "ignores notifications on unknown channels without crashing" do
|
||||
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
|
||||
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
|
||||
|
||||
on_exit(fn ->
|
||||
Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:contact_status")
|
||||
Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:oban_jobs")
|
||||
end)
|
||||
|
||||
msg = notification("some_other_channel", Jason.encode!(%{"x" => 1}))
|
||||
|
||||
assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})
|
||||
refute_receive {:contact_status_changed, _}, 50
|
||||
refute_receive {:oban_job_changed, _}, 50
|
||||
end
|
||||
|
||||
test "ignores arbitrary info messages without crashing" do
|
||||
assert {:noreply, %{pid: nil}} = RepoListener.handle_info(:random_message, %{pid: nil})
|
||||
assert {:noreply, %{pid: nil}} = RepoListener.handle_info({:foo, :bar}, %{pid: nil})
|
||||
end
|
||||
|
||||
test "returns state unchanged regardless of message" do
|
||||
state = %{pid: nil, extra: :keep_me}
|
||||
assert {:noreply, ^state} = RepoListener.handle_info(:whatever, state)
|
||||
|
||||
msg = notification("contact_status_changed", Jason.encode!(%{"a" => 1}))
|
||||
assert {:noreply, ^state} = RepoListener.handle_info(msg, state)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue