import files, add README & LICENSE
This commit is contained in:
+260
@@ -0,0 +1,260 @@
|
||||
package cursor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.auvem.com/go-toolkit/dbx"
|
||||
"github.com/go-jet/jet/v2/mysql"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConnectionFromRelayArgs(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
RegisterColumn(User.ID)
|
||||
|
||||
newZero := func() *Cursor[mysql.IntegerExpression, mysql.ColumnInteger] {
|
||||
return NewCursor(NewInt64Value(0, User.ID), User.ID, OrderDescending)
|
||||
}
|
||||
|
||||
var gotCursor *Cursor[mysql.IntegerExpression, mysql.ColumnInteger]
|
||||
var gotLimit int
|
||||
|
||||
list := func(c *Cursor[mysql.IntegerExpression, mysql.ColumnInteger], limit int) (*Connection[int], error) {
|
||||
gotCursor = c
|
||||
gotLimit = limit
|
||||
return &Connection[int]{}, nil
|
||||
}
|
||||
|
||||
conn, err := ConnectionFromRelayArgs(nil, nil, newZero, list)
|
||||
require.NoError(err)
|
||||
require.NotNil(conn)
|
||||
assert.Nil(gotCursor)
|
||||
assert.Equal(0, gotLimit)
|
||||
|
||||
first := 25
|
||||
conn, err = ConnectionFromRelayArgs(nil, &first, newZero, list)
|
||||
require.NoError(err)
|
||||
require.NotNil(conn)
|
||||
assert.Nil(gotCursor)
|
||||
assert.Equal(25, gotLimit)
|
||||
|
||||
encoded, err := NewCursor(
|
||||
NewInt64Value(7, User.ID),
|
||||
User.ID,
|
||||
OrderDescending,
|
||||
).Encode()
|
||||
require.NoError(err)
|
||||
|
||||
conn, err = ConnectionFromRelayArgs(&encoded, &first, newZero, list)
|
||||
require.NoError(err)
|
||||
require.NotNil(conn)
|
||||
require.NotNil(gotCursor)
|
||||
assert.Equal(int64(7), gotCursor.Index.(*Int64Value).Val)
|
||||
assert.Equal(25, gotLimit)
|
||||
|
||||
bad := "{not-json"
|
||||
_, err = ConnectionFromRelayArgs(&bad, nil, newZero, list)
|
||||
require.Error(err)
|
||||
|
||||
listErr := errors.New("list failed")
|
||||
_, err = ConnectionFromRelayArgs(nil, nil, newZero, func(*Cursor[mysql.IntegerExpression, mysql.ColumnInteger], int) (*Connection[int], error) {
|
||||
return nil, listErr
|
||||
})
|
||||
require.ErrorIs(err, listErr)
|
||||
}
|
||||
|
||||
func TestPageQueryRunSQLComposite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
registerMeetingCursorColumns(t)
|
||||
|
||||
when := time.Date(2026, 6, 1, 9, 0, 0, 0, time.UTC)
|
||||
active := NewCursor(
|
||||
NewStringValue("", Meeting.ID),
|
||||
Meeting.StartTime,
|
||||
OrderDescending,
|
||||
).CopyWithVals(
|
||||
NewStringValue("meeting-uuid-1", Meeting.ID),
|
||||
NewTimestampValue(when, Meeting.StartTime),
|
||||
)
|
||||
|
||||
stmt := mysql.SELECT(Meeting.AllColumns).FROM(Meeting)
|
||||
stmt.WHERE(mysql.Bool(true).AND(PaginateConds(active)))
|
||||
stmt.ORDER_BY(OrderByClauses(active)...)
|
||||
sql := stmt.DebugSql()
|
||||
|
||||
assert.Contains(sql, "(meeting.start_time, meeting.id) < ('")
|
||||
assert.True(
|
||||
strings.Contains(sql, "start_time DESC") && strings.Contains(sql, "id DESC"),
|
||||
"expected composite order by, got: %s", sql,
|
||||
)
|
||||
}
|
||||
|
||||
func TestPageQueryRunSQLSimple(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
RegisterColumn(MeetingRoom.ID)
|
||||
|
||||
active := NewCursor(
|
||||
NewStringValue("room-1", MeetingRoom.ID),
|
||||
MeetingRoom.ID,
|
||||
OrderDescending,
|
||||
)
|
||||
|
||||
stmt := mysql.SELECT(MeetingRoom.AllColumns).FROM(MeetingRoom)
|
||||
stmt.WHERE(mysql.Bool(true).AND(PaginateStringConds(active)))
|
||||
stmt.ORDER_BY(OrderByClauses(active)...)
|
||||
sql := stmt.DebugSql()
|
||||
|
||||
assert.Contains(sql, "meeting_room.id < 'room-1'")
|
||||
assert.Contains(sql, "id DESC")
|
||||
}
|
||||
|
||||
func TestPageQueryRunSQLFirstPageNilCursor(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
registerMeetingCursorColumns(t)
|
||||
|
||||
defaultCursor := NewCursor(
|
||||
NewStringValue("", Meeting.ID),
|
||||
Meeting.StartTime,
|
||||
OrderDescending,
|
||||
)
|
||||
|
||||
var nilCursor *Cursor[mysql.StringExpression, mysql.ColumnString]
|
||||
stmt := mysql.SELECT(Meeting.AllColumns).FROM(Meeting)
|
||||
stmt.WHERE(mysql.Bool(true).AND(PaginateConds(nilCursor)))
|
||||
stmt.ORDER_BY(OrderByClauses(defaultCursor)...)
|
||||
sql := stmt.DebugSql()
|
||||
|
||||
assert.NotContains(sql, "meeting.id < ''")
|
||||
assert.Contains(sql, "start_time DESC")
|
||||
assert.Contains(sql, "id DESC")
|
||||
}
|
||||
|
||||
func TestPageQueryRunSQLFirstPageDefaultCursor(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
registerMeetingCursorColumns(t)
|
||||
|
||||
defaultCursor := NewCursor(
|
||||
NewStringValue("", Meeting.ID),
|
||||
Meeting.StartTime,
|
||||
OrderDescending,
|
||||
)
|
||||
|
||||
stmt := mysql.SELECT(Meeting.AllColumns).FROM(Meeting)
|
||||
stmt.WHERE(mysql.Bool(true).AND(PaginateConds(defaultCursor)))
|
||||
stmt.ORDER_BY(OrderByClauses(defaultCursor)...)
|
||||
sql := stmt.DebugSql()
|
||||
|
||||
assert.NotContains(sql, "meeting.id < ''")
|
||||
assert.Contains(sql, "start_time DESC")
|
||||
assert.Contains(sql, "id DESC")
|
||||
}
|
||||
|
||||
func TestPageQueryRunSQLFirstPageRoomDefaultCursor(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
RegisterColumn(MeetingRoom.ID)
|
||||
|
||||
defaultCursor := NewCursor(
|
||||
NewStringValue("", MeetingRoom.ID),
|
||||
MeetingRoom.ID,
|
||||
OrderDescending,
|
||||
)
|
||||
|
||||
stmt := mysql.SELECT(MeetingRoom.AllColumns).FROM(MeetingRoom)
|
||||
stmt.WHERE(mysql.Bool(true).AND(PaginateStringConds(defaultCursor)))
|
||||
stmt.ORDER_BY(OrderByClauses(defaultCursor)...)
|
||||
sql := stmt.DebugSql()
|
||||
|
||||
assert.NotContains(sql, "meeting_room.id < ''")
|
||||
assert.Contains(sql, "id DESC")
|
||||
}
|
||||
|
||||
func TestPageQueryRun(t *testing.T) {
|
||||
RegisterColumn(User.ID)
|
||||
|
||||
type row struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
defaultCursor := func() *Cursor[mysql.IntegerExpression, mysql.ColumnInteger] {
|
||||
return NewCursor(NewInt64Value(0, User.ID), User.ID, OrderDescending)
|
||||
}
|
||||
|
||||
items := []*row{{ID: 1}, {ID: 2}}
|
||||
scanErr := errors.New("scan failed")
|
||||
afterScanErr := errors.New("after scan failed")
|
||||
|
||||
t.Run("success with default cursor and after scan", func(t *testing.T) {
|
||||
var scannedStmt mysql.SelectStatement
|
||||
q := PageQuery[row, mysql.IntegerExpression, mysql.ColumnInteger]{
|
||||
Stmt: User.SELECT(User.AllColumns).FROM(User),
|
||||
Conds: mysql.Bool(true),
|
||||
Default: defaultCursor,
|
||||
Limit: 10,
|
||||
CountFn: func(_ dbx.Queryable, _, _ GenericCursor) (QueryCountResult, error) {
|
||||
return QueryCountResult{Total: 2, After: 1, Before: 0}, nil
|
||||
},
|
||||
Scan: func(stmt mysql.SelectStatement, dest *[]*row) error {
|
||||
scannedStmt = stmt
|
||||
*dest = items
|
||||
return nil
|
||||
},
|
||||
AfterScan: func(rows []*row) ([]*row, error) {
|
||||
return rows[:1], nil
|
||||
},
|
||||
ToEdge: func(_ *Cursor[mysql.IntegerExpression, mysql.ColumnInteger], item *row) (GenericCursor, error) {
|
||||
return NewCursor(NewInt64Value(item.ID, User.ID), User.ID, OrderDescending), nil
|
||||
},
|
||||
}
|
||||
|
||||
conn, err := q.Run()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, conn)
|
||||
assert.Len(t, conn.Edges, 1)
|
||||
assert.True(t, conn.PageInfo.HasNextPage)
|
||||
assert.False(t, conn.PageInfo.HasPreviousPage)
|
||||
assert.Equal(t, 2, conn.TotalCount)
|
||||
assert.Contains(t, scannedStmt.DebugSql(), "LIMIT 10")
|
||||
})
|
||||
|
||||
t.Run("scan error", func(t *testing.T) {
|
||||
q := PageQuery[row, mysql.IntegerExpression, mysql.ColumnInteger]{
|
||||
Stmt: User.SELECT(User.AllColumns).FROM(User),
|
||||
Conds: mysql.Bool(true),
|
||||
Default: defaultCursor,
|
||||
Scan: func(_ mysql.SelectStatement, _ *[]*row) error {
|
||||
return scanErr
|
||||
},
|
||||
ToEdge: func(_ *Cursor[mysql.IntegerExpression, mysql.ColumnInteger], item *row) (GenericCursor, error) {
|
||||
return NewCursor(NewInt64Value(item.ID, User.ID), User.ID, OrderDescending), nil
|
||||
},
|
||||
}
|
||||
_, err := q.Run()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to run paginated query")
|
||||
})
|
||||
|
||||
t.Run("after scan error", func(t *testing.T) {
|
||||
q := PageQuery[row, mysql.IntegerExpression, mysql.ColumnInteger]{
|
||||
Stmt: User.SELECT(User.AllColumns).FROM(User),
|
||||
Conds: mysql.Bool(true),
|
||||
Default: defaultCursor,
|
||||
Scan: func(_ mysql.SelectStatement, dest *[]*row) error {
|
||||
*dest = items
|
||||
return nil
|
||||
},
|
||||
AfterScan: func(_ []*row) ([]*row, error) {
|
||||
return nil, afterScanErr
|
||||
},
|
||||
ToEdge: func(_ *Cursor[mysql.IntegerExpression, mysql.ColumnInteger], item *row) (GenericCursor, error) {
|
||||
return NewCursor(NewInt64Value(item.ID, User.ID), User.ID, OrderDescending), nil
|
||||
},
|
||||
}
|
||||
_, err := q.Run()
|
||||
require.ErrorIs(t, err, afterScanErr)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user