626 lines
15 KiB
Go
Raw Permalink Normal View History

package syncv3
import (
"encoding/json"
"testing"
"time"
"github.com/matrix-org/sliding-sync/sync2"
"github.com/matrix-org/sliding-sync/sync3"
"github.com/matrix-org/sliding-sync/testutils"
"github.com/matrix-org/sliding-sync/testutils/m"
)
// Test that filters work initially and whilst streamed.
func TestFiltersEncryption(t *testing.T) {
boolTrue := true
boolFalse := false
rig := NewTestRig(t)
defer rig.Finish()
encryptedRoomID := "!TestFilters_encrypted:localhost"
unencryptedRoomID := "!TestFilters_unencrypted:localhost"
rig.SetupV2RoomsForUser(t, alice, NoFlush, map[string]RoomDescriptor{
encryptedRoomID: {
IsEncrypted: true,
},
unencryptedRoomID: {
IsEncrypted: false,
},
})
aliceToken := rig.Token(alice)
// connect and make sure either the encrypted room or not depending on what the filter says
res := rig.V3.mustDoV3Request(t, aliceToken, sync3.Request{
Lists: map[string]sync3.RequestList{
"enc": {
Ranges: sync3.SliceRanges{
[2]int64{0, 1}, // all rooms
},
Filters: &sync3.RequestFilters{
IsEncrypted: &boolTrue,
},
},
"noenc": {
Ranges: sync3.SliceRanges{
[2]int64{0, 1}, // all rooms
},
Filters: &sync3.RequestFilters{
IsEncrypted: &boolFalse,
},
},
},
})
m.MatchResponse(t, res, m.MatchLists(
map[string][]m.ListMatcher{
"enc": {
m.MatchV3Count(1),
m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3SyncOp(0, 0, []string{encryptedRoomID}),
),
},
"noenc": {
m.MatchV3Count(1),
m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
// Note: expect position 0 here---this is a separate list
m.MatchV3SyncOp(0, 0, []string{unencryptedRoomID}),
),
},
},
))
// change the unencrypted room into an encrypted room
rig.EncryptRoom(t, alice, unencryptedRoomID)
// now requesting the encrypted list should include it (added)
res = rig.V3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
Lists: map[string]sync3.RequestList{
"enc": {
Ranges: sync3.SliceRanges{
[2]int64{0, 1}, // all rooms
},
// sticky; should remember filters
},
"noenc": {
Ranges: sync3.SliceRanges{
[2]int64{0, 1}, // all rooms
},
// sticky; should remember filters
},
},
})
m.MatchResponse(t, res, m.MatchLists(
map[string][]m.ListMatcher{
"enc": {
m.MatchV3Count(2),
m.MatchV3Ops(
m.MatchV3DeleteOp(1), m.MatchV3InsertOp(0, unencryptedRoomID),
),
},
"noenc": {
m.MatchV3Count(0),
m.MatchV3Ops(
m.MatchV3DeleteOp(0),
),
},
},
))
// requesting the encrypted list from scratch returns 2 rooms now
res = rig.V3.mustDoV3Request(t, aliceToken, sync3.Request{
Lists: map[string]sync3.RequestList{
"enc": {
Ranges: sync3.SliceRanges{
[2]int64{0, 1}, // all rooms
},
Filters: &sync3.RequestFilters{
IsEncrypted: &boolTrue,
},
}},
})
m.MatchResponse(t, res, m.MatchLists(
map[string][]m.ListMatcher{
"enc": {
m.MatchV3Count(2),
m.MatchV3Ops(
m.MatchV3SyncOp(0, 1, []string{unencryptedRoomID, encryptedRoomID}),
),
},
},
))
// requesting the unencrypted stream from scratch returns 0 rooms
res = rig.V3.mustDoV3Request(t, aliceToken, sync3.Request{
Lists: map[string]sync3.RequestList{
"noenc": {
Ranges: sync3.SliceRanges{
[2]int64{0, 1}, // all rooms
},
Filters: &sync3.RequestFilters{
IsEncrypted: &boolFalse,
},
}},
})
m.MatchResponse(t, res, m.MatchLists(
map[string][]m.ListMatcher{
"noenc": {
m.MatchV3Count(0),
},
},
))
}
func TestFiltersInvite(t *testing.T) {
boolTrue := true
boolFalse := false
rig := NewTestRig(t)
defer rig.Finish()
roomID := "!a:localhost"
2023-08-18 13:23:46 +01:00
t.Log("Alice is invited to a room.")
rig.SetupV2RoomsForUser(t, alice, NoFlush, map[string]RoomDescriptor{
roomID: {
MembershipOfSyncer: "invite",
},
})
aliceToken := rig.Token(alice)
2023-08-18 13:23:46 +01:00
t.Log("Alice sliding syncs, requesting two separate lists: invites and joined rooms.")
res := rig.V3.mustDoV3Request(t, aliceToken, sync3.Request{
Lists: map[string]sync3.RequestList{
"inv": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
IsInvite: &boolTrue,
},
},
"noinv": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
IsInvite: &boolFalse,
},
},
},
})
2023-08-18 13:23:46 +01:00
t.Log("Alice should see the room appear in the invites list, and nothing in the joined rooms list.")
m.MatchResponse(t, res, m.MatchLists(
map[string][]m.ListMatcher{
"inv": {
m.MatchV3Count(1),
m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3SyncOp(0, 0, []string{roomID}),
),
},
"noinv": {
m.MatchV3Count(0),
},
},
))
2023-08-18 13:23:46 +01:00
t.Log("Alice accepts the invite.")
rig.V2.queueResponse(alice, sync2.SyncResponse{
Rooms: sync2.SyncRoomsResponse{
Join: map[string]sync2.SyncV2JoinResponse{
roomID: {
State: sync2.EventsResponse{
Events: createRoomState(t, "@creator:other", time.Now()),
},
Timeline: sync2.TimelineResponse{
Events: []json.RawMessage{testutils.NewJoinEvent(t, alice)},
},
},
},
},
})
2022-05-27 09:54:17 +01:00
// now the room should move from one room to another
res = rig.V3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
Lists: map[string]sync3.RequestList{
"inv": {
2022-05-27 09:54:17 +01:00
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
2022-05-27 09:54:17 +01:00
// sticky; should remember filters
},
"noinv": {
2022-05-27 09:54:17 +01:00
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
2022-05-27 09:54:17 +01:00
// sticky; should remember filters
},
2022-05-27 09:54:17 +01:00
},
})
// the room swaps from the invite list to the join list
m.MatchResponse(t, res, m.MatchLists(
map[string][]m.ListMatcher{
"inv": {
m.MatchV3Count(0),
m.MatchV3Ops(
m.MatchV3DeleteOp(0),
),
},
"noinv": {
m.MatchV3Count(1),
m.MatchV3Ops(
m.MatchV3DeleteOp(0),
m.MatchV3InsertOp(0, roomID),
),
},
},
))
}
func TestFiltersRoomName(t *testing.T) {
rig := NewTestRig(t)
defer rig.Finish()
ridApple := "!a:localhost"
ridPear := "!b:localhost"
ridLemon := "!c:localhost"
ridOrange := "!d:localhost"
ridPineapple := "!e:localhost"
ridBanana := "!f:localhost"
ridKiwi := "!g:localhost"
rig.SetupV2RoomsForUser(t, alice, NoFlush, map[string]RoomDescriptor{
ridApple: {
Name: "apple",
},
ridPear: {
Name: "PEAR",
},
ridLemon: {
Name: "Lemon Lemon",
},
ridOrange: {
Name: "ooooorange",
},
ridPineapple: {
Name: "PineApple",
},
ridBanana: {
Name: "BaNaNaNaNaNaNaN",
},
ridKiwi: {
Name: "kiwiwiwiwiwiwi",
},
})
aliceToken := rig.Token(alice)
// make sure the room name filter works
res := rig.V3.mustDoV3Request(t, aliceToken, sync3.Request{
Lists: map[string]sync3.RequestList{
"a": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
RoomNameFilter: "a",
},
},
},
})
m.MatchResponse(t, res, m.MatchList("a",
m.MatchV3Count(5),
m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3SyncOp(0, 4, []string{
ridApple, ridPear, ridOrange, ridPineapple, ridBanana,
}, true),
),
))
// refine the filter
res = rig.V3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
Lists: map[string]sync3.RequestList{
"a": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
RoomNameFilter: "app",
},
},
},
})
m.MatchResponse(t, res, m.MatchList("a",
m.MatchV3Count(2),
m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3InvalidateOp(0, 4),
m.MatchV3SyncOp(0, 1, []string{
ridApple, ridPineapple,
}, true),
),
))
}
func TestFiltersRoomTypes(t *testing.T) {
rig := NewTestRig(t)
defer rig.Finish()
spaceRoomID := "!spaceRoomID:localhost"
otherRoomID := "!other:localhost"
roomID := "!roomID:localhost"
roomType := "m.space"
otherRoomType := "something_else"
invalid := "lalalalaala"
rig.SetupV2RoomsForUser(t, alice, NoFlush, map[string]RoomDescriptor{
spaceRoomID: {
RoomType: roomType,
},
roomID: {},
otherRoomID: {
RoomType: otherRoomType,
},
})
aliceToken := rig.Token(alice)
// make sure the room_types and not_room_types filters works
res := rig.V3.mustDoV3Request(t, aliceToken, sync3.Request{
Lists: map[string]sync3.RequestList{
// returns spaceRoomID only due to direct match
"a": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
RoomTypes: []*string{&roomType},
},
},
// returns roomID only due to direct match (null = things without a room type)
"b": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
RoomTypes: []*string{nil},
},
},
// returns roomID and otherRoomID due to exclusion
"c": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
NotRoomTypes: []*string{&roomType},
},
},
// returns otherRoomID due to otherRoomType inclusive, roomType is excluded (override)
"d": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
RoomTypes: []*string{&roomType, &otherRoomType},
NotRoomTypes: []*string{&roomType},
},
},
// returns no rooms as filtered room type isn't set on any rooms
"e": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
RoomTypes: []*string{&invalid},
},
},
// returns all rooms as filtered not room type isn't set on any rooms
"f": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20}, // all rooms
},
Filters: &sync3.RequestFilters{
NotRoomTypes: []*string{&invalid},
},
},
},
})
m.MatchResponse(t, res, m.MatchLists(map[string][]m.ListMatcher{
"a": {
2023-04-04 20:03:47 +01:00
m.MatchV3Count(1), m.MatchV3Ops(m.MatchV3SyncOp(0, 0, []string{spaceRoomID})),
},
"b": {
2023-04-04 20:03:47 +01:00
m.MatchV3Count(1), m.MatchV3Ops(m.MatchV3SyncOp(0, 0, []string{roomID})),
},
"c": {
2023-04-04 20:03:47 +01:00
m.MatchV3Count(2), m.MatchV3Ops(m.MatchV3SyncOp(0, 1, []string{roomID, otherRoomID}, true)),
},
"d": {
2023-04-04 20:03:47 +01:00
m.MatchV3Count(1), m.MatchV3Ops(m.MatchV3SyncOp(0, 0, []string{otherRoomID})),
},
"e": {
m.MatchV3Count(0),
},
"f": {
2023-04-04 20:03:47 +01:00
m.MatchV3Count(3), m.MatchV3Ops(m.MatchV3SyncOp(0, 2, []string{roomID, otherRoomID, spaceRoomID}, true)),
},
}))
}
func TestFiltersTags(t *testing.T) {
tagFav := "m.favourite"
tagLow := "m.lowpriority"
rig := NewTestRig(t)
defer rig.Finish()
fav1RoomID := "!fav1:localhost"
fav2RoomID := "!fav2:localhost"
favAndLowRoomID := "!favlow:localhost"
low1RoomID := "!low1:localhost"
low2RoomID := "!low2:localhost"
rig.SetupV2RoomsForUser(t, alice, NoFlush, map[string]RoomDescriptor{
fav1RoomID: {
Tags: map[string]float64{
tagFav: 0.5,
},
},
fav2RoomID: {
Tags: map[string]float64{
tagFav: 0.3,
},
},
favAndLowRoomID: {
Tags: map[string]float64{
tagFav: 0.5,
tagLow: 0.9,
},
},
low1RoomID: {
Tags: map[string]float64{
tagLow: 0.2,
},
},
low2RoomID: {
Tags: map[string]float64{
tagLow: 0.92,
},
},
})
aliceToken := rig.Token(alice)
res := rig.V3.mustDoV3Request(t, aliceToken, sync3.Request{
Lists: map[string]sync3.RequestList{
"fav": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20},
},
Filters: &sync3.RequestFilters{
Tags: []string{tagFav},
},
},
"lp": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20},
},
Filters: &sync3.RequestFilters{
Tags: []string{tagLow},
},
},
"favlp": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20},
},
Filters: &sync3.RequestFilters{
Tags: []string{tagFav, tagLow},
},
},
},
})
m.MatchResponse(t, res, m.MatchList("fav", m.MatchV3Count(3), m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3SyncOp(0, 2, []string{fav1RoomID, fav2RoomID, favAndLowRoomID}, true),
)), m.MatchList("lp", m.MatchV3Count(3), m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3SyncOp(0, 2, []string{low1RoomID, low2RoomID, favAndLowRoomID}, true),
)), m.MatchList("favlp", m.MatchV3Count(5), m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3SyncOp(0, 4, []string{fav1RoomID, fav2RoomID, favAndLowRoomID, low1RoomID, low2RoomID}, true),
)))
// first bump the fav1 room
rig.FlushEvent(t, alice, fav1RoomID, testutils.NewMessageEvent(t, alice, "Hi"))
res = rig.V3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
Lists: map[string]sync3.RequestList{
"fav": {Ranges: sync3.SliceRanges{{0, 20}}},
"lp": {Ranges: sync3.SliceRanges{{0, 20}}},
"favlp": {Ranges: sync3.SliceRanges{{0, 20}}},
},
})
// now nuke it by removing the tag
rig.V2.queueResponse(alice, sync2.SyncResponse{
Rooms: sync2.SyncRoomsResponse{
Join: map[string]sync2.SyncV2JoinResponse{
fav1RoomID: {
AccountData: sync2.EventsResponse{
Events: []json.RawMessage{
testutils.NewAccountData(t, "m.tag", map[string]interface{}{}), // empty content
},
},
},
},
},
})
rig.V2.waitUntilEmpty(t, alice)
// we should see DELETEs
res = rig.V3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
Lists: map[string]sync3.RequestList{
"fav": {Ranges: sync3.SliceRanges{{0, 20}}},
"lp": {Ranges: sync3.SliceRanges{{0, 20}}},
"favlp": {Ranges: sync3.SliceRanges{{0, 20}}},
},
})
m.MatchResponse(t, res, m.MatchList("fav", m.MatchV3Count(2), m.MatchV3Ops(
m.MatchV3DeleteOp(0),
)), m.MatchList("lp", m.MatchV3Ops()), m.MatchList("favlp", m.MatchV3Count(4), m.MatchV3Ops(
m.MatchV3DeleteOp(0),
)))
2022-08-22 18:31:44 +01:00
// check not_tags works
res = rig.V3.mustDoV3Request(t, aliceToken, sync3.Request{
Lists: map[string]sync3.RequestList{
"fav": {
2022-08-22 18:31:44 +01:00
Ranges: sync3.SliceRanges{
[2]int64{0, 20},
},
Filters: &sync3.RequestFilters{
Tags: []string{tagFav},
},
},
"nofav": {
2022-08-22 18:31:44 +01:00
Ranges: sync3.SliceRanges{
[2]int64{0, 20},
},
Filters: &sync3.RequestFilters{
NotTags: []string{tagFav},
},
},
},
})
m.MatchResponse(t, res, m.MatchList("fav", m.MatchV3Count(2), m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3SyncOp(0, 1, []string{fav2RoomID, favAndLowRoomID}, true),
)), m.MatchList("nofav", m.MatchV3Count(3), m.MatchV3Ops(
2023-04-04 20:03:47 +01:00
m.MatchV3SyncOp(0, 2, []string{low1RoomID, low2RoomID, fav1RoomID}, true),
2022-08-22 18:31:44 +01:00
)))
// remove a fav, it should move to the other list
rig.V2.queueResponse(alice, sync2.SyncResponse{
Rooms: sync2.SyncRoomsResponse{
Join: map[string]sync2.SyncV2JoinResponse{
fav2RoomID: {
AccountData: sync2.EventsResponse{
Events: []json.RawMessage{
testutils.NewAccountData(t, "m.tag", map[string]interface{}{
"tags": map[string]interface{}{},
}),
},
},
},
},
},
})
rig.V2.waitUntilEmpty(t, alice)
res = rig.V3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
Lists: map[string]sync3.RequestList{
"fav": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20},
},
},
"nofav": {
Ranges: sync3.SliceRanges{
[2]int64{0, 20},
},
},
},
})
// Room ordering is: (recent first)
// FAV1, LOW2, LOW1, FAVLOW, FAV2
// so lists before were:
// FAVLOW, FAV2
// FAV1, LOW2, LOW1
// now we have removed fav tag on FAV2 so new lists are:
// FAVLOW
// FAV1, LOW2, LOW1, FAV2
m.MatchResponse(t, res, m.MatchList("fav", m.MatchV3Count(1), m.MatchV3Ops(
m.MatchV3DeleteOp(1),
)), m.MatchList("nofav", m.MatchV3Count(4), m.MatchV3Ops(
m.MatchV3DeleteOp(3),
m.MatchV3InsertOp(3, fav2RoomID),
)))
}