fluxer/tests/integration/message_fetch_around_limit_test.go
2026-01-01 21:05:54 +00:00

180 lines
5.4 KiB
Go

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
package integration
import (
"fmt"
"net/http"
"sort"
"testing"
"time"
)
func TestMessageFetchAroundLimit(t *testing.T) {
client := newTestClient(t)
account := createTestAccount(t, client)
guild := createGuild(t, client, account.Token, "Message Fetch Around Limit")
channelID := parseSnowflake(t, guild.SystemChannel)
clearChannelMessages(t, client, channelID, account.Token)
const totalMessages = 60
seedResult := seedMessagesWithContent(t, client, channelID, totalMessages, account.UserID)
if len(seedResult.Messages) != totalMessages {
t.Fatalf("expected %d seeded messages, got %d", totalMessages, len(seedResult.Messages))
}
const limit = 50
olderCount := limit / 2
newerCount := limit - 1 - olderCount
type timedMessage struct {
ID string
Timestamp time.Time
}
ordered := make([]timedMessage, len(seedResult.Messages))
for i, message := range seedResult.Messages {
ts, err := time.Parse(time.RFC3339Nano, message.Timestamp)
if err != nil {
t.Fatalf("failed to parse message timestamp %q: %v", message.Timestamp, err)
}
ordered[i] = timedMessage{ID: message.MessageID, Timestamp: ts}
}
sort.Slice(ordered, func(i, j int) bool {
return ordered[i].Timestamp.Before(ordered[j].Timestamp)
})
if olderCount+newerCount+1 > len(ordered) {
t.Fatalf("not enough seeded messages (%d) for older=%d and newer=%d requests", len(ordered), olderCount, newerCount)
}
anchorIndex := olderCount + 5
if anchorIndex+newerCount >= len(ordered) {
t.Fatalf("anchor index %d too close to end for %d newer messages", anchorIndex, newerCount)
}
anchorMessageID := ordered[anchorIndex].ID
resp, err := client.getWithAuth(
fmt.Sprintf("/channels/%d/messages?limit=%d&around=%s", channelID, limit, anchorMessageID),
account.Token,
)
if err != nil {
t.Fatalf("failed to fetch messages around anchor: %v", err)
}
assertStatus(t, resp, http.StatusOK)
var fetchedMessages []struct {
ID string `json:"id"`
}
decodeJSONResponse(t, resp, &fetchedMessages)
if len(fetchedMessages) != limit {
t.Fatalf("expected %d messages, got %d", limit, len(fetchedMessages))
}
anchorPos := -1
for i, msg := range fetchedMessages {
if msg.ID == anchorMessageID {
anchorPos = i
break
}
}
if anchorPos == -1 {
t.Fatalf("anchor message %s not present in response", anchorMessageID)
}
if anchorPos != newerCount {
t.Fatalf("expected %d messages before anchor, got %d", newerCount, anchorPos)
}
afterCount := len(fetchedMessages) - anchorPos - 1
if afterCount != olderCount {
t.Fatalf("expected %d messages after anchor, got %d", olderCount, afterCount)
}
expectedNewerIDs := make(map[string]struct{})
for i := anchorIndex + 1; i <= anchorIndex+newerCount; i++ {
expectedNewerIDs[ordered[i].ID] = struct{}{}
}
expectedOlderIDs := make(map[string]struct{})
for i := anchorIndex - olderCount; i < anchorIndex; i++ {
expectedOlderIDs[ordered[i].ID] = struct{}{}
}
actualNewerIDs := make(map[string]struct{})
actualNewer := fetchedMessages[:anchorPos]
for i, msg := range actualNewer {
actualNewerIDs[msg.ID] = struct{}{}
msgValue := parseSnowflake(t, msg.ID)
anchorValue := parseSnowflake(t, anchorMessageID)
if msgValue <= anchorValue {
t.Fatalf("newer message %s has snowflake %d <= anchor %s (%d)", msg.ID, msgValue, anchorMessageID, anchorValue)
}
if i > 0 {
prevValue := parseSnowflake(t, actualNewer[i-1].ID)
if prevValue <= msgValue {
t.Fatalf("newer messages not in descending order: %d <= %d", prevValue, msgValue)
}
}
}
actualOlderIDs := make(map[string]struct{})
actualOlder := fetchedMessages[anchorPos+1:]
for i, msg := range actualOlder {
actualOlderIDs[msg.ID] = struct{}{}
msgValue := parseSnowflake(t, msg.ID)
anchorValue := parseSnowflake(t, anchorMessageID)
if msgValue >= anchorValue {
t.Fatalf("older message %s has snowflake %d >= anchor %s (%d)", msg.ID, msgValue, anchorMessageID, anchorValue)
}
if i > 0 {
prevValue := parseSnowflake(t, actualOlder[i-1].ID)
if prevValue <= msgValue {
t.Fatalf("older messages not in descending order: %d <= %d", prevValue, msgValue)
}
}
}
if len(actualNewerIDs) != newerCount {
t.Fatalf("expected %d unique newer messages, got %d", newerCount, len(actualNewerIDs))
}
if len(actualOlderIDs) != olderCount {
t.Fatalf("expected %d unique older messages, got %d", olderCount, len(actualOlderIDs))
}
for id := range expectedNewerIDs {
if _, ok := actualNewerIDs[id]; !ok {
t.Fatalf("expected newer message %s missing", id)
}
}
for id := range expectedOlderIDs {
if _, ok := actualOlderIDs[id]; !ok {
t.Fatalf("expected older message %s missing", id)
}
}
t.Logf("received %d messages around anchor %s (newer=%d, older=%d)", len(fetchedMessages), anchorMessageID, newerCount, olderCount)
}