Skip to content

Commit

Permalink
wip: first draft of a unit test to reproduce this type of issue:
Browse files Browse the repository at this point in the history
1) test Thing.create! create works without associating other things
(ThingTest)
     test/thing_test.exs:20
     ** (Ash.Error.Unknown)
     Bread Crumbs:
       > Exception raised in: Things.get_by_id

     Unknown Error

     * ** (RuntimeError) Error while building reference:
all_other_things.associated_at
       (ash_sql 0.2.38) lib/expr.ex:1796:
AshSql.Expr.default_dynamic_expr/6
       (ash_sql 0.2.38) lib/expr.ex:88:
AshSql.Expr.default_dynamic_expr/6
  • Loading branch information
simpers committed Dec 8, 2024
1 parent 6da0cf0 commit 05c0e68
Show file tree
Hide file tree
Showing 7 changed files with 472 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "role",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "was_cancelled_at",
"type": "utc_datetime"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "co_authored_posts_author_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "authors"
},
"size": null,
"source": "author_id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "co_authored_posts_post_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "posts"
},
"size": null,
"source": "post_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "E1F2AC3AED1987928E3A2446584C268EC54D0BCA616D81A495F4AB26E3999444",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshPostgres.TestRepo",
"schema": null,
"table": "co_authored_posts"
}
48 changes: 48 additions & 0 deletions priv/test_repo/migrations/20241208221219_migrate_resources44.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
defmodule AshPostgres.TestRepo.Migrations.MigrateResources44 do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""

use Ecto.Migration

def up do
create table(:co_authored_posts, primary_key: false) do
add(:role, :text, null: false)
add(:was_cancelled_at, :utc_datetime)

add(
:author_id,
references(:authors,
column: :id,
name: "co_authored_posts_author_id_fkey",
type: :uuid,
prefix: "public"
),
primary_key: true,
null: false
)

add(
:post_id,
references(:posts,
column: :id,
name: "co_authored_posts_post_id_fkey",
type: :uuid,
prefix: "public"
),
primary_key: true,
null: false
)
end
end

def down do
drop(constraint(:co_authored_posts, "co_authored_posts_author_id_fkey"))

drop(constraint(:co_authored_posts, "co_authored_posts_post_id_fkey"))

drop(table(:co_authored_posts))
end
end
212 changes: 212 additions & 0 deletions test/many_to_many_expr_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
defmodule AshPostgres.ManyToManyExprTest do
use AshPostgres.RepoCase, async: false

alias AshPostgres.Test.Author
alias AshPostgres.Test.CoAuthorPost
alias AshPostgres.Test.Post

require Ash.Query

setup ctx do
main_author =
if ctx[:main_author?],
do: create_author(),
else: nil

co_authors =
if ctx[:co_authors],
do: 1..ctx[:co_authors]
|> Stream.map(&
Author
|> Ash.Changeset.for_create(:create, %{first_name: "John #{&1}", last_name: "Doe"})
|> Ash.create!()
)
|> Enum.into([]),
else: []

%{
main_author: main_author,
co_authors: co_authors,
}
end

def create_author(params \\ %{first_name: "John", last_name: "Doe"}) do
Author
|> Ash.Changeset.for_create(:create, params)
|> Ash.create!()
end

def create_post(author) do
Post
|> Ash.Changeset.for_create(:create, %{title: "Post by #{author.first_name}"})
|> Ash.create!()
end

def create_co_author_post(author, post, role) do
CoAuthorPost
|> Ash.Changeset.for_create(:create, %{author_id: author.id, post_id: post.id, role: role})
|> Ash.create!()
end

def get_author!(author_id) do
Author
|> Ash.Query.new()
|> Ash.Query.filter(id == ^author_id)
|> Ash.Query.load([:all_co_authored_posts, :cancelled_co_authored_posts, :editor_of, :writer_of])
|> Ash.read_one!()
end

def get_co_author_post!(a_id, p_id) do
CoAuthorPost
|> Ash.Query.new()
|> Ash.Query.filter(author_id == ^a_id and post_id == ^p_id)
|> Ash.read_one!()
end

def get_post!(post_id) do
Post.get_by_id!(post_id, load: [:co_author_posts, :co_authors_unfiltered, :co_authors])
end

def cancel(author, post) do
get_co_author_post!(author.id, post.id)
|> CoAuthorPost.cancel()
end

def uncancel(author, post) do
get_co_author_post!(author.id, post.id)
|> CoAuthorPost.uncancel()
end

describe "manual join-resource insertion" do
@tag main_author?: true
@tag co_authors: 3
test "filter on many_to_many relationship using parent works as expected - basic",
%{
main_author: main_author,
co_authors: co_authors,
} do
post = create_post(main_author)

[first_ca, second_ca, third_ca] = co_authors

# Add first co-author
create_co_author_post(first_ca, post, :editor)

first_ca = get_author!(first_ca.id)
post = get_post!(post.id)

assert Enum.count(post.co_authors) == 1
assert Enum.count(first_ca.all_co_authored_posts) == 1
assert Enum.count(first_ca.editor_of) == 1
assert Enum.count(first_ca.writer_of) == 0
assert Enum.count(first_ca.cancelled_co_authored_posts) == 0

