ash/test/reactor/notifications_test.exs
James Harton 139058d4c0 fix(Ash.Reactor): crash when calling an ash reactor for the first time.
Thanks to @carlgleisner for the [detailed reproduction](https://github.com/carlgleisner/reactor_notification_worker_issue).

The problem was caused by an attempt to not have nested reactors indepdently publish their notifications separate to the parents but contained a logic flaw which caused the agent to not start, but only the first time you try and use a given reactor.

The fix involves _always_ starting a notification agent for each reactor, but nesting them.  When a reactor completes it either publishes it's notifications to the parent reactor or to ash if there are no parent reactors.
2024-04-10 06:19:38 +12:00

116 lines
3.3 KiB
Elixir

defmodule Ash.Test.Reactor.NotificationsTest do
@moduledoc false
use ExUnit.Case, async: true
use Mimic
import ExUnit.CaptureLog
alias Ash.Reactor.Notifications
describe "init/1" do
test "it starts an agent" do
{:ok, context} = Notifications.init(%{})
assert [] == agent_get(context.ash_notification_agent)
end
test "when there are already notifications in the context it stores them in the agent" do
notifications = build_notifications()
{:ok, context} =
Notifications.init(%{ash_notifications: notifications})
enqueued = agent_get(context.ash_notification_agent)
assert enqueued == notifications
end
test "when there are already notifications in the context it removes them" do
notifications = build_notifications()
{:ok, context} =
Notifications.init(%{ash_notifications: notifications})
refute is_map_key(context, :ash_notifications)
end
end
describe "halt/1" do
setup do
{:ok, context} = Notifications.init(%{})
{:ok, context: context}
end
test "it stops the agent", %{context: context} do
[agent | _] = context.ash_notification_agent
{:ok, context} = Notifications.halt(context)
assert context.ash_notification_agent == []
refute Process.alive?(agent)
end
test "it moves any queued notifications into the context", %{context: context} do
notifications = build_notifications()
:ok = Notifications.enqueue_notifications(context, notifications)
{:ok, context} = Notifications.halt(context)
assert context.ash_notifications == notifications
end
end
describe "complete/2" do
setup do
{:ok, context} = Notifications.init(%{})
{:ok, context: context}
end
test "it publishes any queued notifications", %{context: context} do
notifications = build_notifications()
:ok = Notifications.enqueue_notifications(context, notifications)
expect(Notifications, :publish, fn _context, actual ->
assert actual == notifications
[]
end)
assert {:ok, :result} = Notifications.complete(:result, context)
end
test "it logs a warning when there are unpublished notifications", %{context: context} do
notifications = build_notifications()
:ok = Notifications.enqueue_notifications(context, notifications)
expect(Notifications, :publish, fn _context, notifications -> notifications end)
assert capture_log(fn ->
assert {:ok, :result} = Notifications.complete(:result, context)
end) =~ "Missed 3 notifications"
end
test "it stops the agent", %{context: context} do
[agent | _] = context.ash_notification_agent
{:ok, :result} = Notifications.complete(:result, context)
refute Process.alive?(agent)
end
end
describe "error/2" do
setup do
{:ok, context} = Notifications.init(%{})
{:ok, context: context}
end
test "it stops the agent", %{context: context} do
[agent | _] = context.ash_notification_agent
:ok = Notifications.error([:errors], context)
refute Process.alive?(agent)
end
end
defp build_notifications(how_many \\ 3) do
for i <- 1..how_many do
Ash.Notifier.Notification.new(__MODULE__.FakeResource, data: %{count: i})
end
end
defp agent_get([agent | _]), do: Agent.get(agent, & &1)
end