ash/test/reactor/transaction_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

127 lines
2.9 KiB
Elixir

defmodule Ash.Test.Reactor.TransactionTest do
@moduledoc false
use ExUnit.Case, async: false
use Mimic
alias Ash.Test.Domain
defmodule Post do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Mnesia, domain: Domain
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false, public?: true
end
actions do
default_accept :*
defaults [:destroy, create: :*]
end
end
setup do
ExUnit.CaptureLog.capture_log(fn ->
Ash.DataLayer.Mnesia.start(Domain, [Post])
end)
on_exit(fn ->
ExUnit.CaptureLog.capture_log(fn ->
:mnesia.stop()
:mnesia.delete_schema([node()])
end)
end)
end
test "when the transaction completes successfully it returns the last result" do
defmodule SuccessfulNoReturnTransactionReactor do
@moduledoc false
use Ash.Reactor
ash do
default_domain(Domain)
end
transaction :create_posts, Post do
create :post_1, Post, :create do
inputs(%{title: value("About Marty McFly")})
end
create :post_2, Post, :create do
inputs(%{title: value("About Doc Brown")})
end
end
end
assert {:ok, %{title: "About Doc Brown"}} = Reactor.run(SuccessfulNoReturnTransactionReactor)
end
test "when the transaction completes successfully it returns the specified result" do
defmodule SuccessfulNamedReturnTransactionReactor do
@moduledoc false
use Ash.Reactor
ash do
default_domain(Domain)
end
transaction :create_posts, Post do
create :post_1, Post, :create do
inputs(%{title: value("About Marty McFly")})
end
create :post_2, Post, :create do
inputs(%{title: value("About Doc Brown")})
end
return :post_1
end
end
assert {:ok, %{title: "About Marty McFly"}} =
Reactor.run!(SuccessfulNamedReturnTransactionReactor)
end
test "when the transaction fails it is rolled back" do
defmodule FailAndRollBackTransactionReactor do
@moduledoc false
use Ash.Reactor
ash do
default_domain(Domain)
end
transaction :create_posts, Post do
create :post_1, Post, :create do
inputs(%{title: value("About Marty McFly")})
end
step :fail do
run fn _, _ ->
raise "hell"
end
end
return :post_1
end
end
Ash.DataLayer
|> expect(:rollback, fn resources, reason ->
assert resources == [Post]
assert Exception.message(reason) == "hell"
raise reason
end)
FailAndRollBackTransactionReactor
|> Reactor.run(%{}, %{}, async?: false)
|> Ash.Test.assert_has_error(fn
%Reactor.Error.Invalid.RunStepError{error: error} ->
Exception.message(error) =~ "hell"
_ ->
false
end)
end
end