package pagination import ( "errors" "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]{ { name: "nil items", args: args[string]{max: 10}, wantItems: nil, wantPageInfo: PageInfo{}, }, { name: "empty items", args: args[string]{items: []string{}, max: 10}, wantItems: nil, wantPageInfo: PageInfo{}, }, { 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=="), TotalCount: 3, }, }, { 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{}, }, { 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=="), TotalCount: 3, }, }, { 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=="), TotalCount: 4, }, }, { 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=="), TotalCount: 3, }, }, { 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=="), TotalCount: 3, }, }, { 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=="), TotalCount: 3, }, }, { 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=="), TotalCount: 3, }, }, { 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=="), TotalCount: 3, }, }, } 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) }) } } 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) }) } }