sliding-sync/sync3/sort_test.go
David Robertson 4f69909f8b
Fixup tests
2023-05-24 19:26:28 +01:00

390 lines
10 KiB
Go

package sync3
import (
"reflect"
"strings"
"testing"
"github.com/matrix-org/sliding-sync/internal"
"github.com/matrix-org/sliding-sync/sync3/caches"
)
type finder struct {
rooms map[string]*RoomConnMetadata
roomIDs []string
}
func (f finder) ReadOnlyRoom(roomID string) *RoomConnMetadata {
return f.rooms[roomID]
}
func newFinder(rooms []*RoomConnMetadata) finder {
m := make(map[string]*RoomConnMetadata)
ids := make([]string, len(rooms))
for i := range rooms {
ids[i] = rooms[i].RoomID
m[ids[i]] = rooms[i]
}
return finder{
rooms: m,
roomIDs: ids,
}
}
func TestSortBySingleOperation(t *testing.T) {
const listKey = "my_list"
room1 := "!1:localhost"
room2 := "!2:localhost"
room3 := "!3:localhost"
room4 := "!4:localhost"
rooms := []*RoomConnMetadata{
{
RoomMetadata: internal.RoomMetadata{
RoomID: room1,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 3,
NotificationCount: 12,
CanonicalisedName: "foo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 600},
},
{
RoomMetadata: internal.RoomMetadata{
RoomID: room2,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 0,
NotificationCount: 3,
CanonicalisedName: "koo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 700},
},
{
RoomMetadata: internal.RoomMetadata{
RoomID: room3,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 2,
NotificationCount: 7,
CanonicalisedName: "yoo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 900},
},
{
RoomMetadata: internal.RoomMetadata{
RoomID: room4,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 1,
NotificationCount: 1,
CanonicalisedName: "boo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 800},
},
}
// name: 4,1,2,3
// recency: 3,4,2,1
// highlight: 1,3,4,2
// notif: 1,3,2,4
// level+recency: 3,4,1,2 as 3,4,1 have highlights then sorted by recency
wantMap := map[string][]string{
SortByName: {room4, room1, room2, room3},
SortByRecency: {room3, room4, room2, room1},
SortByHighlightCount: {room1, room3, room4, room2},
SortByNotificationCount: {room1, room3, room2, room4},
SortByNotificationLevel + " " + SortByRecency: {room3, room4, room1, room2},
}
f := newFinder(rooms)
sr := NewSortableRooms(f, listKey, f.roomIDs)
for sortBy, wantOrder := range wantMap {
sr.Sort(strings.Split(sortBy, " "))
var gotRoomIDs []string
for i := range sr.roomIDs {
gotRoomIDs = append(gotRoomIDs, sr.roomIDs[i])
}
for i := range wantOrder {
if wantOrder[i] != gotRoomIDs[i] {
t.Errorf("Sort: %s got %v want %v", sortBy, gotRoomIDs, wantOrder)
}
}
}
}
func TestSortByMultipleOperations(t *testing.T) {
const listKey = "my_list"
room1 := "!1:localhost"
room2 := "!2:localhost"
room3 := "!3:localhost"
room4 := "!4:localhost"
rooms := []*RoomConnMetadata{
{
RoomMetadata: internal.RoomMetadata{
RoomID: room1,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 1,
NotificationCount: 1,
CanonicalisedName: "foo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 600},
},
{
RoomMetadata: internal.RoomMetadata{
RoomID: room2,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 1,
NotificationCount: 5,
CanonicalisedName: "koo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 700},
},
{
RoomMetadata: internal.RoomMetadata{
RoomID: room3,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 0,
NotificationCount: 0,
CanonicalisedName: "yoo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 800},
},
{
RoomMetadata: internal.RoomMetadata{
RoomID: room4,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 0,
NotificationCount: 0,
CanonicalisedName: "boo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 900},
},
}
testCases := []struct {
SortBy []string
WantRooms []string
}{
{
SortBy: []string{SortByHighlightCount, SortByNotificationCount, SortByRecency, SortByName},
WantRooms: []string{room2, room1, room4, room3},
},
{
SortBy: []string{SortByHighlightCount, SortByName},
WantRooms: []string{room1, room2, room4, room3},
},
{
SortBy: []string{SortByNotificationLevel, SortByName},
WantRooms: []string{room1, room2, room4, room3},
},
{
SortBy: []string{SortByNotificationLevel, SortByRecency},
WantRooms: []string{room2, room1, room4, room3},
},
}
f := newFinder(rooms)
sr := NewSortableRooms(f, listKey, f.roomIDs)
for _, tc := range testCases {
sr.Sort(tc.SortBy)
var gotRoomIDs []string
for i := range sr.roomIDs {
gotRoomIDs = append(gotRoomIDs, sr.roomIDs[i])
}
for i := range tc.WantRooms {
if tc.WantRooms[i] != gotRoomIDs[i] {
t.Errorf("Sort: %v got %v want %v", tc.SortBy, gotRoomIDs, tc.WantRooms)
}
}
}
}
// Test that if you remove a room, it updates the lookup map.
func TestSortableRoomsRemove(t *testing.T) {
const listKey = "my_list"
room1 := "!1:localhost"
room2 := "!2:localhost"
rooms := []*RoomConnMetadata{
{
RoomMetadata: internal.RoomMetadata{
RoomID: room1,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 1,
NotificationCount: 1,
CanonicalisedName: "foo",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 700},
},
{
RoomMetadata: internal.RoomMetadata{
RoomID: room2,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 2,
NotificationCount: 2,
CanonicalisedName: "foo2",
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 600},
},
}
f := newFinder(rooms)
sr := NewSortableRooms(f, listKey, f.roomIDs)
if err := sr.Sort([]string{SortByRecency}); err != nil { // room 1 is first, then room 2
t.Fatalf("Sort: %s", err)
}
if i, ok := sr.IndexOf(room1); i != 0 || !ok {
t.Errorf("IndexOf room 1 returned %v %v", i, ok)
}
if i, ok := sr.IndexOf(room2); i != 1 || !ok {
t.Errorf("IndexOf room 2 returned %v %v", i, ok)
}
// Remove room 1, so room 2 should take its place.
rmIndex := sr.Remove(room1)
if rmIndex != 0 {
t.Fatalf("Remove: return removed index %v want 0", rmIndex)
}
// check
if i, ok := sr.IndexOf(room1); ok { // should be !ok
t.Errorf("IndexOf room 1 returned %v %v", i, ok)
}
if i, ok := sr.IndexOf(room2); i != 0 || !ok {
t.Errorf("IndexOf room 2 returned %v %v", i, ok)
}
}
// dedicated test as it relies on multiple fields
func TestSortByNotificationLevel(t *testing.T) {
const listKey = "my_list"
// create the full set of possible sort variables, most recent message last
roomUnencHC := "!unencrypted-highlight-count:localhost"
roomUnencHCNC := "!unencrypted-highlight-and-notif-count:localhost"
roomUnencNC := "!unencrypted-notif-count:localhost"
roomUnenc := "!unencrypted:localhost"
roomEncHC := "!encrypted-highlight-count:localhost"
roomEncHCNC := "!encrypted-highlight-and-notif-count:localhost"
roomEncNC := "!encrypted-notif-count:localhost"
roomEnc := "!encrypted:localhost"
roomsMap := map[string]*RoomConnMetadata{
roomUnencHC: {
RoomMetadata: internal.RoomMetadata{
Encrypted: false,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 1,
NotificationCount: 0,
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 1},
},
roomUnencHCNC: {
RoomMetadata: internal.RoomMetadata{
Encrypted: false,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 1,
NotificationCount: 1,
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 2},
},
roomUnencNC: {
RoomMetadata: internal.RoomMetadata{
Encrypted: false,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 0,
NotificationCount: 1,
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 3},
},
roomUnenc: {
RoomMetadata: internal.RoomMetadata{
Encrypted: false,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 0,
NotificationCount: 0,
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 4},
},
roomEncHC: {
RoomMetadata: internal.RoomMetadata{
Encrypted: true,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 1,
NotificationCount: 0,
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 5},
},
roomEncHCNC: {
RoomMetadata: internal.RoomMetadata{
Encrypted: true,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 1,
NotificationCount: 1,
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 6},
},
roomEncNC: {
RoomMetadata: internal.RoomMetadata{
RoomID: roomEncNC,
Encrypted: true,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 0,
NotificationCount: 1,
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 7},
},
roomEnc: {
RoomMetadata: internal.RoomMetadata{
RoomID: roomEnc,
Encrypted: true,
},
UserRoomData: caches.UserRoomData{
HighlightCount: 0,
NotificationCount: 0,
},
LastInterestedEventTimestamps: map[string]uint64{listKey: 8},
},
}
roomIDs := make([]string, len(roomsMap))
rooms := make([]*RoomConnMetadata, len(roomsMap))
i := 0
for roomID, room := range roomsMap {
room.RoomMetadata.RoomID = roomID
roomIDs[i] = roomID
rooms[i] = room
i++
}
t.Logf("%v", roomIDs)
f := newFinder(rooms)
sr := NewSortableRooms(f, listKey, roomIDs)
if err := sr.Sort([]string{SortByNotificationLevel, SortByRecency}); err != nil {
t.Fatalf("Sort: %s", err)
}
var gotRoomIDs []string
for i := range sr.roomIDs {
gotRoomIDs = append(gotRoomIDs, sr.roomIDs[i])
}
// we expect the rooms to be grouped in this order:
// HIGHLIGHT COUNT > 0
// ENCRYPTED, NOTIF COUNT > 0
// UNENCRYPTED, NOTIF COUNT > 0
// REST
// Within each group, we expect recency sorting due to SortByRecency
wantRoomIDs := []string{
roomEncHCNC, roomEncHC, roomUnencHCNC, roomUnencHC, // in practice we don't expect to see this as encrypted rooms won't have highlight counts > 0
roomEncNC,
roomUnencNC,
roomEnc, roomUnenc,
}
if !reflect.DeepEqual(gotRoomIDs, wantRoomIDs) {
t.Errorf("got: %v", gotRoomIDs)
t.Errorf("want: %v", wantRoomIDs)
}
}