import files, add README & LICENSE

This commit is contained in:
2026-06-29 17:06:34 -07:00
commit aecd90eb84
17 changed files with 2552 additions and 0 deletions
+260
View File
@@ -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)
})
}