ash/test/reactor/create_test.exs
James Harton 86d74ed789
feat(Ash.Reactor): Add a Reactor extension that makes working with resources easy. (#683)
* feat: Add `Ash.Reactor` with create support.

* improvement: Add `Ash.Reactor` update support.

* improvement: Add `Ash.Reactor` destroy support.

* improvement(Ash.Reactor): Support for transactional handling of notifications.

* improvement(Ash.Reactor): Add `read` and `get` steps.

* docs: Add the beginnings of a Reactor topic.

* improvement(Ash.Reactor): add support for generic actions.

* improvement: Add `undo` capability to `create` step.

* improvement: transaction and undo working.

* docs: Start documenting undo behaviour.

* chore: Update to Reactor 0.6.

* improvement: Automatically thread Ash tracers through Reactor.

* improvement(Ash.Reactor): Add undo to generic actions.

* docs: Improve reactor documentation.

* fix: Mimic copying `Ash.Notifier` seems to break the compiler for some reason.
2024-03-02 10:26:25 +13:00

277 lines
6.2 KiB
Elixir

defmodule Ash.Test.ReactorCreateTest do
@moduledoc false
use ExUnit.Case, async: true
defmodule Author do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Ets
ets do
private? true
end
multitenancy do
strategy :attribute
attribute :organisation
global? true
end
attributes do
uuid_primary_key :id
attribute :name, :string, allow_nil?: false
attribute :organisation, :string, allow_nil?: true
end
actions do
defaults [:create, :read, :update, :destroy]
end
relationships do
has_many :posts, Ash.Test.ReactorCreateTest.Post
end
end
defmodule Post do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Ets
ets do
private? true
end
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false
attribute :sub_title, :string
end
actions do
defaults [:create, :read, :update, :destroy]
create :with_actor_as_author do
change relate_actor(:author)
end
end
relationships do
belongs_to :author, Ash.Test.ReactorCreateTest.Author do
attribute_writable? true
allow_nil? true
end
end
end
defmodule Api do
@moduledoc false
use Ash.Api
resources do
resource Ash.Test.ReactorCreateTest.Author
resource Ash.Test.ReactorCreateTest.Post
end
end
test "it can create a post" do
defmodule SimpleCreatePostReactor do
@moduledoc false
use Reactor, extensions: [Ash.Reactor]
ash do
default_api Api
end
input :title
input :sub_title
create :create_post, Post, :create do
inputs(%{title: input(:title), sub_title: input(:sub_title)})
end
end
assert {:ok, post} =
Reactor.run(SimpleCreatePostReactor, %{title: "Title", sub_title: "Sub-title"})
assert post.title == "Title"
assert post.sub_title == "Sub-title"
assert post.__meta__.state == :loaded
end
test "it defaults to the primary action when the action is not supplied" do
defmodule InferredActionNameCreatePostReactor do
@moduledoc false
use Reactor, extensions: [Ash.Reactor]
ash do
default_api Api
end
input :title
input :sub_title
create :create_post, Post do
inputs(%{title: input(:title), sub_title: input(:sub_title)})
end
end
assert {:ok, _post} =
Reactor.run(InferredActionNameCreatePostReactor, %{
title: "Title",
sub_title: "Sub-title"
})
end
test "it merges multiple `inputs` entities together" do
defmodule MergedInputsCreatePostReactor do
@moduledoc false
use Reactor, extensions: [Ash.Reactor]
ash do
default_api Api
end
input :title
input :sub_title
create :create_post, Post, :create do
inputs(%{title: input(:title)})
inputs(%{sub_title: input(:sub_title)})
end
end
assert {:ok, post} =
Reactor.run(MergedInputsCreatePostReactor, %{
title: "Title",
sub_title: "Sub-title"
})
assert post.title == "Title"
assert post.sub_title == "Sub-title"
end
test "`inputs` entities can be transformed separately" do
defmodule TransformedInputsCreatePostReactor do
@moduledoc false
use Reactor, extensions: [Ash.Reactor]
ash do
default_api Api
end
input :title
create :create_post, Post, :create do
inputs %{title: input(:title)} do
transform &%{title: String.upcase(&1.title)}
end
inputs %{sub_title: input(:title)} do
transform &%{sub_title: String.downcase(&1.sub_title)}
end
end
end
assert {:ok, post} = Reactor.run(TransformedInputsCreatePostReactor, %{title: "Title"})
assert post.title == "TITLE"
assert post.sub_title == "title"
end
test "it can provide an actor" do
defmodule CreateWithActorCreatePostReactor do
@moduledoc false
use Reactor, extensions: [Ash.Reactor]
ash do
default_api Api
end
input :author_name
input :title
input :sub_title
create :create_author, Author, :create do
inputs(%{name: input(:author_name)})
end
create :create_post, Post, :with_actor_as_author do
inputs(%{title: input(:title), sub_title: input(:sub_title)})
actor(result(:create_author))
end
end
assert {:ok, post} =
Reactor.run(CreateWithActorCreatePostReactor, %{
author_name: "Marty McFly",
title: "Title",
sub_title: "Sub-title"
})
assert post.author.name == "Marty McFly"
assert post.title == "Title"
end
test "it can provide a tenant" do
defmodule TenantedCreateAuthorReactor do
@moduledoc false
use Reactor, extensions: [Ash.Reactor]
ash do
default_api Api
end
input :author_name
input :organisation_name
create :create_author, Author, :create do
inputs(%{name: input(:author_name)})
tenant input(:organisation_name)
end
end
assert {:ok, author} =
Reactor.run(TenantedCreateAuthorReactor, %{
author_name: "Marty McFly",
organisation_name: "Hill Valley High School"
})
assert author.name == "Marty McFly"
assert author.organisation == "Hill Valley High School"
end
test "it can undo the creation on error" do
defmodule UndoingCreateAuthorReactor do
@moduledoc false
use Ash.Reactor
ash do
default_api Api
end
input :author_name
create :create_author, Author, :create do
inputs(%{name: input(:author_name)})
undo :always
undo_action(:destroy)
end
step :fail do
argument :author, result(:create_author)
run fn _, _ ->
assert [_] = Api.read!(Author)
raise "hell"
end
end
end
assert {:error, _} =
Reactor.run(UndoingCreateAuthorReactor, %{author_name: "Marty McFly"}, %{},
async?: false
)
assert [] = Api.read!(Author)
end
end