ash_graphql/test/relay_test.exs

844 lines
22 KiB
Elixir
Raw Permalink Normal View History

defmodule AshGraphql.RelayTest do
use ExUnit.Case, async: false
require Ash.Query
setup do
on_exit(fn ->
2022-09-28 19:28:44 +13:00
AshGraphql.TestHelpers.stop_ets()
end)
end
describe "relay" do
setup do
letters = ["a", "b", "c", "d", "e"]
for name <- letters do
tag =
AshGraphql.Test.RelayTag
|> Ash.Changeset.for_create(
:create,
name: name
)
improvement!: port AshGraphql to Ash 3.0 (#123) Step 1: update Ash Step 2: mass rename Api to Domain Step 3: Ash.Query.expr -> Ash.Expr.expr Also change ref interpolation Step 4: remove all warnings Step 5: remove registries from tests Step 6: fix filter Step 7: private? -> !public? Step 8: Ash.Calculation -> Ash.Resource.Calculation Step 9: use depend_on_resources/1 -> resources/1 Step 10: add Domain to all resources Step 11: use Ash module for all actions Step 12: add public? true all around Step 13: remove verbose? from options passed during Domain calls Step 14: add simple_sat Step 15: Ash.ErrorKind is no more, so remove code from errors Step 16: sprinkle default_accept :* around tests Step 17: replace Ash.Changeset.new/2 with Ash.Changeset.for_* Step 18: calculation fixups - Context is now a struct and arguments go under the arguments key - Function based calculations receive a list of records - Add a select to query-based loads - select -> load Step 19: pass the correct name to pass the policy in tests Step 20: Ash.Query.new/2 is no more Step 21: add AshGraphql.Resource.embedded? utility function Use that instead of Ash.Type.embedded_type?(resource_or_type) since resources are not types anymore Step 22: handle struct + instance_of: Resource in unions Resources are not type anymore so they need to be passed this way in unions Step 23: ensure we only check GraphQL actions for pagination All reads are now paginated by default, so this triggered a compilation error Step 24: swap arguments for sort on calculations Step 25: remove unused debug? option
2024-04-02 07:03:06 +13:00
|> Ash.create!()
for text <- letters do
AshGraphql.Test.Post
|> Ash.Changeset.for_create(:create, text: text, published: true)
|> Ash.Changeset.manage_relationship(
:relay_tags,
[tag],
on_no_match: :error,
on_lookup: :relate_and_update
)
improvement!: port AshGraphql to Ash 3.0 (#123) Step 1: update Ash Step 2: mass rename Api to Domain Step 3: Ash.Query.expr -> Ash.Expr.expr Also change ref interpolation Step 4: remove all warnings Step 5: remove registries from tests Step 6: fix filter Step 7: private? -> !public? Step 8: Ash.Calculation -> Ash.Resource.Calculation Step 9: use depend_on_resources/1 -> resources/1 Step 10: add Domain to all resources Step 11: use Ash module for all actions Step 12: add public? true all around Step 13: remove verbose? from options passed during Domain calls Step 14: add simple_sat Step 15: Ash.ErrorKind is no more, so remove code from errors Step 16: sprinkle default_accept :* around tests Step 17: replace Ash.Changeset.new/2 with Ash.Changeset.for_* Step 18: calculation fixups - Context is now a struct and arguments go under the arguments key - Function based calculations receive a list of records - Add a select to query-based loads - select -> load Step 19: pass the correct name to pass the policy in tests Step 20: Ash.Query.new/2 is no more Step 21: add AshGraphql.Resource.embedded? utility function Use that instead of Ash.Type.embedded_type?(resource_or_type) since resources are not types anymore Step 22: handle struct + instance_of: Resource in unions Resources are not type anymore so they need to be passed this way in unions Step 23: ensure we only check GraphQL actions for pagination All reads are now paginated by default, so this triggered a compilation error Step 24: swap arguments for sort on calculations Step 25: remove unused debug? option
2024-04-02 07:03:06 +13:00
|> Ash.create!()
end
end
:ok
end
test "neither first nor last passed" do
page = """
query PaginatedPosts {
getRelayTags(sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
count
edges{
cursor
node {
name
}
}
}
}
"""
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"count" => 5,
"pageInfo" => %{
"hasNextPage" => false,
"hasPreviousPage" => false,
"startCursor" => start_cursor,
"endCursor" => end_cursor
},
# relay returned all the records
"edges" => [
%{
"cursor" => start_cursor,
"node" => %{"name" => "a"}
},
%{
"cursor" => _,
"node" => %{"name" => "b"}
},
%{
"cursor" => _,
"node" => %{"name" => "c"}
},
%{
"cursor" => _,
"node" => %{"name" => "d"}
},
%{
"cursor" => end_cursor,
"node" => %{"name" => "e"}
}
]
}
}
}} = Absinthe.run(page, AshGraphql.Test.Schema)
end
test "first page contains few records" do
page = """
query PaginatedPosts {
getRelayTags(first: 2, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"hasNextPage" => true,
"hasPreviousPage" => false,
"startCursor" => start_cursor,
"endCursor" => end_cursor
},
# relay returned only first 2 records
"edges" => [
%{
"cursor" => start_cursor,
"node" => %{"name" => "a"}
},
%{
"cursor" => end_cursor,
"node" => %{"name" => "b"}
}
]
}
}
}} = Absinthe.run(page, AshGraphql.Test.Schema)
end
test "first page contains all records" do
doc = """
query PaginatedPosts {
getRelayTags(first: 6, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"hasNextPage" => false,
"hasPreviousPage" => false,
"startCursor" => start_cursor,
"endCursor" => end_cursor
},
# relay returned all the records
"edges" => [
%{
"cursor" => start_cursor,
"node" => %{"name" => "a"}
},
%{
"cursor" => _,
"node" => %{"name" => "b"}
},
%{
"cursor" => _,
"node" => %{"name" => "c"}
},
%{
"cursor" => _,
"node" => %{"name" => "d"}
},
%{
"cursor" => end_cursor,
"node" => %{"name" => "e"}
}
]
}
}
}} = Absinthe.run(doc, AshGraphql.Test.Schema)
end
test "first with starting cursor" do
page = """
query PaginatedPosts($after: String) {
getRelayTags(first: 2, after: $after, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
# cursor is matching "a" tag
{:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"startCursor" => start_cursor1
}
}
}
}} = Absinthe.run(page, AshGraphql.Test.Schema)
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"hasNextPage" => true,
"hasPreviousPage" => true,
"startCursor" => start_cursor2,
"endCursor" => end_cursor2
},
# relay returned only first 2 records
"edges" => [
%{
"cursor" => start_cursor2,
"node" => %{"name" => "b"}
},
%{
"cursor" => end_cursor2,
"node" => %{"name" => "c"}
}
]
}
}
}} =
Absinthe.run(page, AshGraphql.Test.Schema, variables: %{"after" => start_cursor1})
assert start_cursor1 != start_cursor2
end
test "first with middle cursor" do
page = """
query PaginatedPosts($after: String) {
getRelayTags(first: 2, after: $after, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
# cursor is matching "b" tag
{:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"endCursor" => end_cursor1
}
}
}
}} = Absinthe.run(page, AshGraphql.Test.Schema)
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"hasNextPage" => true,
"hasPreviousPage" => true,
"startCursor" => start_cursor2,
"endCursor" => end_cursor2
},
# relay returned only first 2 records
"edges" => [
%{
"cursor" => start_cursor2,
"node" => %{"name" => "c"}
},
%{
"cursor" => end_cursor2,
"node" => %{"name" => "d"}
}
]
}
}
}} =
Absinthe.run(page, AshGraphql.Test.Schema, variables: %{"after" => end_cursor1})
assert end_cursor1 != start_cursor2
assert end_cursor1 != end_cursor2
end
test "first with final cursor" do
page = """
query PaginatedPosts($after: String) {
getRelayTags(first: 20, after: $after, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
# cursor is matching "f" tag
{:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"endCursor" => end_cursor1
}
}
}
}} = Absinthe.run(page, AshGraphql.Test.Schema)
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"hasNextPage" => false,
"hasPreviousPage" => true,
"startCursor" => nil,
"endCursor" => nil
},
"edges" => []
}
}
}} =
Absinthe.run(page, AshGraphql.Test.Schema, variables: %{"after" => end_cursor1})
end
test "last with starting cursor" do
page1 = """
query PaginatedPosts {
getRelayTags(first: 1, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
# cursor is matching "a" tag
{:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"startCursor" => start_cursor1
}
}
}
}} = Absinthe.run(page1, AshGraphql.Test.Schema)
page2 = """
query PaginatedPosts($before: String) {
getRelayTags(last: 2, before: $before, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"hasNextPage" => false,
"hasPreviousPage" => false,
"endCursor" => nil,
"startCursor" => nil
},
"edges" => []
}
}
}} =
2023-08-01 07:32:02 +12:00
Absinthe.run(page2, AshGraphql.Test.Schema,
variables: %{"before" => start_cursor1}
)
end
test "last with middle cursor" do
page1 = """
query PaginatedPosts($after: String) {
getRelayTags(first: 3, after: $after, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
# cursor is matching "c" tag
{:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"endCursor" => end_cursor1
}
}
}
}} = Absinthe.run(page1, AshGraphql.Test.Schema)
page2 = """
query PaginatedPosts($before: String) {
getRelayTags(last: 1, before: $before, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"hasNextPage" => true,
"hasPreviousPage" => true,
"startCursor" => start_cursor2,
"endCursor" => start_cursor2
},
"edges" => [
%{
"cursor" => start_cursor2,
"node" => %{"name" => "b"}
}
]
}
}
}} =
Absinthe.run(page2, AshGraphql.Test.Schema, variables: %{"before" => end_cursor1})
assert end_cursor1 != start_cursor2
end
test "last with final cursor" do
page1 = """
query PaginatedPosts {
getRelayTags(first: 20, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
# cursor is matching "e" tag
{:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
"endCursor" => end_cursor1
}
}
}
}} = Absinthe.run(page1, AshGraphql.Test.Schema)
page2 = """
query PaginatedPosts($before: String) {
getRelayTags(last: 2, before: $before, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {:ok,
%{
data: %{
"getRelayTags" => %{
"pageInfo" => %{
# item matching 'before' cursor (== "e") is not returned
# but it can be fetched using first + after + endCursor of current page
"hasNextPage" => true,
"hasPreviousPage" => true,
"startCursor" => start_cursor2,
"endCursor" => end_cursor2
},
# relay returned only last 2 records
"edges" => [
%{
"cursor" => start_cursor2,
"node" => %{"name" => "c"}
},
%{
"cursor" => end_cursor2,
"node" => %{"name" => "d"}
}
]
}
}
}} =
Absinthe.run(page2, AshGraphql.Test.Schema, variables: %{"before" => end_cursor1})
assert end_cursor1 != start_cursor2
assert end_cursor1 != end_cursor2
end
end
describe "relay errors" do
test "both first and last are passed" do
page = """
query PaginatedPosts {
getRelayTags(last: 2, first: 10, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {
:ok,
%{
data: %{"getRelayTags" => nil},
errors: [
%{
locations: [%{column: 3, line: 2}],
message: "You can pass either `first` or `last`, not both",
path: ["getRelayTags"]
}
]
}
} = Absinthe.run(page, AshGraphql.Test.Schema)
end
test "last without before cursor" do
page = """
query PaginatedPosts {
getRelayTags(last: 2, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {
:ok,
%{
data: %{"getRelayTags" => nil},
errors: [
%{
locations: [%{column: 3, line: 2}],
message: "You can pass `last` only with `before` cursor",
path: ["getRelayTags"]
}
]
}
} = Absinthe.run(page, AshGraphql.Test.Schema)
end
test "wrong first/last with after/before combinations" do
page = """
query PaginatedPosts($first: Int, $last: Int, $before: String, $after: String) {
getRelayTags(first: $first, last: $last, before: $before, after: $after, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {
:ok,
%{
data: %{"getRelayTags" => nil},
errors: [
%{
locations: [%{column: 3, line: 2}],
message:
"You can pass either `first` and `after` cursor, or `last` and `before` cursor",
path: ["getRelayTags"]
}
]
}
} =
Absinthe.run(page, AshGraphql.Test.Schema,
variables: %{"first" => 20, "before" => "abc"}
)
assert {
:ok,
%{
data: %{"getRelayTags" => nil},
errors: [
%{
locations: [%{column: 3, line: 2}],
message:
"You can pass either `first` and `after` cursor, or `last` and `before` cursor",
path: ["getRelayTags"]
}
]
}
} =
Absinthe.run(page, AshGraphql.Test.Schema,
variables: %{"first" => 20, "after" => "abc", "before" => "abc"}
)
assert {
:ok,
%{
data: %{"getRelayTags" => nil},
errors: [
%{
locations: [%{column: 3, line: 2}],
message:
"You can pass either `first` and `after` cursor, or `last` and `before` cursor",
path: ["getRelayTags"]
}
]
}
} =
Absinthe.run(page, AshGraphql.Test.Schema,
variables: %{"last" => 20, "after" => "abc"}
)
assert {
:ok,
%{
data: %{"getRelayTags" => nil},
errors: [
%{
locations: [%{column: 3, line: 2}],
message:
"You can pass either `first` and `after` cursor, or `last` and `before` cursor",
path: ["getRelayTags"]
}
]
}
} =
Absinthe.run(page, AshGraphql.Test.Schema,
variables: %{"last" => 20, "after" => "abc", "before" => "abc"}
)
end
# return readable error message
test "invalid after cursor" do
page = """
query PaginatedPosts($after: String) {
getRelayTags(first: 2, after: $after, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {
:ok,
%{
data: %{"getRelayTags" => nil},
errors: [
%{
locations: [%{column: 3, line: 2}],
message: "Invalid value provided as a keyset for after: \"abc\"",
short_message: "invalid keyset",
path: ["getRelayTags"]
}
]
}
} = Absinthe.run(page, AshGraphql.Test.Schema, variables: %{"after" => "abc"})
end
test "invalid before cursor" do
page = """
query PaginatedPosts($before: String) {
getRelayTags(last: 1, before: $before, sort: [{field: NAME}]) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
cursor
node {
name
}
}
}
}
"""
assert {
:ok,
%{
data: %{"getRelayTags" => nil},
errors: [
%{
locations: [%{column: 3, line: 2}],
message: "Invalid value provided as a keyset for before: \"abc\"",
short_message: "invalid keyset",
path: ["getRelayTags"]
}
]
}
} = Absinthe.run(page, AshGraphql.Test.Schema, variables: %{"before" => "abc"})
end
end
end