add support for page-based cursortokens (like goext)
Some checks failed
Build Docker and Deploy / Build Docker Container (push) Successful in 52s
Build Docker and Deploy / Run Unit-Tests (push) Failing after 2m49s
Build Docker and Deploy / Deploy to Server (push) Has been skipped

This commit is contained in:
2025-12-03 19:46:09 +01:00
parent d26f18f356
commit 4b8ebf15d2
4 changed files with 339 additions and 33 deletions

View File

@@ -1313,3 +1313,253 @@ func TestListMessagesSubscriptionStatusAllNoSubscription(t *testing.T) {
}
}
func TestListMessagesPaginatedDirect(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
data := tt.InitDefaultData(t, ws)
type msg struct {
ChannelId string `json:"channel_id"`
ChannelInternalName string `json:"channel_internal_name"`
Content string `json:"content"`
MessageId string `json:"message_id"`
OwnerUserId string `json:"owner_user_id"`
Priority int `json:"priority"`
SenderIp string `json:"sender_ip"`
SenderName string `json:"sender_name"`
SenderUserId string `json:"sender_user_id"`
Timestamp string `json:"timestamp"`
Title string `json:"title"`
Trimmed bool `json:"trimmed"`
UsrMessageId string `json:"usr_message_id"`
}
type mglist struct {
Messages []msg `json:"messages"`
NPT string `json:"next_page_token"`
PageSize int `json:"page_size"`
}
// User 16 has 23 messages: "Lorem Ipsum 01" through "Lorem Ipsum 23"
// With page_size=10:
// Page 0 ($0): messages 23-14 (10 items)
// Page 1 ($1): messages 13-04 (10 items)
// Page 2 ($2): messages 03-01 (3 items)
// Test $0 - first page (same as @start)
{
msgList := tt.RequestAuthGet[mglist](t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$0"))
tt.AssertEqual(t, "msgList.len", 10, len(msgList.Messages))
tt.AssertEqual(t, "msgList.PageSize", 10, msgList.PageSize)
tt.AssertEqual(t, "msgList[0]", "Lorem Ipsum 23", msgList.Messages[0].Title)
tt.AssertEqual(t, "msgList[9]", "Lorem Ipsum 14", msgList.Messages[9].Title)
tt.AssertEqual(t, "msgList.NPT", "$1", msgList.NPT)
}
// Test $1 - second page
{
msgList := tt.RequestAuthGet[mglist](t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$1"))
tt.AssertEqual(t, "msgList.len", 10, len(msgList.Messages))
tt.AssertEqual(t, "msgList.PageSize", 10, msgList.PageSize)
tt.AssertEqual(t, "msgList[0]", "Lorem Ipsum 13", msgList.Messages[0].Title)
tt.AssertEqual(t, "msgList[9]", "Lorem Ipsum 04", msgList.Messages[9].Title)
tt.AssertEqual(t, "msgList.NPT", "$2", msgList.NPT)
}
// Test $2 - third/last page
{
msgList := tt.RequestAuthGet[mglist](t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$2"))
tt.AssertEqual(t, "msgList.len", 3, len(msgList.Messages))
tt.AssertEqual(t, "msgList.PageSize", 10, msgList.PageSize)
tt.AssertEqual(t, "msgList[0]", "Lorem Ipsum 03", msgList.Messages[0].Title)
tt.AssertEqual(t, "msgList[2]", "Lorem Ipsum 01", msgList.Messages[2].Title)
tt.AssertEqual(t, "msgList.NPT", "@end", msgList.NPT)
}
}
func TestListMessagesPaginatedDirectJumpToMiddle(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
data := tt.InitDefaultData(t, ws)
type msg struct {
ChannelId string `json:"channel_id"`
ChannelInternalName string `json:"channel_internal_name"`
Content string `json:"content"`
MessageId string `json:"message_id"`
OwnerUserId string `json:"owner_user_id"`
Priority int `json:"priority"`
SenderIp string `json:"sender_ip"`
SenderName string `json:"sender_name"`
SenderUserId string `json:"sender_user_id"`
Timestamp string `json:"timestamp"`
Title string `json:"title"`
Trimmed bool `json:"trimmed"`
UsrMessageId string `json:"usr_message_id"`
}
type mglist struct {
Messages []msg `json:"messages"`
NPT string `json:"next_page_token"`
PageSize int `json:"page_size"`
}
// Jump directly to page 1 (second page) without going through page 0
{
msgList := tt.RequestAuthGet[mglist](t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$1"))
tt.AssertEqual(t, "msgList.len", 10, len(msgList.Messages))
tt.AssertEqual(t, "msgList[0]", "Lorem Ipsum 13", msgList.Messages[0].Title)
tt.AssertEqual(t, "msgList.NPT", "$2", msgList.NPT)
}
// Jump directly to page 2 (third page) without going through pages 0 and 1
{
msgList := tt.RequestAuthGet[mglist](t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$2"))
tt.AssertEqual(t, "msgList.len", 3, len(msgList.Messages))
tt.AssertEqual(t, "msgList[0]", "Lorem Ipsum 03", msgList.Messages[0].Title)
tt.AssertEqual(t, "msgList.NPT", "@end", msgList.NPT)
}
}
func TestListMessagesPaginatedDirectBeyondEnd(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
data := tt.InitDefaultData(t, ws)
type msg struct {
ChannelId string `json:"channel_id"`
ChannelInternalName string `json:"channel_internal_name"`
Content string `json:"content"`
MessageId string `json:"message_id"`
OwnerUserId string `json:"owner_user_id"`
Priority int `json:"priority"`
SenderIp string `json:"sender_ip"`
SenderName string `json:"sender_name"`
SenderUserId string `json:"sender_user_id"`
Timestamp string `json:"timestamp"`
Title string `json:"title"`
Trimmed bool `json:"trimmed"`
UsrMessageId string `json:"usr_message_id"`
}
type mglist struct {
Messages []msg `json:"messages"`
NPT string `json:"next_page_token"`
PageSize int `json:"page_size"`
}
// Request a page beyond the last available data (page 10 when only 3 pages exist)
{
msgList := tt.RequestAuthGet[mglist](t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$10"))
tt.AssertEqual(t, "msgList.len", 0, len(msgList.Messages))
tt.AssertEqual(t, "msgList.NPT", "@end", msgList.NPT)
}
}
func TestListMessagesPaginatedDirectWithFilters(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
data := tt.InitDefaultData(t, ws)
type msg struct {
ChannelId string `json:"channel_id"`
ChannelInternalName string `json:"channel_internal_name"`
Content string `json:"content"`
MessageId string `json:"message_id"`
OwnerUserId string `json:"owner_user_id"`
Priority int `json:"priority"`
SenderIp string `json:"sender_ip"`
SenderName string `json:"sender_name"`
SenderUserId string `json:"sender_user_id"`
Timestamp string `json:"timestamp"`
Title string `json:"title"`
Trimmed bool `json:"trimmed"`
UsrMessageId string `json:"usr_message_id"`
}
type mglist struct {
Messages []msg `json:"messages"`
NPT string `json:"next_page_token"`
PageSize int `json:"page_size"`
TotalCount int `json:"total_count"`
}
// Test pagination with a filter applied
// User 0 has 22 messages, filter by priority=1 should give 11 messages
{
// First page
msgList0 := tt.RequestAuthGet[mglist](t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s&priority=1", 5, "$0"))
tt.AssertEqual(t, "msgList0.len", 5, len(msgList0.Messages))
tt.AssertEqual(t, "msgList0.NPT", "$1", msgList0.NPT)
// Second page
msgList1 := tt.RequestAuthGet[mglist](t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s&priority=1", 5, "$1"))
tt.AssertEqual(t, "msgList1.len", 5, len(msgList1.Messages))
tt.AssertEqual(t, "msgList1.NPT", "$2", msgList1.NPT)
// Third page (last)
msgList2 := tt.RequestAuthGet[mglist](t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s&priority=1", 5, "$2"))
tt.AssertEqual(t, "msgList2.len", 1, len(msgList2.Messages))
tt.AssertEqual(t, "msgList2.NPT", "@end", msgList2.NPT)
}
}
func TestListMessagesPaginatedDirectChainedNavigation(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
data := tt.InitDefaultData(t, ws)
type msg struct {
ChannelId string `json:"channel_id"`
ChannelInternalName string `json:"channel_internal_name"`
Content string `json:"content"`
MessageId string `json:"message_id"`
OwnerUserId string `json:"owner_user_id"`
Priority int `json:"priority"`
SenderIp string `json:"sender_ip"`
SenderName string `json:"sender_name"`
SenderUserId string `json:"sender_user_id"`
Timestamp string `json:"timestamp"`
Title string `json:"title"`
Trimmed bool `json:"trimmed"`
UsrMessageId string `json:"usr_message_id"`
}
type mglist struct {
Messages []msg `json:"messages"`
NPT string `json:"next_page_token"`
PageSize int `json:"page_size"`
}
// Test that following the returned NPT from $0 correctly navigates through all pages
var allMessages []msg
npt := "$0"
for npt != "@end" {
msgList := tt.RequestAuthGet[mglist](t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, npt))
allMessages = append(allMessages, msgList.Messages...)
npt = msgList.NPT
}
// User 16 has 23 messages total
tt.AssertEqual(t, "total messages", 23, len(allMessages))
tt.AssertEqual(t, "first message", "Lorem Ipsum 23", allMessages[0].Title)
tt.AssertEqual(t, "last message", "Lorem Ipsum 01", allMessages[22].Title)
}
func TestListMessagesPaginatedDirectInvalidToken(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
data := tt.InitDefaultData(t, ws)
// Test invalid paginated token (non-numeric after $)
tt.RequestAuthGetShouldFail(t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$abc"), 400, apierr.PAGETOKEN_ERROR)
// Test invalid paginated token (empty after $)
tt.RequestAuthGetShouldFail(t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$"), 400, apierr.PAGETOKEN_ERROR)
// Test invalid paginated token (float)
tt.RequestAuthGetShouldFail(t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$1.5"), 400, apierr.PAGETOKEN_ERROR)
}