# Add second co-author
create_co_author_post(second_ca, post, :writer)

second_ca = get_author!(second_ca.id)
post = get_post!(post.id)

assert Enum.count(post.co_authors) == 2
assert Enum.count(second_ca.all_co_authored_posts) == 1
assert Enum.count(second_ca.editor_of) == 0
assert Enum.count(second_ca.writer_of) == 1
assert Enum.count(second_ca.cancelled_co_authored_posts) == 0

Check warning on line 114 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 114 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 114 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

# Add third co-author
create_co_author_post(third_ca, post, :proof_reader)

third_ca = get_author!(third_ca.id)
post = get_post!(post.id)

assert Enum.count(post.co_authors) == 3
assert Enum.count(third_ca.all_co_authored_posts) == 1
assert Enum.count(third_ca.editor_of) == 0

Check warning on line 124 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 124 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 124 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.
assert Enum.count(third_ca.writer_of) == 0

Check warning on line 125 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 125 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 125 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.
assert Enum.count(third_ca.cancelled_co_authored_posts) == 0

Check warning on line 126 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 126 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 126 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.
end

@tag main_author?: true
@tag co_authors: 4
test "filter on many_to_many relationship using parent works as expected - cancelled",
%{
main_author: main_author,
co_authors: co_authors,
} do
first_post = create_post(main_author)
second_post = create_post(main_author)

[first_ca, second_ca, third_ca, fourth_ca] = co_authors

# Add first co-author
create_co_author_post(first_ca, first_post, :editor)
create_co_author_post(first_ca, second_post, :writer)

first_ca = get_author!(first_ca.id)
first_post = get_post!(first_post.id)

assert Enum.count(first_post.co_authors) == 1
assert Enum.count(first_post.co_authors_unfiltered) == 1

assert Enum.count(first_ca.all_co_authored_posts) == 2
assert Enum.count(first_ca.editor_of) == 1
assert Enum.count(first_ca.writer_of) == 1
assert Enum.count(first_ca.cancelled_co_authored_posts) == 0

Check warning on line 154 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 154 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 154 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

# Add second co-author
create_co_author_post(second_ca, first_post, :proof_reader)
create_co_author_post(second_ca, second_post, :writer)

second_ca = get_author!(second_ca.id)
first_post = get_post!(first_post.id)
second_post = get_post!(second_post.id)

assert Enum.count(second_post.co_authors) == 2
assert Enum.count(second_post.co_authors_unfiltered) == 2

assert Enum.count(second_ca.all_co_authored_posts) == 2
assert Enum.count(second_ca.editor_of) == 0

Check warning on line 168 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 168 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 168 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.
assert Enum.count(second_ca.writer_of) == 1
assert Enum.count(second_ca.cancelled_co_authored_posts) == 0

Check warning on line 170 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 170 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 170 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

# Add third co-author
create_co_author_post(third_ca, first_post, :proof_reader)
create_co_author_post(third_ca, second_post, :proof_reader)
cancel(third_ca, second_post)

third_ca = get_author!(third_ca.id)
first_post = get_post!(first_post.id)
second_post = get_post!(second_post.id)

assert Enum.count(first_post.co_authors) == 3
assert Enum.count(first_post.co_authors_unfiltered) == 3
assert Enum.count(second_post.co_authors) == 2
assert Enum.count(second_post.co_authors_unfiltered) == 3

assert Enum.count(third_ca.all_co_authored_posts) == 2
assert Enum.count(third_ca.editor_of) == 0

Check warning on line 187 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 187 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 187 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.
assert Enum.count(third_ca.writer_of) == 0

Check warning on line 188 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 188 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 188 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.
assert Enum.count(third_ca.cancelled_co_authored_posts) == 1

# Add fourth co-author
create_co_author_post(fourth_ca, first_post, :proof_reader)
create_co_author_post(fourth_ca, second_post, :editor)
cancel(fourth_ca, first_post)
cancel(fourth_ca, second_post)

fourth_ca = get_author!(fourth_ca.id)
first_post = get_post!(first_post.id)
second_post = get_post!(second_post.id)

assert Enum.count(first_post.co_authors) == 3
assert Enum.count(first_post.co_authors_unfiltered) == 4
assert Enum.count(second_post.co_authors) == 2
assert Enum.count(second_post.co_authors_unfiltered) == 4

assert Enum.count(fourth_ca.all_co_authored_posts) == 2
assert Enum.count(fourth_ca.editor_of) == 1
assert Enum.count(fourth_ca.writer_of) == 0

Check warning on line 208 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 208 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.

Check warning on line 208 in test/many_to_many_expr_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

Enum.count is expensive, prefer `Enum.empty?/1` or `list == []`.
assert Enum.count(fourth_ca.cancelled_co_authored_posts) == 2
end
end
end
1 change: 1 addition & 0 deletions test/support/domain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule AshPostgres.Test.Domain do
use Ash.Domain

resources do
resource(AshPostgres.Test.CoAuthorPost)
resource(AshPostgres.Test.Post)
resource(AshPostgres.Test.Comment)
resource(AshPostgres.Test.IntegerPost)
Expand Down
Loading

0 comments on commit 05c0e68

Please sign in to comment.