2023-12-21 15:08:40 +13:00
|
|
|
defmodule Ash.Test.Actions.BulkDestroyTest do
|
|
|
|
@moduledoc false
|
2024-07-03 03:05:13 +12:00
|
|
|
use ExUnit.Case, async: false
|
2023-12-21 15:08:40 +13:00
|
|
|
|
2024-06-26 04:39:18 +12:00
|
|
|
import ExUnit.CaptureLog
|
|
|
|
|
2024-02-22 02:00:20 +13:00
|
|
|
require Ash.Query
|
2024-03-28 09:06:40 +13:00
|
|
|
alias Ash.Test.Domain, as: Domain
|
2024-02-22 02:00:20 +13:00
|
|
|
|
2024-05-03 06:51:06 +12:00
|
|
|
defmodule Notifier do
|
|
|
|
use Ash.Notifier
|
|
|
|
|
|
|
|
def notify(notification) do
|
|
|
|
send(self(), {:notification, notification})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
defmodule AddAfterToTitle do
|
|
|
|
use Ash.Resource.Change
|
|
|
|
|
2024-04-28 02:13:43 +12:00
|
|
|
def change(changeset, _, _) do
|
2023-12-21 15:08:40 +13:00
|
|
|
changeset
|
|
|
|
end
|
|
|
|
|
2024-04-26 14:18:38 +12:00
|
|
|
def atomic(_, _, _), do: :ok
|
|
|
|
|
2024-04-28 02:13:43 +12:00
|
|
|
def batch_change(changesets, _, _) do
|
|
|
|
changesets
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
def after_batch(results, _, _) do
|
|
|
|
Stream.map(results, fn {_changeset, result} ->
|
|
|
|
{:ok, %{result | title: result.title <> "_after"}}
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-06-11 12:14:37 +12:00
|
|
|
defmodule Author do
|
|
|
|
@moduledoc false
|
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
actions do
|
|
|
|
default_accept :*
|
|
|
|
defaults [:read, :create, :update, :destroy]
|
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
|
|
|
uuid_primary_key :id
|
|
|
|
|
|
|
|
attribute :name, :string do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
|
|
|
has_many :posts, Ash.Test.Actions.BulkDestroyTest.Post,
|
|
|
|
destination_attribute: :author_id,
|
|
|
|
public?: true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defmodule PostLink do
|
|
|
|
@moduledoc false
|
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
|
|
|
attribute :type, :string do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
actions do
|
|
|
|
default_accept :*
|
|
|
|
defaults [:read, :destroy, create: :*, update: :*]
|
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
|
|
|
belongs_to :source_post, Ash.Test.Actions.BulkDestroyTest.Post,
|
|
|
|
primary_key?: true,
|
|
|
|
allow_nil?: false,
|
|
|
|
public?: true
|
|
|
|
|
|
|
|
belongs_to :destination_post, Ash.Test.Actions.BulkDestroyTest.Post,
|
|
|
|
primary_key?: true,
|
|
|
|
allow_nil?: false,
|
|
|
|
public?: true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
defmodule Post do
|
|
|
|
@moduledoc false
|
|
|
|
use Ash.Resource,
|
2024-03-28 09:06:40 +13:00
|
|
|
domain: Domain,
|
2024-05-03 06:51:06 +12:00
|
|
|
notifiers: [Notifier],
|
2023-12-21 15:08:40 +13:00
|
|
|
data_layer: Ash.DataLayer.Ets,
|
|
|
|
authorizers: [Ash.Policy.Authorizer]
|
|
|
|
|
|
|
|
ets do
|
|
|
|
private? true
|
|
|
|
end
|
|
|
|
|
2024-07-03 02:41:15 +12:00
|
|
|
code_interface do
|
|
|
|
define :destroy_with_policy
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults [:destroy, create: :*, update: :*]
|
2024-02-22 02:00:20 +13:00
|
|
|
|
|
|
|
read :read do
|
|
|
|
primary? true
|
|
|
|
pagination keyset?: true, required?: false
|
|
|
|
end
|
2023-12-21 15:08:40 +13:00
|
|
|
|
|
|
|
destroy :destroy_with_change do
|
2024-04-26 14:18:38 +12:00
|
|
|
require_atomic? false
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
change fn changeset, _ ->
|
|
|
|
title = Ash.Changeset.get_attribute(changeset, :title)
|
|
|
|
Ash.Changeset.force_change_attribute(changeset, :title, title <> "_stuff")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-18 07:08:19 +12:00
|
|
|
destroy :destroy_with_validation do
|
|
|
|
validate attribute_does_not_equal(:title, "can't delete")
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
destroy :destroy_with_argument do
|
2024-04-26 14:18:38 +12:00
|
|
|
require_atomic? false
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
argument :a_title, :string do
|
|
|
|
allow_nil? false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
destroy :destroy_with_after_action do
|
2024-04-26 14:18:38 +12:00
|
|
|
require_atomic? false
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
change after_action(fn _changeset, result, _context ->
|
2023-12-21 15:08:40 +13:00
|
|
|
{:ok, %{result | title: result.title <> "_stuff"}}
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
destroy :destroy_with_after_batch do
|
2024-04-26 14:18:38 +12:00
|
|
|
require_atomic? false
|
2023-12-21 15:08:40 +13:00
|
|
|
change AddAfterToTitle
|
|
|
|
end
|
|
|
|
|
|
|
|
destroy :destroy_with_after_transaction do
|
2024-04-26 14:18:38 +12:00
|
|
|
require_atomic? false
|
|
|
|
|
2024-05-02 09:34:49 +12:00
|
|
|
argument :a_title, :string
|
|
|
|
|
|
|
|
change after_transaction(fn
|
|
|
|
_changeset, {:ok, result}, _context ->
|
|
|
|
{:ok, %{result | title: result.title <> "_stuff"}}
|
|
|
|
|
|
|
|
_changeset, {:error, error}, _context ->
|
|
|
|
send(self(), {:error, error})
|
2023-12-21 15:08:40 +13:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
destroy :destroy_with_policy do
|
2024-04-26 14:18:38 +12:00
|
|
|
require_atomic? false
|
2023-12-21 15:08:40 +13:00
|
|
|
argument :authorize?, :boolean, allow_nil?: false
|
|
|
|
|
|
|
|
change set_context(%{authorize?: arg(:authorize?)})
|
|
|
|
end
|
2024-02-23 02:01:47 +13:00
|
|
|
|
2024-05-13 07:32:13 +12:00
|
|
|
destroy :destroy_with_filter do
|
|
|
|
change filter(expr(title == "foo"))
|
|
|
|
end
|
|
|
|
|
2024-02-23 02:01:47 +13:00
|
|
|
destroy :soft do
|
2024-04-26 14:18:38 +12:00
|
|
|
require_atomic? false
|
2024-02-23 02:01:47 +13:00
|
|
|
soft? true
|
|
|
|
change set_attribute(:title2, "archived")
|
|
|
|
end
|
2024-05-16 16:24:44 +12:00
|
|
|
|
|
|
|
destroy :forbidden_destroy do
|
|
|
|
end
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
identities do
|
|
|
|
identity :unique_title, :title do
|
2024-03-28 09:06:40 +13:00
|
|
|
pre_check_with Ash.Test.Actions.BulkUpdateTest.Domain
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
policies do
|
2024-05-16 16:24:44 +12:00
|
|
|
policy action(:forbidden_destroy) do
|
|
|
|
authorize_if never()
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
policy action(:destroy_with_policy) do
|
|
|
|
authorize_if context_equals(:authorize?, true)
|
|
|
|
end
|
2024-02-22 02:09:20 +13:00
|
|
|
|
|
|
|
policy action(:read) do
|
|
|
|
authorize_if always()
|
|
|
|
end
|
2024-03-28 09:06:40 +13:00
|
|
|
|
|
|
|
policy always() do
|
|
|
|
authorize_if always()
|
|
|
|
end
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
|
|
|
uuid_primary_key :id
|
2024-03-28 09:06:40 +13:00
|
|
|
attribute :title, :string, allow_nil?: false, public?: true
|
|
|
|
attribute :title2, :string, public?: true
|
|
|
|
attribute :title3, :string, public?: true
|
2023-12-21 15:08:40 +13:00
|
|
|
|
|
|
|
timestamps()
|
|
|
|
end
|
2024-06-11 12:14:37 +12:00
|
|
|
|
|
|
|
relationships do
|
|
|
|
belongs_to :author, Author, public?: true
|
|
|
|
|
|
|
|
many_to_many :related_posts, __MODULE__,
|
|
|
|
through: PostLink,
|
|
|
|
source_attribute_on_join_resource: :source_post_id,
|
|
|
|
destination_attribute_on_join_resource: :destination_post_id,
|
|
|
|
public?: true
|
|
|
|
end
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
2024-06-26 04:39:18 +12:00
|
|
|
defmodule MnesiaPost do
|
|
|
|
@doc false
|
|
|
|
|
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Mnesia, notifiers: [Notifier]
|
|
|
|
|
|
|
|
mnesia do
|
|
|
|
table :mnesia_post_destroys
|
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
|
|
|
uuid_primary_key :id
|
|
|
|
attribute :title, :string, allow_nil?: false, public?: true
|
|
|
|
|
|
|
|
timestamps()
|
|
|
|
end
|
|
|
|
|
|
|
|
actions do
|
|
|
|
default_accept :*
|
|
|
|
defaults [:read, :destroy, :create, :update]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-06-29 05:59:03 +12:00
|
|
|
setup do
|
|
|
|
capture_log(fn ->
|
|
|
|
Ash.DataLayer.Mnesia.start(Domain, [MnesiaPost])
|
|
|
|
end)
|
|
|
|
|
|
|
|
on_exit(fn ->
|
|
|
|
capture_log(fn ->
|
|
|
|
:mnesia.stop()
|
|
|
|
:mnesia.delete_schema([node()])
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
test "returns destroyed records" do
|
|
|
|
assert %Ash.BulkResult{records: [%{}, %{}]} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy!(:destroy, %{},
|
2023-12-21 15:08:40 +13:00
|
|
|
resource: Post,
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: :stream,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [] = Ash.read!(Post)
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
2024-05-03 06:51:06 +12:00
|
|
|
test "sends notifications" do
|
|
|
|
assert %Ash.BulkResult{records: [%{}, %{}]} =
|
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy!(:destroy, %{},
|
|
|
|
resource: Post,
|
|
|
|
strategy: :stream,
|
|
|
|
notify?: true,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
|
|
|
assert_received {:notification, %{data: %{title: "title1"}}}
|
|
|
|
assert_received {:notification, %{data: %{title: "title2"}}}
|
|
|
|
end
|
|
|
|
|
2024-06-26 04:39:18 +12:00
|
|
|
test "sends notifications with stream strategy in transactions" do
|
|
|
|
assert %Ash.BulkResult{records: [%{}, %{}]} =
|
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], MnesiaPost, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true,
|
|
|
|
authorize?: false
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy!(:destroy, %{},
|
|
|
|
resource: MnesiaPost,
|
|
|
|
strategy: :stream,
|
|
|
|
allow_stream_with: :full_read,
|
|
|
|
return_records?: true,
|
|
|
|
notify?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
|
|
|
assert_received {:notification,
|
|
|
|
%{
|
|
|
|
data: %{title: "title1"}
|
|
|
|
}}
|
|
|
|
|
|
|
|
assert_received {:notification,
|
|
|
|
%{
|
|
|
|
data: %{title: "title2"}
|
|
|
|
}}
|
|
|
|
end
|
|
|
|
|
2024-05-23 10:42:19 +12:00
|
|
|
test "doesn't send notifications if not asked to" do
|
|
|
|
assert %Ash.BulkResult{records: [%{}, %{}]} =
|
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy!(:destroy, %{},
|
|
|
|
resource: Post,
|
|
|
|
strategy: :stream,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
|
|
|
refute_received {:notification, _}
|
|
|
|
end
|
|
|
|
|
2024-05-03 06:51:06 +12:00
|
|
|
test "notifications can be returned" do
|
|
|
|
assert %Ash.BulkResult{records: [%{}, %{}], notifications: [%{}, %{}]} =
|
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy!(:destroy, %{},
|
|
|
|
resource: Post,
|
|
|
|
strategy: :stream,
|
|
|
|
return_notifications?: true,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
test "runs changes" do
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
records: [
|
|
|
|
%{title: "title1_stuff"},
|
|
|
|
%{title: "title2_stuff"}
|
|
|
|
]
|
|
|
|
} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy!(:destroy_with_change, %{},
|
2023-12-21 15:08:40 +13:00
|
|
|
resource: Post,
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: [:stream],
|
2023-12-21 15:08:40 +13:00
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|> Map.update!(:records, fn records ->
|
|
|
|
Enum.sort_by(records, & &1.title)
|
|
|
|
end)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [] = Ash.read!(Post)
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "accepts arguments" do
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
records: [
|
2024-04-26 14:18:38 +12:00
|
|
|
%{title: "title1", title2: nil},
|
|
|
|
%{title: "title2", title2: nil}
|
2023-12-21 15:08:40 +13:00
|
|
|
]
|
|
|
|
} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-04-26 14:18:38 +12:00
|
|
|
|> Ash.bulk_destroy!(:destroy_with_argument, %{a_title: "a value"},
|
2023-12-21 15:08:40 +13:00
|
|
|
resource: Post,
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: [:stream],
|
2023-12-21 15:08:40 +13:00
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|> Map.update!(:records, fn records ->
|
|
|
|
Enum.sort_by(records, & &1.title)
|
|
|
|
end)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [] = Ash.read!(Post)
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
2024-07-18 07:08:19 +12:00
|
|
|
test "runs validations" do
|
|
|
|
assert_raise Ash.Error.Invalid, ~r/must not equal "can't delete"/, fn ->
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
records: [
|
|
|
|
%{title: "title1", title2: nil},
|
|
|
|
%{title: "title2", title2: nil}
|
|
|
|
]
|
|
|
|
} =
|
|
|
|
Ash.bulk_create!([%{title: "can't delete"}, %{title: "title2"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy!(:destroy_with_validation, %{},
|
|
|
|
resource: Post,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
test "runs after batch hooks" do
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
records: [
|
2024-04-26 14:18:38 +12:00
|
|
|
%{title: "title1_after"},
|
|
|
|
%{title: "title2_after"}
|
2023-12-21 15:08:40 +13:00
|
|
|
]
|
|
|
|
} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy!(:destroy_with_after_batch, %{},
|
2023-12-21 15:08:40 +13:00
|
|
|
resource: Post,
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: [:atomic],
|
2023-12-21 15:08:40 +13:00
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|> Map.update!(:records, fn records ->
|
|
|
|
Enum.sort_by(records, & &1.title)
|
|
|
|
end)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [] = Ash.read!(Post)
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "will return errors on request" do
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
error_count: 1,
|
2024-04-26 14:18:38 +12:00
|
|
|
errors: [%Ash.Error.Invalid{}]
|
2023-12-21 15:08:40 +13:00
|
|
|
} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy(:destroy_with_argument, %{a_title: %{invalid: :value}},
|
2023-12-21 15:08:40 +13:00
|
|
|
resource: Post,
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: :stream,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [_] = Ash.read!(Post)
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "runs after action hooks" do
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
records: [
|
|
|
|
%{title: "title1_stuff"},
|
|
|
|
%{title: "title2_stuff"}
|
|
|
|
]
|
|
|
|
} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy!(:destroy_with_after_action, %{},
|
2023-12-21 15:08:40 +13:00
|
|
|
resource: Post,
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: :stream,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|> Map.update!(:records, fn records ->
|
|
|
|
Enum.sort_by(records, & &1.title)
|
|
|
|
end)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [] = Ash.read!(Post)
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
2024-05-02 09:34:49 +12:00
|
|
|
test "runs after transaction hooks on success" do
|
2023-12-21 15:08:40 +13:00
|
|
|
assert %Ash.BulkResult{
|
|
|
|
records: [
|
|
|
|
%{title: "title1_stuff"},
|
|
|
|
%{title: "title2_stuff"}
|
|
|
|
]
|
|
|
|
} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy!(:destroy_with_after_transaction, %{},
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: :stream,
|
2023-12-21 15:08:40 +13:00
|
|
|
resource: Post,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|> Map.update!(:records, fn records ->
|
|
|
|
Enum.sort_by(records, & &1.title)
|
|
|
|
end)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [] = Ash.read!(Post)
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
|
|
|
|
2024-05-02 09:34:49 +12:00
|
|
|
test "runs after transaction hooks on failure" do
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
error_count: 1,
|
|
|
|
errors: [%Ash.Error.Invalid{}]
|
|
|
|
} =
|
|
|
|
Ash.bulk_create!([%{title: "title1"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy(:destroy_with_after_transaction, %{a_title: %{invalid: :value}},
|
|
|
|
resource: Post,
|
|
|
|
strategy: :stream,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
|
|
|
assert_receive {:error, _error}
|
|
|
|
end
|
|
|
|
|
2024-02-23 02:01:47 +13:00
|
|
|
test "soft destroys" do
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
records: [
|
|
|
|
%{title2: "archived"},
|
|
|
|
%{title2: "archived"}
|
|
|
|
]
|
|
|
|
} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2024-02-23 02:01:47 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy!(:soft, %{},
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: [:stream],
|
2024-02-23 02:01:47 +13:00
|
|
|
resource: Post,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|> Map.update!(:records, fn records ->
|
|
|
|
Enum.sort_by(records, & &1.title)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2024-05-17 02:57:10 +12:00
|
|
|
test "works with empty list without the need to define a domain" do
|
|
|
|
assert %Ash.BulkResult{records: []} =
|
|
|
|
Ash.bulk_destroy!([], :destroy, %{}, return_records?: true)
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
describe "authorization" do
|
|
|
|
test "policy success results in successes" do
|
|
|
|
assert %Ash.BulkResult{records: [_, _], errors: []} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy(
|
2023-12-21 15:08:40 +13:00
|
|
|
:destroy_with_policy,
|
|
|
|
%{authorize?: true},
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: [:atomic_batches],
|
2023-12-21 15:08:40 +13:00
|
|
|
authorize?: true,
|
|
|
|
resource: Post,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2024-02-22 02:00:20 +13:00
|
|
|
test "policy success results in successes with query" do
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2024-02-22 02:00:20 +13:00
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|
|
|
|
assert %Ash.BulkResult{errors: []} =
|
|
|
|
Post
|
2024-02-22 02:09:20 +13:00
|
|
|
|> Ash.Query.filter(title: [in: ["title1", "title2"]])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy(
|
2024-02-22 02:00:20 +13:00
|
|
|
:destroy_with_policy,
|
|
|
|
%{authorize?: true},
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: :stream,
|
2024-02-22 02:09:20 +13:00
|
|
|
authorize?: true,
|
|
|
|
return_errors?: true
|
2024-02-22 02:00:20 +13:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-12-21 15:08:40 +13:00
|
|
|
test "policy failure results in failures" do
|
2024-04-26 14:18:38 +12:00
|
|
|
assert %Ash.BulkResult{errors: [%Ash.Error.Forbidden{}], records: []} =
|
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy(
|
|
|
|
:destroy_with_policy,
|
|
|
|
%{authorize?: false},
|
|
|
|
authorize?: true,
|
|
|
|
strategy: :atomic,
|
|
|
|
resource: Post,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
|
|
|
assert %Ash.BulkResult{
|
|
|
|
errors: [%Ash.Error.Forbidden{}, %Ash.Error.Forbidden{}],
|
|
|
|
records: []
|
|
|
|
} =
|
2024-03-28 09:06:40 +13:00
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
2023-12-21 15:08:40 +13:00
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.bulk_destroy(
|
2023-12-21 15:08:40 +13:00
|
|
|
:destroy_with_policy,
|
|
|
|
%{authorize?: false},
|
|
|
|
authorize?: true,
|
2024-04-26 14:18:38 +12:00
|
|
|
strategy: :stream,
|
2023-12-21 15:08:40 +13:00
|
|
|
resource: Post,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
end
|
2024-07-03 02:41:15 +12:00
|
|
|
|
|
|
|
test "policy failure results in failures when using code interface" do
|
|
|
|
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Enum.each(fn {:ok, post} ->
|
2024-07-03 03:05:13 +12:00
|
|
|
assert_raise Ash.Error.Forbidden, fn ->
|
|
|
|
Post.destroy_with_policy!(post.id, %{authorize?: false})
|
|
|
|
end
|
2024-07-03 02:41:15 +12:00
|
|
|
end)
|
|
|
|
end
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|
2024-05-13 07:32:13 +12:00
|
|
|
|
|
|
|
test "respects filter with atomics" do
|
|
|
|
assert %Ash.BulkResult{records: [%{title: "foo"}]} =
|
|
|
|
Ash.bulk_create!([%{title: "foo"}, %{title: "bar"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy!(:destroy_with_filter, %{},
|
|
|
|
resource: Post,
|
|
|
|
strategy: :atomic_batches,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
|
|
|
assert [%{title: "bar"}] = Ash.read!(Post)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "respects filter with stream" do
|
|
|
|
assert %Ash.BulkResult{records: [%{title: "foo"}]} =
|
|
|
|
Ash.bulk_create!([%{title: "foo"}, %{title: "bar"}], Post, :create,
|
|
|
|
return_stream?: true,
|
|
|
|
return_records?: true
|
|
|
|
)
|
|
|
|
|> Stream.map(fn {:ok, result} ->
|
|
|
|
result
|
|
|
|
end)
|
|
|
|
|> Ash.bulk_destroy!(:destroy_with_filter, %{},
|
|
|
|
resource: Post,
|
|
|
|
strategy: :stream,
|
|
|
|
return_records?: true,
|
|
|
|
return_errors?: true
|
|
|
|
)
|
|
|
|
|
|
|
|
assert [%{title: "bar"}] = Ash.read!(Post)
|
|
|
|
end
|
2024-05-16 16:24:44 +12:00
|
|
|
|
|
|
|
test "validates the passed-in action" do
|
|
|
|
bulk_result =
|
|
|
|
[%{title: "title1"}, %{title: "title2"}]
|
|
|
|
|> Ash.bulk_create!(Post, :create, return_records?: true)
|
|
|
|
|> Map.get(:records)
|
|
|
|
|> Ash.bulk_destroy(:this_is_not_an_actual_destroy_action, %{}, return_errors?: true)
|
|
|
|
|
|
|
|
assert bulk_result.status == :error
|
|
|
|
assert [] != bulk_result.errors
|
|
|
|
|
2024-05-16 17:02:18 +12:00
|
|
|
assert [_, _] = Ash.read!(Post)
|
2024-05-16 16:24:44 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
test "skipping authorization checks is honoured" do
|
|
|
|
posts =
|
|
|
|
Ash.bulk_create!([%{title: "delete me"}], Post, :create, return_records?: true)
|
|
|
|
|> Map.get(:records)
|
|
|
|
|
|
|
|
result =
|
|
|
|
%Ash.BulkResult{} = Ash.bulk_destroy(posts, :forbidden_destroy, %{}, authorize?: false)
|
|
|
|
|
|
|
|
assert result.status == :success
|
|
|
|
assert result.error_count == 0
|
|
|
|
|
2024-05-16 17:02:18 +12:00
|
|
|
assert [] = Ash.read!(Post)
|
2024-05-16 16:24:44 +12:00
|
|
|
end
|
2024-06-11 12:14:37 +12:00
|
|
|
|
|
|
|
describe "load" do
|
|
|
|
test "allows loading has_many relationship" do
|
|
|
|
post1 = Ash.create!(Post, %{title: "Post 1"})
|
|
|
|
post2 = Ash.create!(Post, %{title: "Post 2"})
|
|
|
|
|
|
|
|
load_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.sort(title: :asc)
|
|
|
|
|> Ash.Query.select([:title])
|
|
|
|
|
|
|
|
assert %Ash.BulkResult{records: [author]} =
|
|
|
|
Author
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "Author"})
|
|
|
|
|> Ash.Changeset.manage_relationship(:posts, [post2, post1],
|
|
|
|
type: :append_and_remove
|
|
|
|
)
|
|
|
|
|> Ash.create!()
|
|
|
|
|> List.wrap()
|
|
|
|
|> Ash.bulk_destroy!(:destroy, %{},
|
|
|
|
resource: Author,
|
|
|
|
return_records?: true,
|
|
|
|
load: [posts: load_query]
|
|
|
|
)
|
|
|
|
|
|
|
|
assert [%Post{title: "Post 1"}, %Post{title: "Post 2"}] = author.posts
|
|
|
|
end
|
|
|
|
|
|
|
|
test "allows loading many_to_many relationship" do
|
|
|
|
related_post1 = Ash.create!(Post, %{title: "Related 1"})
|
|
|
|
related_post2 = Ash.create!(Post, %{title: "Related 2"})
|
|
|
|
|
|
|
|
load_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.sort(title: :asc)
|
|
|
|
|> Ash.Query.select([:title])
|
|
|
|
|
|
|
|
assert %Ash.BulkResult{records: [post]} =
|
|
|
|
Post
|
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "Title"})
|
|
|
|
|> Ash.Changeset.manage_relationship(
|
|
|
|
:related_posts,
|
|
|
|
[related_post2, related_post1],
|
|
|
|
type: :append_and_remove
|
|
|
|
)
|
|
|
|
|> Ash.create!()
|
|
|
|
|> List.wrap()
|
|
|
|
|> Ash.bulk_destroy!(:destroy, %{},
|
|
|
|
resource: Post,
|
|
|
|
return_records?: true,
|
|
|
|
load: [related_posts: load_query]
|
|
|
|
)
|
|
|
|
|
|
|
|
assert [%Post{title: "Related 1"}, %Post{title: "Related 2"}] = post.related_posts
|
|
|
|
end
|
|
|
|
end
|
2023-12-21 15:08:40 +13:00
|
|
|
end
|