2025-06-07 19:23:44 +02:00
|
|
|
package pagination
|
|
|
|
|
|
|
|
|
|
import (
|
2026-01-01 15:27:53 +01:00
|
|
|
"errors"
|
2025-06-07 19:23:44 +02:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestGetPage(t *testing.T) {
|
|
|
|
|
type args[T any] struct {
|
|
|
|
|
items []T
|
|
|
|
|
first *int
|
|
|
|
|
after *string
|
|
|
|
|
last *int
|
|
|
|
|
before *string
|
|
|
|
|
max int
|
|
|
|
|
fn func(T) string
|
|
|
|
|
}
|
|
|
|
|
type testCase[T any] struct {
|
|
|
|
|
name string
|
|
|
|
|
args args[T]
|
|
|
|
|
wantItems []T
|
|
|
|
|
wantPageInfo PageInfo
|
|
|
|
|
}
|
|
|
|
|
tests := []testCase[string]{
|
|
|
|
|
{
|
2025-06-16 09:09:04 +02:00
|
|
|
name: "nil items",
|
2025-06-07 19:23:44 +02:00
|
|
|
args: args[string]{max: 10},
|
|
|
|
|
wantItems: nil,
|
|
|
|
|
wantPageInfo: PageInfo{},
|
|
|
|
|
},
|
2025-06-16 09:09:04 +02:00
|
|
|
{
|
|
|
|
|
name: "empty items",
|
|
|
|
|
args: args[string]{items: []string{}, max: 10},
|
|
|
|
|
wantItems: nil,
|
|
|
|
|
wantPageInfo: PageInfo{},
|
|
|
|
|
},
|
2025-06-07 19:23:44 +02:00
|
|
|
{
|
|
|
|
|
name: "first 2",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3"}, first: ptr(2), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: []string{"1", "2"},
|
|
|
|
|
wantPageInfo: PageInfo{
|
|
|
|
|
StartCursor: ptr("MQ=="),
|
|
|
|
|
HasNextPage: true,
|
|
|
|
|
EndCursor: ptr("Mg=="),
|
2026-01-01 15:37:19 +01:00
|
|
|
TotalCount: 3,
|
2025-06-07 19:23:44 +02:00
|
|
|
},
|
|
|
|
|
},
|
2025-06-16 12:04:52 +02:00
|
|
|
{
|
|
|
|
|
name: "no more items",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3"}, first: ptr(2), after: ptr("Mw=="), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: nil,
|
|
|
|
|
wantPageInfo: PageInfo{},
|
|
|
|
|
},
|
2025-06-15 19:56:47 +02:00
|
|
|
{
|
|
|
|
|
name: "first 10",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3"}, first: ptr(10), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: []string{"1", "2", "3"},
|
|
|
|
|
wantPageInfo: PageInfo{
|
|
|
|
|
StartCursor: ptr("MQ=="),
|
|
|
|
|
HasNextPage: false,
|
|
|
|
|
EndCursor: ptr("Mw=="),
|
2026-01-01 15:37:19 +01:00
|
|
|
TotalCount: 3,
|
2025-06-15 19:56:47 +02:00
|
|
|
},
|
|
|
|
|
},
|
2025-06-07 19:23:44 +02:00
|
|
|
{
|
|
|
|
|
name: "after 2",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3", "4"}, first: ptr(2), after: ptr("Mg=="), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: []string{"3", "4"},
|
|
|
|
|
wantPageInfo: PageInfo{
|
|
|
|
|
StartCursor: ptr("Mw=="),
|
|
|
|
|
HasNextPage: false,
|
|
|
|
|
HasPreviousPage: true,
|
|
|
|
|
EndCursor: ptr("NA=="),
|
2026-01-01 15:37:19 +01:00
|
|
|
TotalCount: 4,
|
2025-06-07 19:23:44 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "end of items",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3"}, first: ptr(2), after: ptr("Mg=="), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: []string{"3"},
|
|
|
|
|
wantPageInfo: PageInfo{
|
|
|
|
|
StartCursor: ptr("Mw=="),
|
|
|
|
|
HasNextPage: false,
|
|
|
|
|
HasPreviousPage: true,
|
|
|
|
|
EndCursor: ptr("Mw=="),
|
2026-01-01 15:37:19 +01:00
|
|
|
TotalCount: 3,
|
2025-06-07 19:23:44 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "last 2",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3"}, last: ptr(2), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: []string{"2", "3"},
|
|
|
|
|
wantPageInfo: PageInfo{
|
|
|
|
|
StartCursor: ptr("Mg=="),
|
|
|
|
|
HasNextPage: false,
|
|
|
|
|
HasPreviousPage: true,
|
|
|
|
|
EndCursor: ptr("Mw=="),
|
2026-01-01 15:37:19 +01:00
|
|
|
TotalCount: 3,
|
2025-06-07 19:23:44 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "before 3",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3"}, last: ptr(2), before: ptr("Mw=="), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: []string{"1", "2"},
|
|
|
|
|
wantPageInfo: PageInfo{
|
|
|
|
|
StartCursor: ptr("MQ=="),
|
|
|
|
|
HasNextPage: true,
|
|
|
|
|
HasPreviousPage: false,
|
|
|
|
|
EndCursor: ptr("Mg=="),
|
2026-01-01 15:37:19 +01:00
|
|
|
TotalCount: 3,
|
2025-06-07 19:23:44 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "before 2",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3"}, last: ptr(2), before: ptr("Mg=="), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: []string{"1"},
|
|
|
|
|
wantPageInfo: PageInfo{
|
|
|
|
|
StartCursor: ptr("MQ=="),
|
|
|
|
|
HasNextPage: true,
|
|
|
|
|
HasPreviousPage: false,
|
|
|
|
|
EndCursor: ptr("MQ=="),
|
2026-01-01 15:37:19 +01:00
|
|
|
TotalCount: 3,
|
2025-06-07 19:23:44 +02:00
|
|
|
},
|
|
|
|
|
},
|
2026-01-01 15:09:00 +01:00
|
|
|
{
|
|
|
|
|
name: "last exceeds items count",
|
|
|
|
|
args: args[string]{items: []string{"1", "2", "3"}, last: ptr(10), max: 10, fn: func(s string) string { return s }},
|
|
|
|
|
wantItems: []string{"1", "2", "3"},
|
|
|
|
|
wantPageInfo: PageInfo{
|
|
|
|
|
StartCursor: ptr("MQ=="),
|
|
|
|
|
HasNextPage: false,
|
|
|
|
|
HasPreviousPage: false,
|
|
|
|
|
EndCursor: ptr("Mw=="),
|
2026-01-01 15:37:19 +01:00
|
|
|
TotalCount: 3,
|
2026-01-01 15:09:00 +01:00
|
|
|
},
|
|
|
|
|
},
|
2025-06-07 19:23:44 +02:00
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got, got1 := GetPage(tt.args.items, tt.args.first, tt.args.after, tt.args.last, tt.args.before, tt.args.max, tt.args.fn)
|
|
|
|
|
assert.Equalf(t, tt.wantItems, got, "GetPage(%v, %v, %v, %v, %v, %v)", tt.args.items, tt.args.first, tt.args.after, tt.args.last, tt.args.before, tt.args.max)
|
|
|
|
|
assert.Equalf(t, tt.wantPageInfo, got1, "GetPage(%v, %v, %v, %v, %v, %v)", tt.args.items, tt.args.first, tt.args.after, tt.args.last, tt.args.before, tt.args.max)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-01 15:27:53 +01:00
|
|
|
|
|
|
|
|
func TestValidate(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
first *int
|
|
|
|
|
after *string
|
|
|
|
|
last *int
|
|
|
|
|
before *string
|
|
|
|
|
wantErr error
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "valid first",
|
|
|
|
|
first: ptr(10),
|
|
|
|
|
wantErr: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid last",
|
|
|
|
|
last: ptr(10),
|
|
|
|
|
wantErr: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "first and last both provided",
|
|
|
|
|
first: ptr(10),
|
|
|
|
|
last: ptr(10),
|
|
|
|
|
wantErr: ErrFirstAndLastProvided,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "negative first",
|
|
|
|
|
first: ptr(-1),
|
|
|
|
|
wantErr: ErrFirstNegative,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "negative last",
|
|
|
|
|
last: ptr(-1),
|
|
|
|
|
wantErr: ErrLastNegative,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "after and before both provided",
|
|
|
|
|
after: ptr("MQ=="),
|
|
|
|
|
before: ptr("Mg=="),
|
|
|
|
|
wantErr: ErrAfterAndBeforeProvided,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid after cursor",
|
|
|
|
|
after: ptr("not-valid-base64!@#"),
|
|
|
|
|
wantErr: ErrInvalidAfterCursor,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid before cursor",
|
|
|
|
|
before: ptr("not-valid-base64!@#"),
|
|
|
|
|
wantErr: ErrInvalidBeforeCursor,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
err := Validate(tt.first, tt.after, tt.last, tt.before)
|
|
|
|
|
if tt.wantErr != nil {
|
|
|
|
|
assert.True(t, errors.Is(err, tt.wantErr), "Validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
|
} else {
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDecodeCursor(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
cursor *string
|
|
|
|
|
want string
|
|
|
|
|
wantErr error
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "nil cursor",
|
|
|
|
|
cursor: nil,
|
|
|
|
|
want: "",
|
|
|
|
|
wantErr: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid cursor",
|
|
|
|
|
cursor: ptr("dGVzdA=="),
|
|
|
|
|
want: "test",
|
|
|
|
|
wantErr: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid base64",
|
|
|
|
|
cursor: ptr("not-valid-base64!@#"),
|
|
|
|
|
want: "",
|
|
|
|
|
wantErr: ErrInvalidCursor,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got, err := DecodeCursor(tt.cursor)
|
|
|
|
|
if tt.wantErr != nil {
|
|
|
|
|
assert.True(t, errors.Is(err, tt.wantErr), "DecodeCursor() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
|
} else {
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, tt.want, got)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestValidateCursor(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
cursor *string
|
|
|
|
|
wantErr error
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "nil cursor",
|
|
|
|
|
cursor: nil,
|
|
|
|
|
wantErr: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid cursor",
|
|
|
|
|
cursor: ptr("dGVzdA=="),
|
|
|
|
|
wantErr: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid cursor",
|
|
|
|
|
cursor: ptr("not-valid-base64!@#"),
|
|
|
|
|
wantErr: ErrInvalidCursor,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
err := ValidateCursor(tt.cursor)
|
|
|
|
|
if tt.wantErr != nil {
|
|
|
|
|
assert.True(t, errors.Is(err, tt.wantErr), "ValidateCursor() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
|
} else {
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestEncodeCursor(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
cursor string
|
|
|
|
|
want string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "simple string",
|
|
|
|
|
cursor: "test",
|
|
|
|
|
want: "dGVzdA==",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "empty string",
|
|
|
|
|
cursor: "",
|
|
|
|
|
want: "",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got := EncodeCursor(tt.cursor)
|
|
|
|
|
assert.Equal(t, tt.want, got)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|