From 7e0b5d52ce7cdf77c01863968428c6a883a37143 Mon Sep 17 00:00:00 2001 From: Wilmer Cantillo Date: Sun, 10 Aug 2025 22:32:19 -0500 Subject: [PATCH 1/3] failed to filter array repro. fix assert_valid_resource_objects --- lib/ash_json_api/test/test.ex | 17 ++++--- .../fetching_data/filtering_test.exs | 47 ++++++++++++++++++- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/lib/ash_json_api/test/test.ex b/lib/ash_json_api/test/test.ex index c6165f82..a822ea3b 100644 --- a/lib/ash_json_api/test/test.ex +++ b/lib/ash_json_api/test/test.ex @@ -362,13 +362,16 @@ defmodule AshJsonApi.Test do "data" => results } = conn.resp_body - assert Enum.all?(results, fn - %{"type" => ^expected_type, "id" => maybe_known_id} -> - Enum.member?(expected_ids, maybe_known_id) - - _ -> - false - end) + # Extract actual IDs from results + actual_ids = + Enum.map(results, fn + %{"type" => ^expected_type, "id" => id} -> id + other -> flunk("Unexpected result: #{inspect(other)}") + end) + + # Check that we have exactly the expected IDs (order doesn't matter) + assert Enum.sort(actual_ids) == Enum.sort(expected_ids), + "Expected IDs #{inspect(expected_ids)}, but got #{inspect(actual_ids)}" conn end diff --git a/test/spec_compliance/fetching_data/filtering_test.exs b/test/spec_compliance/fetching_data/filtering_test.exs index 61d42406..2559bf99 100644 --- a/test/spec_compliance/fetching_data/filtering_test.exs +++ b/test/spec_compliance/fetching_data/filtering_test.exs @@ -4,6 +4,19 @@ defmodule AshJsonApiTest.FetchingData.Filtering do # credo:disable-for-this-file Credo.Check.Readability.MaxLineLength + defmodule Narrative do + use Ash.Type.Enum, + values: [ + :novel, + :legend, + :myth, + :fable, + :tale, + :action, + :plot + ] + end + defmodule Author do use Ash.Resource, domain: AshJsonApiTest.FetchingData.Filtering.Domain, @@ -72,13 +85,18 @@ defmodule AshJsonApiTest.FetchingData.Filtering do defaults([:read, :update, :destroy]) create :create do - accept([:name, :author_id]) + accept([:name, :author_id, :narratives]) end end attributes do uuid_primary_key(:id) attribute(:name, :string, public?: true) + + attribute(:narratives, {:array, Narrative}, + default: [], + public?: true + ) end relationships do @@ -162,6 +180,33 @@ defmodule AshJsonApiTest.FetchingData.Filtering do |> assert_invalid_resource_objects("post", [post.id]) end + test "in/not_in filter" do + post = + Post + |> Ash.Changeset.for_create(:create, %{name: "foo", narratives: [:novel, :legend]}) + |> Ash.create!() + + post2 = + Post + |> Ash.Changeset.for_create(:create, %{name: "bar", narratives: [:legend]}) + |> Ash.create!() + + _conn = + Domain + |> get("/posts?filter[narratives][in]=novel,legend", status: 200) + |> assert_valid_resource_objects("post", [post.id, post2.id]) + + # _conn = + # Domain + # |> get("/posts?filter[narratives][in][][]=novel", status: 200) + # |> assert_valid_resource_objects("post", [post.id, post2.id]) + + # _conn = + # Domain + # |> get("/posts?filter[narratives][not_in]=novel,legend", status: 200) + # |> assert_valid_resource_objects("post", []) + end + test "is_nil filter" do post = Post From 7158ff71468782f8daf7cc2f5dbb40a0512c55ac Mon Sep 17 00:00:00 2001 From: Wilmer Cantillo Date: Mon, 11 Aug 2025 10:14:32 -0500 Subject: [PATCH 2/3] test: add array filtering test cases with different query param formats --- .../fetching_data/filtering_test.exs | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/test/spec_compliance/fetching_data/filtering_test.exs b/test/spec_compliance/fetching_data/filtering_test.exs index 2559bf99..e2f38428 100644 --- a/test/spec_compliance/fetching_data/filtering_test.exs +++ b/test/spec_compliance/fetching_data/filtering_test.exs @@ -191,16 +191,51 @@ defmodule AshJsonApiTest.FetchingData.Filtering do |> Ash.Changeset.for_create(:create, %{name: "bar", narratives: [:legend]}) |> Ash.create!() - _conn = - Domain - |> get("/posts?filter[narratives][in]=novel,legend", status: 200) - |> assert_valid_resource_objects("post", [post.id, post2.id]) + # single value + # _conn = + # Domain + # |> get("/filter[narratives][in]=novel", + # status: 200 + # ) + # |> assert_valid_resource_objects("post", [post.id, post2.id]) + + # comma-separated + # _conn = + # Domain + # |> get("/posts?filter[narratives][in]=novel,legend", + # status: 200 + # ) + # |> assert_valid_resource_objects("post", [post.id, post2.id]) + + # PHP-style array + # _conn = + # Domain + # |> get("/posts?filter[narratives][in][]=novel&filter[narratives][in][]=legend", + # status: 200 + # ) + # |> assert_valid_resource_objects("post", [post.id, post2.id]) + # nested array # _conn = # Domain - # |> get("/posts?filter[narratives][in][][]=novel", status: 200) + # |> get("/posts?filter[narratives][in][][]=novel ", + # status: 200 + # ) # |> assert_valid_resource_objects("post", [post.id, post2.id]) + # bracket notation + # _conn = + # Domain + # |> get("/posts?filter[narratives][in]=[novel,legend]", status: 200) + # |> assert_valid_resource_objects("post", [post.id, post2.id]) + + _conn = + Domain + |> get("/posts?filter[narratives][in][0]=novel&filter[narratives][in][1]=legend", + status: 200 + ) + |> assert_valid_resource_objects("post", [post.id, post2.id]) + # _conn = # Domain # |> get("/posts?filter[narratives][not_in]=novel,legend", status: 200) From 65219c3d9157e45de83a0110857b9a1a9376bb20 Mon Sep 17 00:00:00 2001 From: Wilmer Cantillo Date: Mon, 11 Aug 2025 10:59:38 -0500 Subject: [PATCH 3/3] test: add array filtering tests for equals and in/not_in operators --- .../fetching_data/filtering_test.exs | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/test/spec_compliance/fetching_data/filtering_test.exs b/test/spec_compliance/fetching_data/filtering_test.exs index e2f38428..fbb21e14 100644 --- a/test/spec_compliance/fetching_data/filtering_test.exs +++ b/test/spec_compliance/fetching_data/filtering_test.exs @@ -180,7 +180,49 @@ defmodule AshJsonApiTest.FetchingData.Filtering do |> assert_invalid_resource_objects("post", [post.id]) end - test "in/not_in filter" do + test "equals array filter" do + post = + Post + |> Ash.Changeset.for_create(:create, %{name: "foo", narratives: [:novel, :legend]}) + |> Ash.create!() + + post2 = + Post + |> Ash.Changeset.for_create(:create, %{name: "bar", narratives: [:legend]}) + |> Ash.create!() + + _conn = + Domain + |> get("/posts?filter[narratives][]=novel&filter[narratives][]=legend", + status: 200 + ) + |> assert_valid_resource_objects("post", [post.id]) + + # order of values matters + _conn = + Domain + |> get("/posts?filter[narratives][]=legend&filter[narratives][]=novel", + status: 200 + ) + |> assert_valid_resource_objects("post", []) + + _conn = + Domain + |> get("/posts?filter[narratives][]=legend", + status: 200 + ) + |> assert_valid_resource_objects("post", [post2.id]) + + # it's not inclusion check + _conn = + Domain + |> get("/posts?filter[narratives][]=novel", + status: 200 + ) + |> assert_valid_resource_objects("post", []) + end + + test "in/not_in array filter" do post = Post |> Ash.Changeset.for_create(:create, %{name: "foo", narratives: [:novel, :legend]}) @@ -194,7 +236,7 @@ defmodule AshJsonApiTest.FetchingData.Filtering do # single value # _conn = # Domain - # |> get("/filter[narratives][in]=novel", + # |> get("posts/filter[narratives][in]=novel", # status: 200 # ) # |> assert_valid_resource_objects("post", [post.id, post2.id]) @@ -207,6 +249,17 @@ defmodule AshJsonApiTest.FetchingData.Filtering do # ) # |> assert_valid_resource_objects("post", [post.id, post2.id]) + # deep object + # _conn = + # Domain + # |> get( + # URI.encode( + # "/posts?filter=[{name:\"narratives\", op: \"in\", val: [\"novel\",\"legend\"]}]" + # ), + # status: 200 + # ) + # |> assert_valid_resource_objects("post", [post.id, post2.id]) + # PHP-style array # _conn = # Domain