From a098a830b1ccaf6b3b237b531fd9c3c384e9c8ec Mon Sep 17 00:00:00 2001 From: Robert Graff Date: Tue, 28 Nov 2023 11:32:38 -0800 Subject: [PATCH] test: Nested form errors for argument attribute (#119) --- .gitpod.yml | 5 +++ test/form_test.exs | 41 ++++++++++++++++- test/support/resources/author.ex | 8 ++++ test/support/resources/embedded_argument.ex | 8 ++++ .../validations/validate_embedded_argument.ex | 44 +++++++++++++++++++ 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 .gitpod.yml create mode 100644 test/support/resources/embedded_argument.ex create mode 100644 test/support/validations/validate_embedded_argument.ex diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..326062b --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,5 @@ +image: elixir:latest + +tasks: + - init: mix deps.get && mix compile + command: iex -S mix \ No newline at end of file diff --git a/test/form_test.exs b/test/form_test.exs index bcbad65..a48057a 100644 --- a/test/form_test.exs +++ b/test/form_test.exs @@ -4,7 +4,7 @@ defmodule AshPhoenix.FormTest do import ExUnit.CaptureLog alias AshPhoenix.Form - alias AshPhoenix.Test.{Api, Comment, OtherApi, Post, PostWithDefault} + alias AshPhoenix.Test.{Api, Author, Comment, OtherApi, Post, PostWithDefault} alias Phoenix.HTML.FormData describe "validate_opts" do @@ -782,6 +782,45 @@ defmodule AshPhoenix.FormTest do assert [nested_form] = inputs_for(form, :post) assert nested_form.errors == [{:text, {"is required", []}}] end + + test "errors with a path are propagated down to the appropirate nested form" do + author = %Author{ + email: "me@example.com" + } + + form = + author + |> Form.for_update(:update_with_embedded_argument, api: Api, forms: [auto?: true]) + |> Form.add_form(:embedded_argument, params: %{}) + |> Form.validate(%{"embedded_argument" => %{"value" => "you@example.com"}}) + |> form_for("action") + [nested_form] = inputs_for(form, :embedded_argument) + + + # This is the top level error with a path to the nest form. + assert [ + %Ash.Error.Changes.InvalidArgument{ + field: :value, + message: "must match email", + value: "you@example.com", + path: [:embedded_argument], + class: :invalid + } + ] = form.source.source.errors + assert form.errors == [] + + # This is the error on the nested form. + assert [ + %Ash.Error.Changes.InvalidArgument{ + field: :value, + message: "must match email", + value: "you@example.com", + path: [], + class: :invalid + } + ] = nested_form.source.source.errors + assert nested_form.errors == [{:value, {"must match email", []}}] + end end describe "data" do diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index c589c3b..e6e1fb3 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -13,6 +13,14 @@ defmodule AshPhoenix.Test.Author do actions do defaults([:create, :read, :update]) + + update :update_with_embedded_argument do + # This an empty change, just so test how we handle errors on embedded arguments + accept [] + argument :embedded_argument, AshPhoenix.Test.EmbeddedArgument, allow_nil?: false + + validate { AshPhoenix.Test.ValidateEmbeddedArgument, [] } + end end relationships do diff --git a/test/support/resources/embedded_argument.ex b/test/support/resources/embedded_argument.ex new file mode 100644 index 0000000..2055d87 --- /dev/null +++ b/test/support/resources/embedded_argument.ex @@ -0,0 +1,8 @@ +defmodule AshPhoenix.Test.EmbeddedArgument do + @moduledoc false + use Ash.Resource, data_layer: :embedded + + attributes do + attribute :value, :string + end +end diff --git a/test/support/validations/validate_embedded_argument.ex b/test/support/validations/validate_embedded_argument.ex new file mode 100644 index 0000000..a3b50b7 --- /dev/null +++ b/test/support/validations/validate_embedded_argument.ex @@ -0,0 +1,44 @@ +defmodule AshPhoenix.Test.ValidateEmbeddedArgument do + @moduledoc """ + This is a contrived example, but we want to validate one or more arguments' attributes + against an attribute on the parent resource and then put an error on the + changeset that will get propogated down to the nest form for the embedded argument. + """ + + use Ash.Resource.Validation + + # This is the name of our embedded argument + @embedded_argument :embedded_argument + + # This is the name of the attribute on the embedded argument + @embedded_attribute :value + + @impl true + def validate(changeset, _opts) do + case Ash.Changeset.get_argument(changeset, @embedded_argument) do + nil -> + :ok + + argument -> + apply_validation(changeset, argument) + end + end + + def apply_validation(changeset, argument) do + email = Ash.Changeset.get_attribute(changeset, :email) + value = Map.get(argument, @embedded_attribute) + + if value == email do + :ok + else + {:error, + Ash.Error.Changes.InvalidArgument.exception( + field: @embedded_attribute, + message: "must match email", + value: value, + path: [@embedded_argument] + ) + } + end + end +end