ash_postgres/test/atomics_test.exs

259 lines
7.2 KiB
Elixir

defmodule AshPostgres.AtomicsTest do
alias AshPostgres.Test.Author
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.Post
import Ash.Expr
require Ash.Query
test "atomics work on upserts" do
id = Ash.UUID.generate()
Post
|> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true)
|> Ash.Changeset.atomic_update(:price, expr(price + 1))
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true)
|> Ash.Changeset.atomic_update(:price, expr(price + 1))
|> Ash.create!()
assert [%{price: 2}] = Post |> Ash.read!()
end
test "a basic atomic works" do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Ash.create!()
assert %{price: 2} =
post
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.atomic_update(:price, expr(price + 1))
|> Ash.update!()
end
test "atomics work with maps that contain lists" do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Ash.create!()
assert %{list_of_stuff: [%{"foo" => [%{"a" => 1}]}]} =
post
|> Ash.Changeset.for_update(:update, %{list_of_stuff: [%{foo: [%{a: 1}]}]})
|> Ash.update!()
end
test "atomics work with maps that contain lists that contain maps that contain lists etc." do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Ash.create!()
assert %{list_of_stuff: [%{"foo" => [%{"a" => 1, "b" => %{"c" => [1, 2, 3]}}]}]} =
post
|> Ash.Changeset.for_update(:update, %{
list_of_stuff: [%{foo: [%{a: 1, b: %{c: [1, 2, 3]}}]}]
})
|> Ash.update!()
end
test "atomics work with maps that contain expressions in a deep structure" do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Ash.create!()
assert %{list_of_stuff: [%{"foo" => [%{"a" => 1, "b" => %{"c" => [1, 2, 3]}}]}]} =
post
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.atomic_update(%{
list_of_stuff: [
%{foo: [%{a: 1, b: %{c: [1, 2, expr(type(fragment("3"), :integer))]}}]}
]
})
|> Ash.update!()
end
test "an atomic update can be set to the value of an aggregate" do
author =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"})
|> Ash.create!()
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "bar", author_id: author.id})
|> Ash.create!()
# just asserting that there is no exception here
post
|> Ash.Changeset.for_update(:set_title_to_sum_of_author_count_of_posts)
|> Ash.update!()
end
test "an atomic update can be set to the value of a related aggregate" do
author =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"})
|> Ash.create!()
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "bar", author_id: author.id})
|> Ash.create!()
# just asserting that there is no exception here
post
|> Ash.Changeset.for_update(:set_title_to_author_profile_description)
|> Ash.update!()
end
test "an atomic validation is based on where it appears in the action" do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "bar"})
|> Ash.create!()
# just asserting that there is no exception here
Post
|> Ash.Query.filter(id == ^post.id)
|> Ash.Query.limit(1)
|> Ash.bulk_update!(:change_title_to_foo_unless_its_already_foo, %{})
end
test "an atomic works with a datetime" do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Ash.create!()
now = DateTime.utc_now()
assert %{created_at: ^now} =
post
|> Ash.Changeset.new()
|> Ash.Changeset.atomic_update(:created_at, expr(^now))
|> Ash.Changeset.for_update(:update, %{})
|> Ash.update!()
end
test "an atomic that violates a constraint will return the proper error" do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Ash.create!()
assert_raise Ash.Error.Invalid, ~r/does not exist/, fn ->
post
|> Ash.Changeset.new()
|> Ash.Changeset.atomic_update(:organization_id, Ash.UUID.generate())
|> Ash.Changeset.for_update(:update, %{})
|> Ash.update!()
end
end
test "an atomic can refer to a calculation" do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Ash.create!()
post =
post
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.atomic_update(:score, expr(score_after_winning))
|> Ash.update!()
assert post.score == 1
end
test "an atomic can be attached to an action" do
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Ash.create!()
assert Post.increment_score!(post, 2).score == 2
assert Post.increment_score!(post, 2).score == 4
end
test "relationships can be used in atomic update" do
author =
Author
|> Ash.Changeset.for_create(:create, %{
first_name: "John",
last_name: "Doe"
})
|> Ash.create!()
parent_post =
Post
|> Ash.Changeset.for_create(:create, %{price: 42, author_id: author.id})
|> Ash.create!()
post =
Post
|> Ash.Changeset.for_create(:create, %{
price: 1,
author_id: author.id,
parent_post_id: parent_post.id
})
|> Ash.create!()
post =
post
|> Ash.Changeset.for_update(:set_title_from_author, %{})
|> Ash.update!()
assert post.title == "John"
post =
post
|> Ash.Changeset.for_update(:set_attributes_from_parent, %{})
|> Ash.update!()
assert post.title == "John"
end
test "relationships can be used in atomic update and in an atomic update filter" do
author =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"})
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id})
|> Ash.create!()
post =
Post
|> Ash.Query.filter(author.last_name == "Doe")
|> Ash.bulk_update!(:set_title_from_author, %{}, return_records?: true)
|> Map.get(:records)
|> List.first()
assert post.title == "John"
end
test "relationships can be used in atomic update and in an atomic update filter when first join is a left join" do
author =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"})
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id})
|> Ash.create!()
assert [] =
Post
|> Ash.Query.filter(is_nil(author.last_name))
|> Ash.bulk_update!(:set_title_from_author, %{}, return_records?: true)
|> Map.get(:records)
end
end