Files
cursor/query_count_test.go
T
end 683b0ddbf4 docs: add package godoc and clarify public API
Prepare the extracted library for external consumption with grouped
godoc, extension point docs, and setup requirements. Wire
ErrBadCursorString into decode paths and drop redundant Paginate*Conds
aliases.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 17:14:05 -07:00

179 lines
5.1 KiB
Go

package cursor
import (
"context"
"database/sql"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/go-jet/jet/v2/mysql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValueMethods(t *testing.T) {
RegisterColumn(Scratch.ID)
intVal, err := NewInt64Value(7, Scratch.ID).Value()
require.NoError(t, err)
assert.Equal(t, int64(7), intVal)
uintVal, err := NewUint64Value(9, Spare.ID).Value()
require.NoError(t, err)
assert.Equal(t, uint64(9), uintVal)
}
func TestCursorString(t *testing.T) {
RegisterColumn(User.ID)
c := NewCursor(NewInt64Value(3, User.ID), User.ID, OrderAscending)
assert.JSONEq(t, `{"index":{"key":{"table":"user","column":"id"},"val":3},"order_col":{"table":"user","column":"id"},"order_dir":"ASC"}`, c.String())
}
func TestUsesTupleOrderingEdgeCases(t *testing.T) {
assert.False(t, (*Cursor[mysql.IntegerExpression, mysql.ColumnInteger])(nil).UsesTupleOrdering())
RegisterColumn(User.ID)
sameCol := NewCursor(NewInt64Value(0, User.ID), User.ID, OrderDescending)
assert.False(t, sameCol.UsesTupleOrdering())
}
func TestPaginateCondsValueTypes(t *testing.T) {
RegisterColumn(User.ID, Meeting.StartTime)
intCursor := NewCursor(NewInt64Value(1, User.ID), User.ID, OrderDescending)
sql := User.SELECT(User.AllColumns).WHERE(PaginateConds(intCursor)).DebugSql()
assert.Contains(t, sql, "user.id < 1")
strCursor := NewCursor(NewStringValue("x", MeetingRoom.ID), MeetingRoom.ID, OrderDescending)
RegisterColumn(MeetingRoom.ID)
sql = MeetingRoom.SELECT(MeetingRoom.AllColumns).WHERE(PaginateConds(strCursor)).DebugSql()
assert.Contains(t, sql, "meeting_room.id < 'x'")
tsCursor := NewCursor(
NewTimestampValue(mustTime("2026-01-01T00:00:00Z"), Meeting.StartTime),
Meeting.StartTime,
OrderDescending,
)
sql = Meeting.SELECT(Meeting.AllColumns).WHERE(PaginateConds(tsCursor)).DebugSql()
assert.Contains(t, sql, "meeting.start_time <")
}
func TestNewCursorFromJSONError(t *testing.T) {
RegisterColumn(User.ID)
_, err := NewCursorFromJSON(NewInt64Value(0, User.ID), []byte(`{invalid`))
require.Error(t, err)
require.ErrorIs(t, err, ErrBadCursorString)
}
func TestColWrongTypePanics(t *testing.T) {
RegisterColumn(User.ID)
// Key points at user.id (integer) but we construct StringValue for it.
sv := &StringValue{Key: NewColumnKey(User.ID), Val: "x"}
assert.Panics(t, func() { sv.Col() })
}
func TestQueryCount(t *testing.T) {
registerMeetingCursorColumns(t)
db, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { db.Close() })
rows := sqlmock.NewRows([]string{
"QueryCountResult.Total",
"QueryCountResult.Before",
"QueryCountResult.After",
}).AddRow(10, 3, 5)
mock.ExpectQuery("SELECT").WillReturnRows(rows)
when := mustTime("2026-06-01T09:00:00Z")
start := NewCursor(
NewStringValue("", Meeting.ID),
Meeting.StartTime,
OrderDescending,
).CopyWithVals(
NewStringValue("meeting-uuid-1", Meeting.ID),
NewTimestampValue(when, Meeting.StartTime),
)
end := start.CopyWithVals(
NewStringValue("meeting-uuid-2", Meeting.ID),
NewTimestampValue(when, Meeting.StartTime),
)
sqlo := &queryableDB{db: db}
res, err := QueryCount(sqlo, Meeting.ID, Meeting, mysql.Bool(true), start, end)
require.NoError(t, err)
assert.Equal(t, QueryCountResult{Total: 10, Before: 3, After: 5}, res)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryCountError(t *testing.T) {
registerMeetingCursorColumns(t)
db, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { db.Close() })
mock.ExpectQuery("SELECT").WillReturnError(assert.AnError)
when := mustTime("2026-06-01T09:00:00Z")
cursor := NewCursor(
NewStringValue("", Meeting.ID),
Meeting.StartTime,
OrderDescending,
).CopyWithVals(
NewStringValue("meeting-uuid-1", Meeting.ID),
NewTimestampValue(when, Meeting.StartTime),
)
sqlo := &queryableDB{db: db}
_, err = QueryCount(sqlo, Meeting.ID, Meeting, mysql.Bool(true), cursor, cursor)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to query count")
}
func TestBuildQueryCountFnInvoke(t *testing.T) {
registerMeetingCursorColumns(t)
db, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { db.Close() })
rows := sqlmock.NewRows([]string{
"QueryCountResult.Total",
"QueryCountResult.Before",
"QueryCountResult.After",
}).AddRow(1, 0, 0)
mock.ExpectQuery("SELECT").WillReturnRows(rows)
when := mustTime("2026-06-01T09:00:00Z")
cursor := NewCursor(
NewStringValue("", Meeting.ID),
Meeting.StartTime,
OrderDescending,
).CopyWithVals(
NewStringValue("meeting-uuid-1", Meeting.ID),
NewTimestampValue(when, Meeting.StartTime),
)
fn := BuildQueryCountFn(Meeting.ID, Meeting, mysql.Bool(true))
res, err := fn(&queryableDB{db: db}, cursor, cursor)
require.NoError(t, err)
assert.Equal(t, 1, res.Total)
}
// queryableDB implements dbx.Queryable for tests.
type queryableDB struct {
db *sql.DB
}
func (q *queryableDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
return q.db.QueryContext(ctx, query, args...)
}
func (q *queryableDB) Query(query string, args ...any) (*sql.Rows, error) {
return q.db.Query(query, args...)
}