ash/test/error_test.exs

201 lines
7.5 KiB
Elixir

defmodule Ash.Test.ErrorTest do
@moduledoc false
use ExUnit.Case, async: true
defmodule TestError do
use Ash.Error.Exception
def_ash_error([:some_field])
end
defmodule TestResource do
use Ash.Resource, data_layer: Ash.DataLayer.Ets
actions do
defaults [:create, :read, :update, :destroy]
end
attributes do
uuid_primary_key :id
end
end
describe "to_error_class" do
test "returns exception if it is a map/struct with class: :special" do
assert Ash.Error.to_error_class(%{class: :special}, []) == %{class: :special}
end
test "returns exception if it is a map/struct with class: :special wrapped in a list" do
assert Ash.Error.to_error_class([%{class: :special}], []) == %{class: :special}
end
test "returns exception if it is a map/struct with class: :special wrapped in an Invalid error" do
err = Ash.Error.Invalid.exception(errors: [%{class: :special}])
assert Ash.Error.to_error_class(err, []) == %{class: :special}
end
test "returns chosen error if the value argument is a list of values" do
values = [[a: "a", b: "b"], [c: "c", d: "d"], "foo", "bar"]
result = Ash.Error.to_error_class(values, [])
assert match?(%Ash.Error.Unknown{}, result)
# given the test arrangement, each error in the error class should
# * be an Ash.Error.Unknown.UnknownError
# * have a .class == :unknown
# * have a .error that is a distinct element of the uniq subset of the values provided
assert same_elements?(Enum.map(result.errors, fn err -> err.error end), values)
for err <- result.errors do
assert match?(%Ash.Error.Unknown.UnknownError{}, err)
assert err.class == :unknown
end
end
test "returns chosen error if the value argument is a list of errors" do
err1 = Ash.Error.Unknown.UnknownError.exception(error: :an_error)
err2 = Ash.Error.Invalid.exception(errors: [:more, :errors])
result = Ash.Error.to_error_class([err1, err2], [])
# the parent error will be of the invalid class because it take precedence over unknown
assert match?(%Ash.Error.Invalid{}, result)
# the parent error's errors field gets prepended to the list of other errors
assert same_elements?(result.errors, [:more, :errors, err1])
end
test "has a context field populated when there is a single error" do
test_error = TestError.exception([])
err = Ash.Error.to_error_class(test_error, error_context: "some context")
assert err.error_context == ["some context"]
end
test "has a context field populated when there is a list of errors" do
test_error1 = TestError.exception(some_field: :a)
test_error2 = TestError.exception(some_field: :b)
err = Ash.Error.to_error_class([test_error1, test_error2], error_context: "some context")
assert err.error_context == ["some context"]
end
test "accumulates error_context field in child errors" do
error1 = Ash.Error.to_ash_error("whoops!", nil, error_context: "some context")
error2 = Ash.Error.to_ash_error("whoops, again!!", nil, error_context: "some other context")
error_class =
Ash.Error.to_error_class([error1, error2], error_context: "some higher context")
child_error_1 = Enum.find(error_class.errors, fn err -> err.error == "whoops!" end)
assert child_error_1.error_context == ["some higher context", "some context"]
child_error_2 = Enum.find(error_class.errors, fn err -> err.error == "whoops, again!!" end)
assert child_error_2.error_context == ["some higher context", "some other context"]
end
test "accumulates error_context field in child errors who have no error_context of their own" do
error1 = Ash.Error.to_ash_error("whoops!", nil, error_context: "some context")
error2 = Ash.Error.to_ash_error("whoops, again!!", nil)
error_class =
Ash.Error.to_error_class([error1, error2], error_context: "some higher context")
child_error_1 = Enum.find(error_class.errors, fn err -> err.error == "whoops!" end)
assert child_error_1.error_context == ["some higher context", "some context"]
child_error_2 = Enum.find(error_class.errors, fn err -> err.error == "whoops, again!!" end)
assert child_error_2.error_context == ["some higher context"]
end
test "leaves child error contexts unchanged if no error_context field provided" do
error1 = Ash.Error.to_ash_error("whoops!", nil, error_context: "some context")
error2 = Ash.Error.to_ash_error("whoops, again!!", nil, error_context: "some other context")
error_class = Ash.Error.to_error_class([error1, error2])
child_error_1 = Enum.find(error_class.errors, fn err -> err.error == "whoops!" end)
assert child_error_1.error_context == ["some context"]
child_error_2 = Enum.find(error_class.errors, fn err -> err.error == "whoops, again!!" end)
assert child_error_2.error_context == ["some other context"]
end
test "error message contains error context breadcrumbs" do
error1 = Ash.Error.to_ash_error("whoops!", nil, error_context: "some context")
error2 = Ash.Error.to_ash_error("whoops, again!!", nil, error_context: "some other context")
error_class =
Ash.Error.to_error_class([error1, error2], error_context: "some higher context")
error_message = Ash.Error.Unknown.message(error_class)
assert error_message =~ "Context: some higher context > some context"
assert error_message =~ "Context: some higher context > some other context"
end
test "error message still renders when there's no error context" do
error1 = Ash.Error.to_ash_error("whoops!")
error2 = Ash.Error.to_ash_error("whoops, again!!")
error_class = Ash.Error.to_error_class([error1, error2])
error_message = Ash.Error.Unknown.message(error_class)
assert error_message =~ "Unknown Error\n\n* whoops!"
end
test "has a context field populated in changeset" do
test_error = TestError.exception([])
cs = Ash.Changeset.for_create(TestResource, :create)
err = Ash.Error.to_error_class(test_error, changeset: cs, error_context: "some context")
assert err.error_context == ["some context"]
[cs_error] = err.changeset.errors
assert cs_error.error_context == ["some context"]
end
test "accumulates error_context field in changeset's copy of error hierarchy" do
error1 = Ash.Error.to_ash_error("whoops!", nil, error_context: "some context")
error2 = Ash.Error.to_ash_error("whoops, again!!", nil, error_context: "some other context")
cs = Ash.Changeset.for_create(TestResource, :create)
error_class =
Ash.Error.to_error_class([error1, error2],
changeset: cs,
error_context: "some higher context"
)
cs_child_error_1 =
Enum.find(error_class.changeset.errors, fn err -> err.error == "whoops!" end)
cs_child_error_2 =
Enum.find(error_class.changeset.errors, fn err -> err.error == "whoops, again!!" end)
assert cs_child_error_1.error_context == ["some higher context", "some context"]
assert cs_child_error_2.error_context == ["some higher context", "some other context"]
end
end
describe "to_ash_error" do
test "populates error_context field" do
error = Ash.Error.to_ash_error("whoops!", nil, error_context: "some context")
assert error.error_context == ["some context"]
end
end
defp same_elements?(xs, ys) when is_list(xs) and is_list(ys) do
Enum.sort(xs) == Enum.sort(ys)
end
defp same_elements?(_, _), do: false
end