ash/test/actions/pagination_test.exs

802 lines
23 KiB
Elixir
Raw Normal View History

2020-10-12 16:55:47 +13:00
defmodule Ash.Actions.PaginationTest do
use ExUnit.Case, async: true
require Ash.Query
2022-04-21 06:35:45 +12:00
defmodule Post do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Ets
ets do
private?(true)
end
attributes do
uuid_primary_key :id
attribute :user_id, :uuid
attribute :body, :string
2022-04-21 06:35:45 +12:00
end
actions do
defaults [:create, :update, :destroy]
read :read do
primary? true
2022-04-21 11:43:41 +12:00
pagination offset?: true, required?: true, default_limit: 25
2022-04-21 06:35:45 +12:00
end
end
relationships do
belongs_to :user, Ash.Actions.PaginationTest.User, define_attribute?: false
2022-04-21 06:35:45 +12:00
end
end
2020-10-12 16:55:47 +13:00
defmodule User do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Ets
ets do
private?(true)
end
actions do
read :offset do
2022-04-21 06:35:45 +12:00
pagination offset?: true, countable: true, required?: true
2020-10-12 16:55:47 +13:00
end
read :optional_offset do
pagination offset?: true, countable: true, required?: false
end
read :offset_countable_by_default do
pagination offset?: true, countable: :by_default, required?: false
end
read :required_offset_with_default do
pagination offset?: true, countable: true, required?: false, default_limit: 25
end
read :keyset do
pagination keyset?: true, countable: true
end
read :optional_keyset do
pagination keyset?: true, countable: true, required?: false
end
read :keyset_countable_by_default do
pagination keyset?: true, countable: :by_default, required?: false
end
read :required_keyset_with_default do
pagination keyset?: true, countable: true, required?: false, default_limit: 25
end
read :both_required do
primary? true
pagination keyset?: true, offset?: true, countable: true
end
read :both_optional do
pagination keyset?: true, offset?: true, countable: true, default_limit: 25
end
defaults [:create, :update]
2020-10-12 16:55:47 +13:00
end
attributes do
uuid_primary_key :id
2020-10-12 16:55:47 +13:00
attribute :name, :string
attribute :subname, :string
2020-10-12 16:55:47 +13:00
end
2022-04-21 06:35:45 +12:00
aggregates do
count :count_of_posts, :posts
end
calculations do
calculate :name_with_arg, :string, expr(name) do
argument :does_nothing, :boolean
end
end
2022-04-21 06:35:45 +12:00
relationships do
has_many :posts, Post
end
2020-10-12 16:55:47 +13:00
end
defmodule Registry do
@moduledoc false
use Ash.Registry
entries do
2022-04-21 06:35:45 +12:00
entry User
entry Post
end
end
2020-10-12 16:55:47 +13:00
defmodule Api do
use Ash.Api
resources do
registry Registry
2020-10-12 16:55:47 +13:00
end
end
test "pagination is required by default" do
feat: freeform expressions feat: validatiosn in actions feat: query arguments feat: add `Ash.Query.for_read/3` feat: return changeset with API errors feat: add case insensitive string `CiString`/`:ci_string` feat: support `context/1` and `arg/1` in filter templates feat: support targeting notifications with the `for` option feat: add `ago/2` query function feat: add basic arithmetic operators (+, *, -, /) feat: `sensitive?` option for attributes feat: `sensitive?` option for arguments feat: `private` arguments, which can’t be set using `for_<action>` feat: add `prevent_change` which will erase changes just before the changeset is committed feat: add `match?` validation that supports a custom error message feat: add `interval` type to support `ago/2` function feat: add `url_encoded_binary` type feat: add `function` type improvement: `changing?` is now a validation improvement: add `Transformer.get_persisted/3` improvement: add `api` field to `Notification` improvement: standardize errors, add `to_error_class` improvement: use `Comp` everywhere Improvement: use action on changeset if set by `for_<action_type>` improvement: `action_failed?` field on change sets improvement: remove ability for data layers to add operators (for now at least) Improvement: Changeset.apply_attributes/2 now returns an error tuple Improvement: add a bunch of new/informative errors improvement: runtime filter now uses left join logic (a naive implementation of it) improvement: support more filter templates in resources Improvement: basic/naive type system for operators/functions Fix: properly expand module aliases for options w/o compile time dependency chore(engine): track changeset changes for the request with `manage_changeset?: true`
2021-01-22 09:21:58 +13:00
assert_raise Ash.Error.Invalid, ~r/Pagination is required/, fn ->
2020-10-12 16:55:47 +13:00
Api.read!(User, page: false)
end
end
test "a default limit allows not specifying page parameters" do
feat: freeform expressions feat: validatiosn in actions feat: query arguments feat: add `Ash.Query.for_read/3` feat: return changeset with API errors feat: add case insensitive string `CiString`/`:ci_string` feat: support `context/1` and `arg/1` in filter templates feat: support targeting notifications with the `for` option feat: add `ago/2` query function feat: add basic arithmetic operators (+, *, -, /) feat: `sensitive?` option for attributes feat: `sensitive?` option for arguments feat: `private` arguments, which can’t be set using `for_<action>` feat: add `prevent_change` which will erase changes just before the changeset is committed feat: add `match?` validation that supports a custom error message feat: add `interval` type to support `ago/2` function feat: add `url_encoded_binary` type feat: add `function` type improvement: `changing?` is now a validation improvement: add `Transformer.get_persisted/3` improvement: add `api` field to `Notification` improvement: standardize errors, add `to_error_class` improvement: use `Comp` everywhere Improvement: use action on changeset if set by `for_<action_type>` improvement: `action_failed?` field on change sets improvement: remove ability for data layers to add operators (for now at least) Improvement: Changeset.apply_attributes/2 now returns an error tuple Improvement: add a bunch of new/informative errors improvement: runtime filter now uses left join logic (a naive implementation of it) improvement: support more filter templates in resources Improvement: basic/naive type system for operators/functions Fix: properly expand module aliases for options w/o compile time dependency chore(engine): track changeset changes for the request with `manage_changeset?: true`
2021-01-22 09:21:58 +13:00
assert_raise Ash.Error.Invalid, ~r/Limit is required/, fn ->
2020-10-21 06:11:21 +13:00
Api.read!(User, page: [offset: 1])
2020-10-12 16:55:47 +13:00
end
Api.read!(User, action: :required_offset_with_default)
end
describe "offset pagination" do
setup do
for i <- 0..9 do
user = Api.create!(Ash.Changeset.new(User, %{name: "#{i}"}))
if i != 0 do
for x <- 1..i do
Api.create!(Ash.Changeset.new(Post, %{body: "#{i}-#{x}", user_id: user.id}))
end
end
2020-10-12 16:55:47 +13:00
end
:ok
end
test "can be limited" do
assert Enum.count(Api.read!(User, action: :optional_offset, page: false)) == 10
assert Enum.count(Api.read!(User, action: :optional_offset, page: [limit: 5]).results) == 5
end
test "can be offset" do
assert Enum.count(Api.read!(User, action: :optional_offset, page: false)) == 10
2020-10-21 06:11:21 +13:00
assert Enum.count(
Api.read!(User, action: :optional_offset, page: [offset: 5, limit: 5]).results
) == 5
2020-10-12 16:55:47 +13:00
end
test "can include a full count" do
assert Api.read!(User, action: :optional_offset, page: [limit: 1, count: true]).count == 10
end
2022-06-01 03:56:15 +12:00
test "can include a full count with an offset" do
assert Api.read!(User, action: :optional_offset, page: [offset: 5, limit: 1, count: true]).count ==
10
end
2020-10-12 16:55:47 +13:00
test "can default to including a count" do
assert Api.read!(User, action: :offset_countable_by_default, page: [limit: 1]).count == 10
end
test "count is not included by default otherwise" do
assert is_nil(Api.read!(User, action: :optional_offset, page: [limit: 1]).count)
end
test "`count: false` prevents the count from occurring even if it is on `by_default`" do
assert is_nil(
Api.read!(User,
action: :offset_countable_by_default,
page: [limit: 1, count: false]
).count
)
end
test "pagination works with a sort applied" do
names =
User
|> Ash.Query.sort(:name)
2020-10-21 06:11:21 +13:00
|> Api.read!(page: [offset: 5, limit: 5])
2020-10-12 16:55:47 +13:00
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["5", "6", "7", "8", "9"]
end
test "pagination works with a reversed sort applied" do
names =
User
|> Ash.Query.sort(name: :desc)
2020-10-21 06:11:21 +13:00
|> Api.read!(page: [offset: 5, limit: 5])
2020-10-12 16:55:47 +13:00
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["4", "3", "2", "1", "0"]
end
test "pagination works with a filter" do
names =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
2020-10-21 06:11:21 +13:00
|> Api.read!(page: [offset: 1, limit: 5])
2020-10-12 16:55:47 +13:00
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["3", "2", "1", "0"]
end
test "the next page can be fetched" do
assert %{results: [%{name: "3"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(page: [offset: 1, limit: 1])
assert %{results: [%{name: "2"}]} = Api.page!(page, :next)
end
test "the previous page can be fetched" do
assert %{results: [%{name: "3"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(page: [offset: 1, limit: 1])
assert %{results: [%{name: "4"}]} = Api.page!(page, :prev)
end
test "the first page can be fetched" do
assert %{results: [%{name: "2"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(page: [offset: 2, limit: 1])
assert %{results: [%{name: "4"}]} = Api.page!(page, :first)
end
test "the last page can be fetched if the count was requested" do
assert %{results: [%{name: "3"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(page: [offset: 1, limit: 1, count: true])
assert %{results: [%{name: "0"}]} = Api.page!(page, :last)
end
end
describe "keyset pagination with nil fields" do
setup do
users =
for i <- 0..9 do
if rem(i, 2) == 0 do
Api.create!(Ash.Changeset.new(User, %{name: "#{i}", subname: "#{i}"}))
else
Api.create!(Ash.Changeset.new(User, %{name: "#{i}"}))
end
end
[users: users]
end
test "can be paged through when a non-nil value is the keyset" do
%{results: first_results} =
User
|> Ash.Query.sort([:subname, :name])
|> Api.read!(action: :keyset, page: [limit: 5])
assert Enum.map(first_results, & &1.name) == ~w(0 2 4 6 8)
keyset =
first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)
%{results: second_results} =
User
|> Ash.Query.sort([:subname, :name])
|> Api.read!(action: :keyset, page: [limit: 5, after: keyset])
assert Enum.map(second_results, & &1.name) == ~w(1 3 5 7 9)
end
test "can be paged through when a nil value is the keyset" do
%{results: first_results} =
User
|> Ash.Query.sort([:subname, :name])
|> Api.read!(action: :keyset, page: [limit: 6])
assert Enum.map(first_results, & &1.name) == ~w(0 2 4 6 8 1)
keyset =
first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)
%{results: second_results} =
User
|> Ash.Query.sort([:subname, :name])
|> Api.read!(action: :keyset, page: [limit: 6, after: keyset])
assert Enum.map(second_results, & &1.name) == ~w(3 5 7 9)
end
test "can be paged through when a non-nil value is the keyset using nils_first" do
%{results: first_results} =
User
|> Ash.Query.sort(subname: :asc_nils_first, name: :asc_nils_first)
|> Api.read!(action: :keyset, page: [limit: 5])
assert Enum.map(first_results, & &1.name) == ~w(1 3 5 7 9)
keyset =
first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)
%{results: second_results} =
User
|> Ash.Query.sort(subname: :asc_nils_first, name: :asc_nils_first)
|> Api.read!(action: :keyset, page: [limit: 5, after: keyset])
assert Enum.map(second_results, & &1.name) == ~w(0 2 4 6 8)
end
test "can be paged through when a nil value is the keyset using nils_first" do
%{results: first_results} =
User
|> Ash.Query.sort(subname: :asc_nils_first, name: :asc_nils_first)
|> Api.read!(action: :keyset, page: [limit: 6])
assert Enum.map(first_results, & &1.name) == ~w(1 3 5 7 9 0)
keyset =
first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)
%{results: second_results} =
User
|> Ash.Query.sort(subname: :asc_nils_first, name: :asc_nils_first)
|> Api.read!(action: :keyset, page: [limit: 5, after: keyset])
assert Enum.map(second_results, & &1.name) == ~w(2 4 6 8)
end
end
2020-10-12 16:55:47 +13:00
describe "keyset pagination" do
setup do
users =
for i <- 0..9 do
user = Api.create!(Ash.Changeset.new(User, %{name: "#{i}"}))
if i != 0 do
for x <- 1..i do
Api.create!(Ash.Changeset.new(Post, %{body: "#{i}-#{x}", user_id: user.id}))
end
end
end
2020-10-12 16:55:47 +13:00
[users: users]
2020-10-12 16:55:47 +13:00
end
test "can be limited" do
assert Enum.count(Api.read!(User, action: :optional_keyset, page: false)) == 10
assert Enum.count(Api.read!(User, action: :optional_keyset, page: [limit: 5]).results) == 5
end
test "can include a full count" do
assert Api.read!(User, action: :optional_keyset, page: [limit: 1, count: true]).count == 10
end
2022-06-01 03:56:15 +12:00
test "can include a full count with a sort and limit" do
assert 10 =
User
|> Ash.Query.sort(:name)
|> Api.read!(action: :optional_keyset, page: [limit: 1, count: true])
|> Map.get(:count)
end
2020-10-12 16:55:47 +13:00
test "can default to including a count" do
assert Api.read!(User, action: :keyset_countable_by_default, page: [limit: 1]).count == 10
end
test "count is not included by default otherwise" do
assert is_nil(Api.read!(User, action: :optional_keyset, page: [limit: 1]).count)
end
test "`count: false` prevents the count from occurring even if it is on `by_default`" do
assert is_nil(
Api.read!(User,
action: :keyset_countable_by_default,
page: [limit: 1, count: false]
).count
)
end
test "can ask for records after a specific keyset" do
%{results: [%{id: id, __metadata__: %{keyset: keyset}}]} =
2020-10-12 16:55:47 +13:00
Api.read!(User, action: :keyset, page: [limit: 1])
%{results: [%{id: next_id}]} =
Api.read!(User, action: :keyset, page: [limit: 1, after: keyset])
refute id == next_id
end
2022-06-01 03:56:15 +12:00
test "can get the full count when asking for records after a specific keyset" do
%{results: [%{__metadata__: %{keyset: keyset}}], count: 10} =
Api.read!(User, action: :keyset, page: [count: true, limit: 1])
assert %{count: 10} =
Api.read!(User, action: :keyset, page: [count: true, limit: 1, after: keyset])
end
test "an invalid keyset returns an appropriate error" do
assert_raise(Ash.Error.Invalid, ~r/Invalid value provided as a keyset/, fn ->
Api.read!(User, action: :keyset, page: [limit: 1, after: "~"])
end)
end
2020-10-12 16:55:47 +13:00
test "can ask for records before a specific keyset" do
%{results: [%{id: id, __metadata__: %{keyset: keyset}}]} =
2020-10-12 16:55:47 +13:00
Api.read!(User, action: :keyset, page: [limit: 1])
%{results: [%{id: next_id, __metadata__: %{keyset: keyset2}}]} =
2020-10-12 16:55:47 +13:00
Api.read!(User, action: :keyset, page: [limit: 1, after: keyset])
refute id == next_id
%{results: [%{id: before_id}]} =
Api.read!(User, action: :keyset, page: [limit: 1, before: keyset2])
assert id == before_id
end
test "can ask for records before a specific keyset, with the sort order honored" do
%{results: users} =
User |> Ash.Query.sort(:name) |> Api.read!(action: :keyset, page: [limit: 100])
users = Enum.sort_by(users, & &1.name)
last_user = List.last(users)
%{results: results} =
User
|> Ash.Query.sort(:name)
|> Api.read!(action: :keyset, page: [limit: 2, before: last_user.__metadata__.keyset])
assert Enum.map(results, & &1.name) == [
"7",
"8"
]
end
2022-06-01 03:56:15 +12:00
test "can ask for records before a specific keyset, with the full count shown" do
%{results: users} =
User |> Ash.Query.sort(:name) |> Api.read!(action: :keyset, page: [limit: 100])
users = Enum.sort_by(users, & &1.name)
last_user = List.last(users)
assert Enum.count(users) == 10
assert %{count: 10} =
User
|> Ash.Query.sort(:name)
|> Api.read!(
action: :keyset,
page: [count: true, limit: 2, before: last_user.__metadata__.keyset]
)
end
2020-10-12 16:55:47 +13:00
test "pagination works with a sort applied" do
page =
User
|> Ash.Query.filter(name == "4")
|> Ash.Query.sort(:name)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
2020-10-12 16:55:47 +13:00
names =
User
|> Ash.Query.sort(:name)
|> Api.read!(page: [after: keyset, limit: 5])
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["5", "6", "7", "8", "9"]
end
test "pagination works with a :desc sort applied" do
page =
User
|> Ash.Query.filter(name == "4")
|> Ash.Query.sort(name: :desc)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
names =
User
|> Ash.Query.sort(:name)
|> Api.read!(page: [after: keyset, limit: 5])
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["5", "6", "7", "8", "9"]
end
test "pagination works with a sort applied that uses an aggregate" do
page =
User
|> Ash.Query.filter(count_of_posts == 4)
|> Ash.Query.sort(:name)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
page =
User
|> Ash.Query.sort(:count_of_posts)
|> Api.read!(page: [after: keyset, limit: 4])
names =
page
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["5", "6", "7", "8"]
assert page.more?
end
test "pagination more? is false when there are no more records" do
page =
User
|> Ash.Query.filter(count_of_posts == 5)
|> Ash.Query.sort(:name)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
page =
User
|> Ash.Query.sort(:count_of_posts)
|> Api.read!(page: [after: keyset, limit: 4])
names =
page
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["6", "7", "8", "9"]
refute page.more?
end
test "pagination works with a sort applied that uses an aggregate using `before`" do
page =
User
|> Ash.Query.filter(count_of_posts == 4)
|> Ash.Query.sort(:name)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
page =
User
|> Ash.Query.sort(:count_of_posts)
|> Api.read!(page: [before: keyset, limit: 3])
names =
page
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["1", "2", "3"]
assert page.more?
page =
User
|> Ash.Query.sort(:count_of_posts)
|> Api.read!(page: [after: keyset, limit: 4])
names =
page
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["5", "6", "7", "8"]
assert page.more?
end
test "pagination more? is false when there are no more records using `before`" do
2022-09-13 05:38:00 +12:00
page =
User
|> Ash.Query.filter(count_of_posts == 4)
|> Ash.Query.sort(:name)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
page =
User
|> Ash.Query.sort(:count_of_posts)
|> Api.read!(page: [before: keyset, limit: 4])
names =
page
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["0", "1", "2", "3"]
refute page.more?
end
test "pagination works with a sort applied that uses an aggregate desc" do
User
|> Ash.Query.load(:count_of_posts)
2023-02-10 15:21:58 +13:00
|> Api.read!(page: [limit: 10])
|> Map.get(:results)
|> Enum.map(&{&1.name, &1.count_of_posts})
page =
User
|> Ash.Query.filter(count_of_posts == 4)
|> Ash.Query.sort(:name)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
names =
User
|> Ash.Query.sort(count_of_posts: :desc)
|> Api.read!(page: [after: keyset, limit: 5])
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["3", "2", "1", "0"]
end
test "pagination works with a sort applied that uses a calculation with arguments" do
page =
User
|> Ash.Query.filter(name_with_arg == "4")
|> Ash.Query.sort(name_with_arg: %{does_nothing: true})
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
names =
User
|> Ash.Query.sort(name_with_arg: %{does_nothing: true})
|> Api.read!(page: [after: keyset, limit: 5])
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["5", "6", "7", "8", "9"]
end
test "pagination works with a sort applied that uses a calculation desc" do
page =
User
|> Ash.Query.filter(name_with_arg == "4")
|> Ash.Query.sort(name_with_arg: {:desc, %{does_nothing: true}})
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
names =
User
|> Ash.Query.sort(name_with_arg: {:desc, %{does_nothing: true}})
|> Api.read!(page: [after: keyset, limit: 5])
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["3", "2", "1", "0"]
end
2020-10-12 16:55:47 +13:00
test "pagination works with a reversed sort applied" do
page =
User
|> Ash.Query.filter(name == "5")
|> Ash.Query.sort(name: :desc)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
2020-10-12 16:55:47 +13:00
names =
User
|> Ash.Query.sort(name: :desc)
|> Api.read!(page: [after: keyset, limit: 5])
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["4", "3", "2", "1", "0"]
end
test "pagination works with a filter" do
page =
User
|> Ash.Query.filter(name == "5")
|> Ash.Query.sort(name: :desc)
|> Api.read!(page: [limit: 1])
keyset = Enum.at(page.results, 0).__metadata__.keyset
2020-10-12 16:55:47 +13:00
names =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name != "4")
|> Api.read!(page: [after: keyset, limit: 5])
|> Map.get(:results)
|> Enum.map(& &1.name)
assert names == ["3", "2", "1", "0"]
end
test "the next page can be fetched" do
assert %{results: [%{name: "4"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(page: [limit: 1])
assert %{results: [%{name: "3"}]} = Api.page!(page, :next)
end
test "the previous page can be fetched" do
assert %{results: [%{name: "4"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(page: [limit: 1], action: :optional_keyset)
2020-10-12 16:55:47 +13:00
assert %{results: [%{name: "3"}]} = page = Api.page!(page, :next)
assert %{results: [%{name: "4"}]} = Api.page!(page, :prev)
end
test "the first page can be fetched" do
assert %{results: [%{name: "4"}]} =
page =
User
|> Ash.Query.sort(name: :desc)
|> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
|> Api.read!(page: [limit: 1])
assert %{results: [%{name: "3"}]} = page = Api.page!(page, :next)
assert %{results: [%{name: "4"}]} = Api.page!(page, :first)
end
end
describe "when both are supported" do
setup do
for i <- 0..9 do
Api.create!(Ash.Changeset.new(User, %{name: "#{i}"}))
end
:ok
end
test "it defaults to offset pagination" do
assert %Ash.Page.Offset{} = Api.read!(User, action: :both_optional, page: [limit: 10])
end
test "it adds a keyset to the records, even though it returns an offset page" do
for result <- Api.read!(User, action: :both_optional, page: [limit: 10]).results do
refute is_nil(result.__metadata__.keyset)
2020-10-12 16:55:47 +13:00
end
end
end
2022-04-21 06:35:45 +12:00
describe "loading with pagination" do
test "it does not paginate loads" do
user = Api.create!(Ash.Changeset.new(User, %{name: "user"}))
Api.create!(Ash.Changeset.new(Post, %{user_id: user.id}))
assert [_ | _] =
user
|> Api.load!([posts: :user], tenant: nil, actor: nil)
|> Map.get(:posts)
end
end
2020-10-12 16:55:47 +13:00
end