ash/test/actions/pagination_test.exs

403 lines
12 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
defmodule User do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Ets
ets do
private?(true)
end
actions do
read :offset do
pagination offset?: true, countable: true
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
create :default
update :default
end
attributes do
attribute :id, :uuid, primary_key?: true, default: &Ecto.UUID.generate/0
attribute :name, :string
end
end
defmodule Api do
use Ash.Api
resources do
resource User
end
end
test "pagination is required by default" do
assert_raise Ash.Error.Invalid.PaginationRequired, fn ->
Api.read!(User, page: false)
end
end
test "a default limit allows not specifying page parameters" do
assert_raise Ash.Error.Invalid.LimitRequired, fn ->
Api.read!(User)
end
Api.read!(User, action: :required_offset_with_default)
end
describe "offset pagination" do
setup do
for i <- 0..9 do
Api.create!(Ash.Changeset.new(User, %{name: "#{i}"}))
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
assert Enum.count(Api.read!(User, action: :optional_offset, page: [offset: 5]).results) == 5
end
test "can include a full count" do
assert Api.read!(User, action: :optional_offset, page: [limit: 1, count: true]).count == 10
end
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)
|> Api.read!(page: [offset: 5])
|> 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)
|> Api.read!(page: [offset: 5])
|> 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"])
|> Api.read!(page: [offset: 1])
|> 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 not 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])
assert %{results: [%{name: "0"}]} = Api.page!(page, :last)
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" do
setup do
for i <- 0..9 do
Api.create!(Ash.Changeset.new(User, %{name: "#{i}"}))
end
:ok
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
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}}]} =
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
test "can ask for records before a specific keyset" do
%{results: [%{id: id, metadata: %{keyset: keyset}}]} =
Api.read!(User, action: :keyset, page: [limit: 1])
%{results: [%{id: next_id, metadata: %{keyset: keyset2}}]} =
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 "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
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 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
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
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])
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
test "the last 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: "0"}]} = Api.page!(page, :last)
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)
end
end
end
end