mirror of
https://github.com/matrix-org/sliding-sync.git
synced 2025-03-10 13:37:11 +00:00
Migrate lists_test to end-to-end tests
Add more helper functions like `WithPos` and `MatchTimeline`.
This commit is contained in:
parent
86570aaff4
commit
a40441e963
@ -27,6 +27,11 @@ const (
|
||||
SharedSecret = "complement"
|
||||
)
|
||||
|
||||
var (
|
||||
boolTrue = true
|
||||
boolFalse = false
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
ID string `json:"event_id"`
|
||||
Type string `json:"type"`
|
||||
@ -53,6 +58,18 @@ type Event struct {
|
||||
Redacts string `json:"redacts"`
|
||||
}
|
||||
|
||||
func NewEncryptionEvent() Event {
|
||||
return Event{
|
||||
Type: "m.room.encryption",
|
||||
StateKey: ptr(""),
|
||||
Content: map[string]interface{}{
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"rotation_period_ms": 604800000,
|
||||
"rotation_period_msgs": 100,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type MessagesBatch struct {
|
||||
Chunk []json.RawMessage `json:"chunk"`
|
||||
Start string `json:"start"`
|
||||
@ -510,6 +527,11 @@ func (c *CSAPI) MustDoFunc(t *testing.T, method string, paths []string, opts ...
|
||||
// SlidingSync performs a single sliding sync request
|
||||
func (c *CSAPI) SlidingSync(t *testing.T, data sync3.Request, opts ...RequestOpt) (resBody *sync3.Response) {
|
||||
t.Helper()
|
||||
if len(opts) == 0 {
|
||||
opts = append(opts, WithQueries(url.Values{
|
||||
"timeout": []string{"500"},
|
||||
}))
|
||||
}
|
||||
opts = append(opts, WithJSONBody(t, data))
|
||||
res := c.MustDoFunc(t, "POST", []string{"_matrix", "client", "unstable", "org.matrix.msc3575", "sync"}, opts...)
|
||||
body := ParseJSON(t, res)
|
||||
|
504
tests-e2e/lists_test.go
Normal file
504
tests-e2e/lists_test.go
Normal file
@ -0,0 +1,504 @@
|
||||
package syncv3_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/sync-v3/sync3"
|
||||
"github.com/matrix-org/sync-v3/testutils/m"
|
||||
)
|
||||
|
||||
// Test that multiple lists can be independently scrolled through
|
||||
func TestMultipleLists(t *testing.T) {
|
||||
alice := registerNewUser(t)
|
||||
|
||||
// make 10 encrypted rooms and make 10 unencrypted rooms. [0] is most recent
|
||||
var encryptedRoomIDs []string
|
||||
var unencryptedRoomIDs []string
|
||||
for i := 0; i < 10; i++ {
|
||||
unencryptedRoomID := alice.CreateRoom(t, map[string]interface{}{
|
||||
"preset": "public_chat",
|
||||
})
|
||||
unencryptedRoomIDs = append([]string{unencryptedRoomID}, unencryptedRoomIDs...) // push in array
|
||||
encryptedRoomID := alice.CreateRoom(t, map[string]interface{}{
|
||||
"preset": "public_chat",
|
||||
"initial_state": []Event{
|
||||
NewEncryptionEvent(),
|
||||
},
|
||||
})
|
||||
encryptedRoomIDs = append([]string{encryptedRoomID}, encryptedRoomIDs...) // push in array
|
||||
time.Sleep(time.Millisecond) // ensure timestamp changes
|
||||
}
|
||||
|
||||
// request 2 lists, one set encrypted, one set unencrypted
|
||||
res := alice.SlidingSync(t, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsEncrypted: &boolTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsEncrypted: &boolFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
m.MatchResponse(t, res,
|
||||
m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(encryptedRoomIDs)),
|
||||
m.MatchV3Ops(m.MatchV3SyncOp(0, 2, encryptedRoomIDs[:3])),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(unencryptedRoomIDs)),
|
||||
m.MatchV3Ops(m.MatchV3SyncOp(0, 2, unencryptedRoomIDs[:3])),
|
||||
}),
|
||||
m.MatchRoomSubscriptionsStrict(map[string][]m.RoomMatcher{
|
||||
encryptedRoomIDs[0]: {},
|
||||
encryptedRoomIDs[1]: {},
|
||||
encryptedRoomIDs[2]: {},
|
||||
unencryptedRoomIDs[0]: {},
|
||||
unencryptedRoomIDs[1]: {},
|
||||
unencryptedRoomIDs[2]: {},
|
||||
}),
|
||||
)
|
||||
|
||||
// now scroll one of the lists
|
||||
res = alice.SlidingSync(t, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms still
|
||||
},
|
||||
},
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
[2]int64{3, 5}, // next 3 rooms
|
||||
},
|
||||
},
|
||||
},
|
||||
}, WithPos(res.Pos))
|
||||
m.MatchResponse(t, res, m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(encryptedRoomIDs)),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(unencryptedRoomIDs)),
|
||||
m.MatchV3Ops(
|
||||
m.MatchV3SyncOp(3, 5, unencryptedRoomIDs[3:6]),
|
||||
),
|
||||
}), m.MatchRoomSubscriptionsStrict(map[string][]m.RoomMatcher{
|
||||
unencryptedRoomIDs[3]: {},
|
||||
unencryptedRoomIDs[4]: {},
|
||||
unencryptedRoomIDs[5]: {},
|
||||
}))
|
||||
|
||||
// now shift the last/oldest unencrypted room to an encrypted room and make sure both lists update
|
||||
alice.SendEventSynced(t, unencryptedRoomIDs[len(unencryptedRoomIDs)-1], NewEncryptionEvent())
|
||||
// update our source of truth: the last unencrypted room is now the first encrypted room
|
||||
encryptedRoomIDs = append([]string{unencryptedRoomIDs[len(unencryptedRoomIDs)-1]}, encryptedRoomIDs...)
|
||||
unencryptedRoomIDs = unencryptedRoomIDs[:len(unencryptedRoomIDs)-1]
|
||||
|
||||
// We are tracking the first few encrypted rooms so we expect list 0 to update
|
||||
// However we do not track old unencrypted rooms so we expect no change in list 1
|
||||
res = alice.SlidingSync(t, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms still
|
||||
},
|
||||
},
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
[2]int64{3, 5}, // next 3 rooms
|
||||
},
|
||||
},
|
||||
},
|
||||
}, WithPos(res.Pos))
|
||||
m.MatchResponse(t, res, m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(encryptedRoomIDs)),
|
||||
m.MatchV3Ops(
|
||||
m.MatchV3DeleteOp(2),
|
||||
m.MatchV3InsertOp(0, encryptedRoomIDs[0]),
|
||||
),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(unencryptedRoomIDs)),
|
||||
}))
|
||||
}
|
||||
|
||||
// Test that bumps only update a single list and not both. Regression test for when
|
||||
// DM rooms get bumped they appeared in the is_dm:false list.
|
||||
func TestMultipleListsDMUpdate(t *testing.T) {
|
||||
alice := registerNewUser(t)
|
||||
var dmRoomIDs []string
|
||||
var groupRoomIDs []string
|
||||
dmContent := map[string]interface{}{} // user_id -> [room_id]
|
||||
// make 5 group rooms and make 5 DMs rooms. Room 0 is most recent to ease checks
|
||||
for i := 0; i < 5; i++ {
|
||||
dmUserID := fmt.Sprintf("@dm_%d:synapse", i) // TODO: domain brittle
|
||||
groupRoomID := alice.CreateRoom(t, map[string]interface{}{
|
||||
"preset": "public_chat",
|
||||
})
|
||||
groupRoomIDs = append([]string{groupRoomID}, groupRoomIDs...) // push in array
|
||||
dmRoomID := alice.CreateRoom(t, map[string]interface{}{
|
||||
"preset": "trusted_private_chat",
|
||||
"is_direct": true,
|
||||
"invite": []string{dmUserID},
|
||||
})
|
||||
dmRoomIDs = append([]string{dmRoomID}, dmRoomIDs...) // push in array
|
||||
dmContent[dmUserID] = []string{dmRoomID}
|
||||
time.Sleep(time.Millisecond) // ensure timestamp changes
|
||||
}
|
||||
// set the account data
|
||||
alice.SetGlobalAccountData(t, "m.direct", dmContent)
|
||||
|
||||
// request 2 lists, one set DM, one set no DM
|
||||
res := alice.SlidingSync(t, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsDM: &boolTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsDM: &boolFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
m.MatchResponse(t, res, m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(dmRoomIDs)),
|
||||
m.MatchV3Ops(m.MatchV3SyncOp(0, 2, dmRoomIDs[:3])),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(groupRoomIDs)),
|
||||
m.MatchV3Ops(m.MatchV3SyncOp(0, 2, groupRoomIDs[:3])),
|
||||
}))
|
||||
|
||||
// now bring the last DM room to the top with a notif
|
||||
pingEventID := alice.SendEventSynced(t, dmRoomIDs[len(dmRoomIDs)-1], Event{
|
||||
Type: "m.room.message",
|
||||
Content: map[string]interface{}{"body": "ping", "msgtype": "m.text"},
|
||||
})
|
||||
// update our source of truth: swap the last and first elements
|
||||
dmRoomIDs[0], dmRoomIDs[len(dmRoomIDs)-1] = dmRoomIDs[len(dmRoomIDs)-1], dmRoomIDs[0]
|
||||
|
||||
// now get the delta: only the DM room should change
|
||||
res = alice.SlidingSync(t, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms still
|
||||
},
|
||||
},
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms still
|
||||
},
|
||||
},
|
||||
},
|
||||
}, WithPos(res.Pos))
|
||||
m.MatchResponse(t, res, m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(dmRoomIDs)),
|
||||
m.MatchV3Ops(
|
||||
m.MatchV3DeleteOp(2),
|
||||
m.MatchV3InsertOp(0, dmRoomIDs[0]),
|
||||
),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(groupRoomIDs)),
|
||||
}), m.MatchRoomSubscription(dmRoomIDs[0], MatchRoomTimelineMostRecent(1, []Event{
|
||||
{
|
||||
Type: "m.room.message",
|
||||
ID: pingEventID,
|
||||
},
|
||||
})))
|
||||
}
|
||||
|
||||
// Test that a new list can be added mid-connection
|
||||
func TestNewListMidConnection(t *testing.T) {
|
||||
alice := registerNewUser(t)
|
||||
var roomIDs []string
|
||||
// make rooms
|
||||
for i := 0; i < 4; i++ {
|
||||
roomID := alice.CreateRoom(t, map[string]interface{}{
|
||||
"preset": "public_chat",
|
||||
})
|
||||
roomIDs = append([]string{roomID}, roomIDs...) // push in array
|
||||
time.Sleep(time.Millisecond) // ensure timestamp changes
|
||||
}
|
||||
|
||||
// first request no list
|
||||
res := alice.SlidingSync(t, sync3.Request{
|
||||
Lists: []sync3.RequestList{},
|
||||
})
|
||||
m.MatchResponse(t, res, m.MatchLists())
|
||||
|
||||
// now add a list
|
||||
res = alice.SlidingSync(t, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, WithPos(res.Pos))
|
||||
m.MatchResponse(t, res, m.MatchList(0, m.MatchV3Count(len(roomIDs)), m.MatchV3Ops(
|
||||
m.MatchV3SyncOp(0, 2, roomIDs[:3]),
|
||||
)))
|
||||
}
|
||||
|
||||
// Tests that if a room appears in >1 list that we union room subscriptions correctly.
|
||||
func TestMultipleOverlappingLists(t *testing.T) {
|
||||
alice := registerNewUser(t)
|
||||
|
||||
var allRoomIDs []string
|
||||
var encryptedRoomIDs []string
|
||||
var dmRoomIDs []string
|
||||
dmContent := map[string]interface{}{} // user_id -> [room_id]
|
||||
dmUserID := "@bob:synapse"
|
||||
// make 3 encrypted rooms, 3 encrypted/dm rooms, 3 dm rooms.
|
||||
// [0] is the newest room.
|
||||
for i := 9; i >= 0; i-- {
|
||||
isEncrypted := i < 6
|
||||
isDM := i >= 3
|
||||
|
||||
createContent := map[string]interface{}{
|
||||
"preset": "private_chat",
|
||||
}
|
||||
if isEncrypted {
|
||||
createContent["initial_state"] = []Event{
|
||||
NewEncryptionEvent(),
|
||||
}
|
||||
}
|
||||
if isDM {
|
||||
createContent["is_direct"] = true
|
||||
createContent["invite"] = []string{dmUserID}
|
||||
}
|
||||
roomID := alice.CreateRoom(t, createContent)
|
||||
time.Sleep(time.Millisecond)
|
||||
if isDM {
|
||||
var roomIDs []string
|
||||
roomIDsInt, ok := dmContent[dmUserID]
|
||||
if ok {
|
||||
roomIDs = roomIDsInt.([]string)
|
||||
}
|
||||
dmContent[dmUserID] = append(roomIDs, roomID)
|
||||
dmRoomIDs = append([]string{roomID}, dmRoomIDs...)
|
||||
}
|
||||
if isEncrypted {
|
||||
encryptedRoomIDs = append([]string{roomID}, encryptedRoomIDs...)
|
||||
}
|
||||
allRoomIDs = append([]string{roomID}, allRoomIDs...) // push entries so [0] is newest
|
||||
}
|
||||
// set the account data
|
||||
t.Logf("DM rooms: %v", dmRoomIDs)
|
||||
t.Logf("Encrypted rooms: %v", encryptedRoomIDs)
|
||||
alice.SetGlobalAccountData(t, "m.direct", dmContent)
|
||||
|
||||
// seed the proxy: so we can get timeline correctly as it uses limit:1 initially.
|
||||
alice.SlidingSync(t, sync3.Request{})
|
||||
|
||||
// send messages to track timeline. The most recent messages are:
|
||||
// - ENCRYPTION EVENT (if set)
|
||||
// - DM INVITE EVENT (if set)
|
||||
// - This ping message (always)
|
||||
roomToEventID := make(map[string]string, len(allRoomIDs))
|
||||
for i := len(allRoomIDs) - 1; i >= 0; i-- {
|
||||
roomToEventID[allRoomIDs[i]] = alice.SendEventSynced(t, allRoomIDs[i], Event{
|
||||
Type: "m.room.message",
|
||||
Content: map[string]interface{}{"body": "ping", "msgtype": "m.text"},
|
||||
})
|
||||
}
|
||||
|
||||
// request 2 lists: one DMs, one encrypted. The room subscriptions are different so they should be UNION'd correctly.
|
||||
// We request 5 rooms to ensure there is some overlap but not total overlap:
|
||||
// newest top 5 DM
|
||||
// v .-----------.
|
||||
// E E E ED* ED* D D D D
|
||||
// `-----------`
|
||||
// top 5 Encrypted
|
||||
//
|
||||
// Rooms with * are union'd
|
||||
res := alice.SlidingSync(t, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 4}, // first 5 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 2, // pull in the ping msg + some state event depending on the room type
|
||||
RequiredState: [][2]string{
|
||||
{"m.room.join_rules", ""},
|
||||
},
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsEncrypted: &boolTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 4}, // first 5 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1, // pull in ping message only
|
||||
RequiredState: [][2]string{
|
||||
{"m.room.power_levels", ""},
|
||||
},
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsDM: &boolTrue,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
m.MatchResponse(t, res,
|
||||
m.MatchList(0, m.MatchV3Ops(m.MatchV3SyncOp(0, 4, encryptedRoomIDs[:5]))),
|
||||
m.MatchList(1, m.MatchV3Ops(m.MatchV3SyncOp(0, 4, dmRoomIDs[:5]))),
|
||||
m.MatchRoomSubscriptions(map[string][]m.RoomMatcher{
|
||||
// encrypted rooms just come from the encrypted only list
|
||||
encryptedRoomIDs[0]: {
|
||||
m.MatchRoomInitial(true),
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
Type: "m.room.join_rules",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
}),
|
||||
MatchRoomTimeline([]Event{
|
||||
{Type: "m.room.encryption", StateKey: ptr("")},
|
||||
{ID: roomToEventID[encryptedRoomIDs[0]]},
|
||||
}),
|
||||
},
|
||||
encryptedRoomIDs[1]: {
|
||||
m.MatchRoomInitial(true),
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
Type: "m.room.join_rules",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
}),
|
||||
MatchRoomTimeline([]Event{
|
||||
{Type: "m.room.encryption", StateKey: ptr("")},
|
||||
{ID: roomToEventID[encryptedRoomIDs[1]]},
|
||||
}),
|
||||
},
|
||||
encryptedRoomIDs[2]: {
|
||||
m.MatchRoomInitial(true),
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
Type: "m.room.join_rules",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
}),
|
||||
MatchRoomTimeline([]Event{
|
||||
{Type: "m.room.encryption", StateKey: ptr("")},
|
||||
{ID: roomToEventID[encryptedRoomIDs[2]]},
|
||||
}),
|
||||
},
|
||||
// overlapping with DM rooms
|
||||
encryptedRoomIDs[3]: {
|
||||
m.MatchRoomInitial(true),
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
Type: "m.room.join_rules",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
{
|
||||
Type: "m.room.power_levels",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
}),
|
||||
MatchRoomTimeline([]Event{
|
||||
{Type: "m.room.member", StateKey: ptr(dmUserID)},
|
||||
{ID: roomToEventID[encryptedRoomIDs[3]]},
|
||||
}),
|
||||
},
|
||||
encryptedRoomIDs[4]: {
|
||||
m.MatchRoomInitial(true),
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
Type: "m.room.join_rules",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
{
|
||||
Type: "m.room.power_levels",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
}),
|
||||
MatchRoomTimeline([]Event{
|
||||
{Type: "m.room.member", StateKey: ptr(dmUserID)},
|
||||
{ID: roomToEventID[encryptedRoomIDs[4]]},
|
||||
}),
|
||||
},
|
||||
// DM only rooms
|
||||
dmRoomIDs[2]: {
|
||||
m.MatchRoomInitial(true),
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
Type: "m.room.power_levels",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
}),
|
||||
MatchRoomTimelineMostRecent(1, []Event{{ID: roomToEventID[dmRoomIDs[2]]}}),
|
||||
},
|
||||
dmRoomIDs[3]: {
|
||||
m.MatchRoomInitial(true),
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
Type: "m.room.power_levels",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
}),
|
||||
MatchRoomTimelineMostRecent(1, []Event{{ID: roomToEventID[dmRoomIDs[3]]}}),
|
||||
},
|
||||
dmRoomIDs[4]: {
|
||||
m.MatchRoomInitial(true),
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
Type: "m.room.power_levels",
|
||||
StateKey: ptr(""),
|
||||
},
|
||||
}),
|
||||
MatchRoomTimelineMostRecent(1, []Event{{ID: roomToEventID[dmRoomIDs[4]]}}),
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
@ -3,6 +3,7 @@ package syncv3_test
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -33,6 +34,13 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func WithPos(pos string) RequestOpt {
|
||||
return WithQueries(url.Values{
|
||||
"pos": []string{pos},
|
||||
"timeout": []string{"500"}, // 0.5s
|
||||
})
|
||||
}
|
||||
|
||||
func assertEventsEqual(t *testing.T, wantList []Event, gotList []json.RawMessage) {
|
||||
t.Helper()
|
||||
err := eventsEqual(wantList, gotList)
|
||||
@ -77,11 +85,13 @@ func eventsEqual(wantList []Event, gotList []json.RawMessage) error {
|
||||
func MatchRoomTimelineMostRecent(n int, events []Event) m.RoomMatcher {
|
||||
subset := events[len(events)-n:]
|
||||
return func(r sync3.Room) error {
|
||||
if len(r.Timeline) < len(subset) {
|
||||
return fmt.Errorf("timeline length mismatch: got %d want at least %d", len(r.Timeline), len(subset))
|
||||
}
|
||||
gotSubset := r.Timeline[len(r.Timeline)-n:]
|
||||
return eventsEqual(events, gotSubset)
|
||||
return MatchRoomTimeline(subset)(r)
|
||||
}
|
||||
}
|
||||
|
||||
func MatchRoomTimeline(events []Event) m.RoomMatcher {
|
||||
return func(r sync3.Room) error {
|
||||
return eventsEqual(events, r.Timeline)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package syncv3_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/matrix-org/sync-v3/sync3"
|
||||
@ -82,10 +81,7 @@ func TestRoomStateTransitions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, WithQueries(url.Values{
|
||||
"timeout": []string{"500"},
|
||||
"pos": []string{bobRes.Pos},
|
||||
}))
|
||||
}, WithPos(bobRes.Pos))
|
||||
m.MatchResponse(t, bobRes, m.MatchNoV3Ops(), m.MatchList(0, m.MatchV3Count(2)), m.MatchRoomSubscription(inviteRoomID,
|
||||
MatchRoomRequiredState([]Event{
|
||||
{
|
||||
|
@ -2,7 +2,6 @@ package syncv3_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/matrix-org/sync-v3/sync3"
|
||||
@ -79,10 +78,7 @@ func TestSecurityLiveStreamEventLeftLeak(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}},
|
||||
}, WithQueries(url.Values{
|
||||
"timeout": []string{"500"}, // 0.5s
|
||||
"pos": []string{aliceRes.Pos},
|
||||
}))
|
||||
}, WithPos(aliceRes.Pos))
|
||||
|
||||
timeline := aliceRes.Rooms[roomID].Timeline
|
||||
lastTwoEvents := timeline[len(timeline)-2:]
|
||||
@ -125,10 +121,7 @@ func TestSecurityLiveStreamEventLeftLeak(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}},
|
||||
}, WithQueries(url.Values{
|
||||
"timeout": []string{"500"}, // 0.5s
|
||||
"pos": []string{eveRes.Pos},
|
||||
}))
|
||||
}, WithPos(eveRes.Pos))
|
||||
// the room is deleted from eve's point of view and she sees up to and including her kick event
|
||||
m.MatchResponse(t, eveRes, m.MatchList(0, m.MatchV3Count(0), m.MatchV3Ops(m.MatchV3DeleteOp(0))), m.MatchRoomSubscription(
|
||||
roomID, m.MatchRoomName(""), m.MatchRoomRequiredState(nil), m.MatchRoomTimelineMostRecent(1, []json.RawMessage{kickEvent}),
|
||||
@ -194,10 +187,7 @@ func TestSecurityRoomSubscriptionLeak(t *testing.T) {
|
||||
[2]int64{0, 10}, // doesn't matter
|
||||
},
|
||||
}},
|
||||
}, WithQueries(url.Values{
|
||||
"timeout": []string{"500"}, // 0.5s
|
||||
"pos": []string{eveRes.Pos},
|
||||
}))
|
||||
}, WithPos(eveRes.Pos))
|
||||
|
||||
// Assert that Eve doesn't see anything
|
||||
m.MatchResponse(t, eveRes, m.MatchList(0, m.MatchV3Count(1)), m.MatchNoV3Ops(), m.MatchRoomSubscriptionsStrict(map[string][]m.RoomMatcher{}))
|
||||
|
@ -1,571 +0,0 @@
|
||||
package syncv3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/sync-v3/sync2"
|
||||
"github.com/matrix-org/sync-v3/sync3"
|
||||
"github.com/matrix-org/sync-v3/testutils"
|
||||
"github.com/matrix-org/sync-v3/testutils/m"
|
||||
)
|
||||
|
||||
// Test that multiple lists can be independently scrolled through
|
||||
func TestMultipleLists(t *testing.T) {
|
||||
boolTrue := true
|
||||
boolFalse := false
|
||||
pqString := testutils.PrepareDBConnectionString()
|
||||
// setup code
|
||||
v2 := runTestV2Server(t)
|
||||
v3 := runTestServer(t, v2, pqString)
|
||||
defer v2.close()
|
||||
defer v3.close()
|
||||
var allRooms []roomEvents
|
||||
var encryptedRooms []roomEvents
|
||||
var unencryptedRooms []roomEvents
|
||||
baseTimestamp := time.Now()
|
||||
// make 10 encrypted rooms and make 10 unencrypted rooms. Room 0 is most recent to ease checks
|
||||
for i := 0; i < 10; i++ {
|
||||
ts := baseTimestamp.Add(time.Duration(-1*i) * time.Second)
|
||||
encRoom := roomEvents{
|
||||
roomID: fmt.Sprintf("!encrypted_%d:localhost", i),
|
||||
events: append(createRoomState(t, alice, ts), []json.RawMessage{
|
||||
testutils.NewStateEvent(
|
||||
t, "m.room.encryption", "", alice, map[string]interface{}{
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"rotation_period_ms": 604800000,
|
||||
"rotation_period_msgs": 100,
|
||||
}, testutils.WithTimestamp(ts),
|
||||
),
|
||||
}...),
|
||||
}
|
||||
room := roomEvents{
|
||||
roomID: fmt.Sprintf("!unencrypted_%d:localhost", i),
|
||||
events: createRoomState(t, alice, ts),
|
||||
}
|
||||
allRooms = append(allRooms, []roomEvents{encRoom, room}...)
|
||||
encryptedRooms = append(encryptedRooms, encRoom)
|
||||
unencryptedRooms = append(unencryptedRooms, room)
|
||||
}
|
||||
v2.addAccount(alice, aliceToken)
|
||||
v2.queueResponse(alice, sync2.SyncResponse{
|
||||
Rooms: sync2.SyncRoomsResponse{
|
||||
Join: v2JoinTimeline(allRooms...),
|
||||
},
|
||||
})
|
||||
|
||||
// request 2 lists, one set encrypted, one set unencrypted
|
||||
res := v3.mustDoV3Request(t, aliceToken, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsEncrypted: &boolTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsEncrypted: &boolFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
m.MatchResponse(t, res,
|
||||
m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(encryptedRooms)),
|
||||
m.MatchV3Ops(m.MatchV3SyncOpFn(func(op *sync3.ResponseOpRange) error {
|
||||
// first 3 encrypted rooms
|
||||
return checkRoomList(res, op, encryptedRooms[:3])
|
||||
}),
|
||||
),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(unencryptedRooms)),
|
||||
m.MatchV3Ops(m.MatchV3SyncOpFn(func(op *sync3.ResponseOpRange) error {
|
||||
// first 3 unencrypted rooms
|
||||
return checkRoomList(res, op, unencryptedRooms[:3])
|
||||
})),
|
||||
}),
|
||||
)
|
||||
|
||||
// now scroll one of the lists
|
||||
res = v3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms still
|
||||
},
|
||||
},
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
[2]int64{3, 5}, // next 3 rooms
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.MatchResponse(t, res, m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(encryptedRooms)),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(unencryptedRooms)),
|
||||
m.MatchV3Ops(
|
||||
m.MatchV3SyncOpFn(func(op *sync3.ResponseOpRange) error {
|
||||
return checkRoomList(res, op, unencryptedRooms[3:6])
|
||||
}),
|
||||
),
|
||||
}))
|
||||
|
||||
// now shift the last/oldest unencrypted room to an encrypted room and make sure both lists update
|
||||
v2.queueResponse(alice, sync2.SyncResponse{
|
||||
Rooms: sync2.SyncRoomsResponse{
|
||||
Join: map[string]sync2.SyncV2JoinResponse{
|
||||
unencryptedRooms[len(unencryptedRooms)-1].roomID: {
|
||||
Timeline: sync2.TimelineResponse{
|
||||
Events: []json.RawMessage{
|
||||
testutils.NewStateEvent(
|
||||
t, "m.room.encryption", "", alice, map[string]interface{}{
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"rotation_period_ms": 604800000,
|
||||
"rotation_period_msgs": 100,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
v2.waitUntilEmpty(t, alice)
|
||||
// update our source of truth: the last unencrypted room is now the first encrypted room
|
||||
encryptedRooms = append([]roomEvents{unencryptedRooms[len(unencryptedRooms)-1]}, encryptedRooms...)
|
||||
unencryptedRooms = unencryptedRooms[:len(unencryptedRooms)-1]
|
||||
|
||||
res = v3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms still
|
||||
},
|
||||
},
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
[2]int64{3, 5}, // next 3 rooms
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
// We are tracking the first few encrypted rooms so we expect list 0 to update
|
||||
// However we do not track old unencrypted rooms so we expect no change in list 1
|
||||
m.MatchResponse(t, res, m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(encryptedRooms)),
|
||||
m.MatchV3Ops(
|
||||
m.MatchV3DeleteOp(2),
|
||||
m.MatchV3InsertOp(0, encryptedRooms[0].roomID),
|
||||
),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(unencryptedRooms)),
|
||||
}))
|
||||
}
|
||||
|
||||
// Test that highlights / bumps only update a single list and not both. Regression test for when
|
||||
// DM rooms get bumped they appeared in the is_dm:false list.
|
||||
func TestMultipleListsDMUpdate(t *testing.T) {
|
||||
boolTrue := true
|
||||
boolFalse := false
|
||||
one := 1
|
||||
pqString := testutils.PrepareDBConnectionString()
|
||||
// setup code
|
||||
v2 := runTestV2Server(t)
|
||||
v3 := runTestServer(t, v2, pqString)
|
||||
defer v2.close()
|
||||
defer v3.close()
|
||||
var allRooms []roomEvents
|
||||
var dmRooms []roomEvents
|
||||
var groupRooms []roomEvents
|
||||
baseTimestamp := time.Now()
|
||||
dmContent := map[string][]string{} // user_id -> [room_id]
|
||||
// make 10 group rooms and make 10 DMs rooms. Room 0 is most recent to ease checks
|
||||
for i := 0; i < 10; i++ {
|
||||
ts := baseTimestamp.Add(time.Duration(-1*i) * time.Second)
|
||||
dmUser := fmt.Sprintf("@dm_%d:localhost", i)
|
||||
dmRoomID := fmt.Sprintf("!dm_%d:localhost", i)
|
||||
dmRoom := roomEvents{
|
||||
roomID: dmRoomID,
|
||||
events: append(createRoomState(t, alice, ts), []json.RawMessage{
|
||||
testutils.NewJoinEvent(
|
||||
t, dmUser, testutils.WithTimestamp(ts),
|
||||
),
|
||||
}...),
|
||||
}
|
||||
groupRoom := roomEvents{
|
||||
roomID: fmt.Sprintf("!group_%d:localhost", i),
|
||||
events: createRoomState(t, alice, ts),
|
||||
}
|
||||
allRooms = append(allRooms, []roomEvents{dmRoom, groupRoom}...)
|
||||
dmRooms = append(dmRooms, dmRoom)
|
||||
groupRooms = append(groupRooms, groupRoom)
|
||||
dmContent[dmUser] = []string{dmRoomID}
|
||||
}
|
||||
v2.addAccount(alice, aliceToken)
|
||||
v2.queueResponse(alice, sync2.SyncResponse{
|
||||
AccountData: sync2.EventsResponse{
|
||||
Events: []json.RawMessage{
|
||||
testutils.NewEvent(t, "m.direct", alice, dmContent),
|
||||
},
|
||||
},
|
||||
Rooms: sync2.SyncRoomsResponse{
|
||||
Join: v2JoinTimeline(allRooms...),
|
||||
},
|
||||
})
|
||||
|
||||
// request 2 lists, one set DM, one set no DM
|
||||
res := v3.mustDoV3Request(t, aliceToken, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsDM: &boolTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsDM: &boolFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
m.MatchResponse(t, res, m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(dmRooms)),
|
||||
m.MatchV3Ops(m.MatchV3SyncOpFn(func(op *sync3.ResponseOpRange) error {
|
||||
// first 3 DM rooms
|
||||
return checkRoomList(res, op, dmRooms[:3])
|
||||
})),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(groupRooms)),
|
||||
m.MatchV3Ops(m.MatchV3SyncOpFn(func(op *sync3.ResponseOpRange) error {
|
||||
// first 3 group rooms
|
||||
return checkRoomList(res, op, groupRooms[:3])
|
||||
})),
|
||||
}))
|
||||
|
||||
// now bring the last DM room to the top with a notif
|
||||
pingMessage := testutils.NewEvent(t, "m.room.message", alice, map[string]interface{}{"body": "ping"})
|
||||
v2.queueResponse(alice, sync2.SyncResponse{
|
||||
Rooms: sync2.SyncRoomsResponse{
|
||||
Join: map[string]sync2.SyncV2JoinResponse{
|
||||
dmRooms[len(dmRooms)-1].roomID: {
|
||||
UnreadNotifications: sync2.UnreadNotifications{
|
||||
HighlightCount: &one,
|
||||
},
|
||||
Timeline: sync2.TimelineResponse{
|
||||
Events: []json.RawMessage{
|
||||
pingMessage,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
v2.waitUntilEmpty(t, alice)
|
||||
// update our source of truth
|
||||
dmRooms = append([]roomEvents{dmRooms[len(dmRooms)-1]}, dmRooms[1:]...)
|
||||
dmRooms[0].events = append(dmRooms[0].events, pingMessage)
|
||||
|
||||
// now get the delta: only the DM room should change
|
||||
res = v3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms still
|
||||
},
|
||||
},
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms still
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.MatchResponse(t, res, m.MatchLists([]m.ListMatcher{
|
||||
m.MatchV3Count(len(dmRooms)),
|
||||
m.MatchV3Ops(
|
||||
m.MatchV3DeleteOp(2),
|
||||
m.MatchV3InsertOp(0, dmRooms[0].roomID),
|
||||
),
|
||||
}, []m.ListMatcher{
|
||||
m.MatchV3Count(len(groupRooms)),
|
||||
}), m.MatchRoomSubscription(dmRooms[0].roomID, m.MatchRoomHighlightCount(1), m.MatchRoomTimelineMostRecent(1, dmRooms[0].events)))
|
||||
}
|
||||
|
||||
// Test that a new list can be added mid-connection
|
||||
func TestNewListMidConnection(t *testing.T) {
|
||||
pqString := testutils.PrepareDBConnectionString()
|
||||
// setup code
|
||||
v2 := runTestV2Server(t)
|
||||
v3 := runTestServer(t, v2, pqString)
|
||||
defer v2.close()
|
||||
defer v3.close()
|
||||
var allRooms []roomEvents
|
||||
baseTimestamp := time.Now()
|
||||
// make 10 rooms
|
||||
for i := 0; i < 10; i++ {
|
||||
ts := baseTimestamp.Add(time.Duration(-1*i) * time.Second)
|
||||
room := roomEvents{
|
||||
roomID: fmt.Sprintf("!%d:localhost", i),
|
||||
events: createRoomState(t, alice, ts),
|
||||
}
|
||||
allRooms = append(allRooms, room)
|
||||
}
|
||||
v2.addAccount(alice, aliceToken)
|
||||
v2.queueResponse(alice, sync2.SyncResponse{
|
||||
Rooms: sync2.SyncRoomsResponse{
|
||||
Join: v2JoinTimeline(allRooms...),
|
||||
},
|
||||
})
|
||||
|
||||
// first request no list
|
||||
res := v3.mustDoV3Request(t, aliceToken, sync3.Request{
|
||||
Lists: []sync3.RequestList{},
|
||||
})
|
||||
|
||||
m.MatchResponse(t, res, m.MatchLists())
|
||||
|
||||
// now add a list
|
||||
res = v3.mustDoV3RequestWithPos(t, aliceToken, res.Pos, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 2}, // first 3 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.MatchResponse(t, res, m.MatchList(0, m.MatchV3Count(len(allRooms)), m.MatchV3Ops(
|
||||
m.MatchV3SyncOpFn(func(op *sync3.ResponseOpRange) error {
|
||||
return checkRoomList(res, op, allRooms[0:3])
|
||||
}),
|
||||
)))
|
||||
}
|
||||
|
||||
// Tests that if a room appears in >1 list that we union room subscriptions correctly.
|
||||
func TestMultipleOverlappingLists(t *testing.T) {
|
||||
boolTrue := true
|
||||
pqString := testutils.PrepareDBConnectionString()
|
||||
// setup code
|
||||
v2 := runTestV2Server(t)
|
||||
v3 := runTestServer(t, v2, pqString)
|
||||
defer v2.close()
|
||||
defer v3.close()
|
||||
var allRooms []roomEvents
|
||||
var encryptedRooms []roomEvents
|
||||
var encryptedRoomIDs []string
|
||||
var dmRooms []roomEvents
|
||||
var dmRoomIDs []string
|
||||
dmContent := map[string][]string{} // user_id -> [room_id]
|
||||
dmUser := bob
|
||||
baseTimestamp := time.Now()
|
||||
// make 3 encrypted rooms, 3 encrypted/dm rooms, 3 dm rooms.
|
||||
for i := 0; i < 9; i++ {
|
||||
isEncrypted := i < 6
|
||||
isDM := i >= 3
|
||||
ts := baseTimestamp.Add(time.Duration(-1*i) * time.Second)
|
||||
room := roomEvents{
|
||||
roomID: fmt.Sprintf("!room_%d:localhost", i),
|
||||
events: createRoomState(t, alice, ts),
|
||||
}
|
||||
if isEncrypted {
|
||||
room.events = append(room.events, testutils.NewStateEvent(
|
||||
t, "m.room.encryption", "", alice, map[string]interface{}{
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"rotation_period_ms": 604800000,
|
||||
"rotation_period_msgs": 100,
|
||||
}, testutils.WithTimestamp(ts),
|
||||
))
|
||||
}
|
||||
if isDM {
|
||||
dmContent[dmUser] = append(dmContent[dmUser], room.roomID)
|
||||
}
|
||||
allRooms = append(allRooms, room)
|
||||
if isEncrypted {
|
||||
encryptedRooms = append(encryptedRooms, room)
|
||||
encryptedRoomIDs = append(encryptedRoomIDs, room.roomID)
|
||||
}
|
||||
if isDM {
|
||||
dmRooms = append(dmRooms, room)
|
||||
dmRoomIDs = append(dmRoomIDs, room.roomID)
|
||||
}
|
||||
}
|
||||
v2.addAccount(alice, aliceToken)
|
||||
v2.queueResponse(alice, sync2.SyncResponse{
|
||||
AccountData: sync2.EventsResponse{
|
||||
Events: []json.RawMessage{
|
||||
testutils.NewEvent(t, "m.direct", alice, dmContent),
|
||||
},
|
||||
},
|
||||
Rooms: sync2.SyncRoomsResponse{
|
||||
Join: v2JoinTimeline(allRooms...),
|
||||
},
|
||||
})
|
||||
// request 2 lists: one DMs, one encrypted. The room subscriptions are different so they should be UNION'd correctly.
|
||||
// We request 5 rooms to ensure there is some overlap but not total overlap:
|
||||
// newest top 5 DM
|
||||
// v .-------------.
|
||||
// E E E ED* ED* ED* D D D
|
||||
// `-----------`
|
||||
// top 5 Encrypted
|
||||
//
|
||||
// Rooms with * are union'd
|
||||
res := v3.mustDoV3Request(t, aliceToken, sync3.Request{
|
||||
Lists: []sync3.RequestList{
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 4}, // first 5 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 3,
|
||||
RequiredState: [][2]string{
|
||||
{"m.room.join_rules", ""},
|
||||
},
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsEncrypted: &boolTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
Sort: []string{sync3.SortByRecency},
|
||||
Ranges: sync3.SliceRanges{
|
||||
[2]int64{0, 4}, // first 5 rooms
|
||||
},
|
||||
RoomSubscription: sync3.RoomSubscription{
|
||||
TimelineLimit: 1,
|
||||
RequiredState: [][2]string{
|
||||
{"m.room.power_levels", ""},
|
||||
},
|
||||
},
|
||||
Filters: &sync3.RequestFilters{
|
||||
IsDM: &boolTrue,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
m.MatchResponse(t, res,
|
||||
m.MatchList(0, m.MatchV3Ops(m.MatchV3SyncOp(0, 4, encryptedRoomIDs[:5]))),
|
||||
m.MatchList(1, m.MatchV3Ops(m.MatchV3SyncOp(0, 4, dmRoomIDs[:5]))),
|
||||
m.MatchRoomSubscriptions(map[string][]m.RoomMatcher{
|
||||
// encrypted rooms just come from the encrypted only list
|
||||
encryptedRoomIDs[0]: {
|
||||
m.MatchRoomInitial(true),
|
||||
m.MatchRoomRequiredState([]json.RawMessage{
|
||||
encryptedRooms[0].getStateEvent("m.room.join_rules", ""),
|
||||
}),
|
||||
m.MatchRoomTimelineMostRecent(3, encryptedRooms[0].events),
|
||||
},
|
||||
encryptedRoomIDs[1]: {
|
||||
m.MatchRoomInitial(true),
|
||||
m.MatchRoomRequiredState([]json.RawMessage{
|
||||
encryptedRooms[1].getStateEvent("m.room.join_rules", ""),
|
||||
}),
|
||||
m.MatchRoomTimelineMostRecent(3, encryptedRooms[1].events),
|
||||
},
|
||||
encryptedRoomIDs[2]: {
|
||||
m.MatchRoomInitial(true),
|
||||
m.MatchRoomRequiredState([]json.RawMessage{
|
||||
encryptedRooms[2].getStateEvent("m.room.join_rules", ""),
|
||||
}),
|
||||
m.MatchRoomTimelineMostRecent(3, encryptedRooms[2].events),
|
||||
},
|
||||
// overlapping with DM rooms
|
||||
encryptedRoomIDs[3]: {
|
||||
m.MatchRoomInitial(true),
|
||||
m.MatchRoomRequiredState([]json.RawMessage{
|
||||
encryptedRooms[3].getStateEvent("m.room.join_rules", ""),
|
||||
encryptedRooms[3].getStateEvent("m.room.power_levels", ""),
|
||||
}),
|
||||
m.MatchRoomTimelineMostRecent(3, encryptedRooms[3].events),
|
||||
},
|
||||
encryptedRoomIDs[4]: {
|
||||
m.MatchRoomInitial(true),
|
||||
m.MatchRoomRequiredState([]json.RawMessage{
|
||||
encryptedRooms[4].getStateEvent("m.room.join_rules", ""),
|
||||
encryptedRooms[4].getStateEvent("m.room.power_levels", ""),
|
||||
}),
|
||||
m.MatchRoomTimelineMostRecent(3, encryptedRooms[4].events),
|
||||
},
|
||||
// DM only rooms
|
||||
dmRoomIDs[2]: {
|
||||
m.MatchRoomInitial(true),
|
||||
m.MatchRoomRequiredState([]json.RawMessage{
|
||||
dmRooms[2].getStateEvent("m.room.power_levels", ""),
|
||||
}),
|
||||
m.MatchRoomTimelineMostRecent(1, dmRooms[2].events),
|
||||
},
|
||||
dmRoomIDs[3]: {
|
||||
m.MatchRoomInitial(true),
|
||||
m.MatchRoomRequiredState([]json.RawMessage{
|
||||
dmRooms[3].getStateEvent("m.room.power_levels", ""),
|
||||
}),
|
||||
m.MatchRoomTimelineMostRecent(1, dmRooms[3].events),
|
||||
},
|
||||
dmRoomIDs[4]: {
|
||||
m.MatchRoomInitial(true),
|
||||
m.MatchRoomRequiredState([]json.RawMessage{
|
||||
dmRooms[4].getStateEvent("m.room.power_levels", ""),
|
||||
}),
|
||||
m.MatchRoomTimelineMostRecent(1, dmRooms[4].events),
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// Check that the range op matches all the wantRooms
|
||||
func checkRoomList(res *sync3.Response, op *sync3.ResponseOpRange, wantRooms []roomEvents) error {
|
||||
if len(op.RoomIDs) != len(wantRooms) {
|
||||
return fmt.Errorf("want %d rooms, got %d", len(wantRooms), len(op.RoomIDs))
|
||||
}
|
||||
for i := range wantRooms {
|
||||
err := wantRooms[i].MatchRoom(op.RoomIDs[i],
|
||||
res.Rooms[op.RoomIDs[i]],
|
||||
m.MatchRoomTimelineMostRecent(1, wantRooms[i].events),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -378,10 +378,10 @@ func MatchAccountData(globals []json.RawMessage, rooms map[string][]json.RawMess
|
||||
}
|
||||
}
|
||||
|
||||
func CheckList(res sync3.ResponseList, matchers ...ListMatcher) error {
|
||||
func CheckList(i int, res sync3.ResponseList, matchers ...ListMatcher) error {
|
||||
for _, m := range matchers {
|
||||
if err := m(res); err != nil {
|
||||
return fmt.Errorf("MatchList: %v", err)
|
||||
return fmt.Errorf("MatchList[%d]: %v", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -393,7 +393,7 @@ func MatchList(i int, matchers ...ListMatcher) RespMatcher {
|
||||
return fmt.Errorf("MatchSingleList: index %d does not exist, got %d lists", i, len(res.Lists))
|
||||
}
|
||||
list := res.Lists[i]
|
||||
return CheckList(list, matchers...)
|
||||
return CheckList(i, list, matchers...)
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,7 +403,7 @@ func MatchLists(matchers ...[]ListMatcher) RespMatcher {
|
||||
return fmt.Errorf("MatchLists: got %d matchers for %d lists", len(matchers), len(res.Lists))
|
||||
}
|
||||
for i := range matchers {
|
||||
if err := CheckList(res.Lists[i], matchers[i]...); err != nil {
|
||||
if err := CheckList(i, res.Lists[i], matchers[i]...); err != nil {
|
||||
return fmt.Errorf("MatchLists[%d]: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user