2022-07-26 17:54:58 +01:00
package syncv3_test
import (
"fmt"
2022-08-11 15:07:36 +01:00
"sync"
2022-07-26 17:54:58 +01:00
"testing"
"time"
2023-10-11 12:23:46 +01:00
"github.com/matrix-org/complement/b"
"github.com/matrix-org/sliding-sync/sync3/extensions"
"github.com/tidwall/gjson"
2022-12-15 11:08:50 +00:00
"github.com/matrix-org/sliding-sync/sync3"
"github.com/matrix-org/sliding-sync/testutils/m"
2022-07-26 17:54:58 +01:00
)
// 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 ++ {
2023-10-11 12:23:46 +01:00
unencryptedRoomID := alice . MustCreateRoom ( t , map [ string ] interface { } {
2022-07-26 17:54:58 +01:00
"preset" : "public_chat" ,
} )
unencryptedRoomIDs = append ( [ ] string { unencryptedRoomID } , unencryptedRoomIDs ... ) // push in array
2023-10-11 12:23:46 +01:00
encryptedRoomID := alice . MustCreateRoom ( t , map [ string ] interface { } {
2022-07-26 17:54:58 +01:00
"preset" : "public_chat" ,
2023-10-11 12:23:46 +01:00
"initial_state" : [ ] b . Event {
2022-07-26 17:54:58 +01:00
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 {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"enc" : {
2022-07-26 17:54:58 +01:00
Sort : [ ] string { sync3 . SortByRecency } ,
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms
} ,
RoomSubscription : sync3 . RoomSubscription {
TimelineLimit : 1 ,
} ,
Filters : & sync3 . RequestFilters {
IsEncrypted : & boolTrue ,
} ,
} ,
2022-12-20 13:32:39 +00:00
"unenc" : {
2022-07-26 17:54:58 +01:00
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 ,
2022-12-20 13:32:39 +00:00
m . MatchLists ( map [ string ] [ ] m . ListMatcher {
"enc" : {
m . MatchV3Count ( len ( encryptedRoomIDs ) ) ,
m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 2 , encryptedRoomIDs [ : 3 ] ) ) ,
} ,
"unenc" : {
m . MatchV3Count ( len ( unencryptedRoomIDs ) ) ,
m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 2 , unencryptedRoomIDs [ : 3 ] ) ) ,
} ,
2022-07-26 17:54:58 +01:00
} ) ,
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 {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"enc" : {
2022-07-26 17:54:58 +01:00
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms still
} ,
} ,
2022-12-20 13:32:39 +00:00
"unenc" : {
2022-07-26 17:54:58 +01:00
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms
[ 2 ] int64 { 3 , 5 } , // next 3 rooms
} ,
} ,
} ,
} , WithPos ( res . Pos ) )
2022-12-20 13:32:39 +00:00
m . MatchResponse ( t , res , m . MatchLists ( map [ string ] [ ] m . ListMatcher {
"enc" : {
m . MatchV3Count ( len ( encryptedRoomIDs ) ) ,
} ,
"unenc" : {
m . MatchV3Count ( len ( unencryptedRoomIDs ) ) ,
m . MatchV3Ops (
m . MatchV3SyncOp ( 3 , 5 , unencryptedRoomIDs [ 3 : 6 ] ) ,
) ,
} ,
2022-07-26 17:54:58 +01:00
} ) , 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 {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"enc" : {
2022-07-26 17:54:58 +01:00
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms still
} ,
} ,
2022-12-20 13:32:39 +00:00
"unenc" : {
2022-07-26 17:54:58 +01:00
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms
[ 2 ] int64 { 3 , 5 } , // next 3 rooms
} ,
} ,
} ,
} , WithPos ( res . Pos ) )
2022-12-20 13:32:39 +00:00
m . MatchResponse ( t , res , m . MatchLists ( map [ string ] [ ] m . ListMatcher {
"enc" : {
m . MatchV3Count ( len ( encryptedRoomIDs ) ) ,
m . MatchV3Ops (
m . MatchV3DeleteOp ( 2 ) ,
m . MatchV3InsertOp ( 0 , encryptedRoomIDs [ 0 ] ) ,
) ,
} ,
"unenc" : {
m . MatchV3Count ( len ( unencryptedRoomIDs ) ) ,
} ,
2022-07-26 17:54:58 +01:00
} ) )
}
// 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
2023-10-11 12:23:46 +01:00
groupRoomID := alice . MustCreateRoom ( t , map [ string ] interface { } {
2022-07-26 17:54:58 +01:00
"preset" : "public_chat" ,
} )
groupRoomIDs = append ( [ ] string { groupRoomID } , groupRoomIDs ... ) // push in array
2023-10-11 12:23:46 +01:00
dmRoomID := alice . MustCreateRoom ( t , map [ string ] interface { } {
2022-07-26 17:54:58 +01:00
"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
2023-10-11 12:23:46 +01:00
alice . MustSetGlobalAccountData ( t , "m.direct" , dmContent )
2022-07-26 17:54:58 +01:00
// request 2 lists, one set DM, one set no DM
res := alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"dm" : {
2022-07-26 17:54:58 +01:00
Sort : [ ] string { sync3 . SortByRecency } ,
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms
} ,
RoomSubscription : sync3 . RoomSubscription {
TimelineLimit : 1 ,
} ,
Filters : & sync3 . RequestFilters {
IsDM : & boolTrue ,
} ,
} ,
2022-12-20 13:32:39 +00:00
"nodm" : {
2022-07-26 17:54:58 +01:00
Sort : [ ] string { sync3 . SortByRecency } ,
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms
} ,
RoomSubscription : sync3 . RoomSubscription {
TimelineLimit : 1 ,
} ,
Filters : & sync3 . RequestFilters {
IsDM : & boolFalse ,
} ,
} ,
} ,
} )
2022-12-20 13:32:39 +00:00
m . MatchResponse ( t , res , m . MatchLists ( map [ string ] [ ] m . ListMatcher {
"dm" : {
m . MatchV3Count ( len ( dmRoomIDs ) ) ,
m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 2 , dmRoomIDs [ : 3 ] ) ) ,
} ,
"nodm" : {
m . MatchV3Count ( len ( groupRoomIDs ) ) ,
m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 2 , groupRoomIDs [ : 3 ] ) ) ,
} ,
2022-07-26 17:54:58 +01:00
} ) )
// now bring the last DM room to the top with a notif
2023-10-11 12:23:46 +01:00
pingEventID := alice . SendEventSynced ( t , dmRoomIDs [ len ( dmRoomIDs ) - 1 ] , b . Event {
2022-07-26 17:54:58 +01:00
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 {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"dm" : {
2022-07-26 17:54:58 +01:00
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms still
} ,
} ,
2022-12-20 13:32:39 +00:00
"nodm" : {
2022-07-26 17:54:58 +01:00
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms still
} ,
} ,
} ,
} , WithPos ( res . Pos ) )
2022-12-20 13:32:39 +00:00
m . MatchResponse ( t , res , m . MatchLists ( map [ string ] [ ] m . ListMatcher {
"dm" : {
m . MatchV3Count ( len ( dmRoomIDs ) ) ,
m . MatchV3Ops (
m . MatchV3DeleteOp ( 2 ) ,
m . MatchV3InsertOp ( 0 , dmRoomIDs [ 0 ] ) ,
) ,
} ,
"nodm" : {
m . MatchV3Count ( len ( groupRoomIDs ) ) ,
} ,
2022-07-26 17:54:58 +01:00
} ) , 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 ++ {
2023-10-11 12:23:46 +01:00
roomID := alice . MustCreateRoom ( t , map [ string ] interface { } {
2022-07-26 17:54:58 +01:00
"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 {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList { } ,
2022-07-26 17:54:58 +01:00
} )
2022-12-20 13:32:39 +00:00
m . MatchResponse ( t , res , m . MatchLists ( nil ) )
2022-07-26 17:54:58 +01:00
// now add a list
res = alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-07-26 17:54:58 +01:00
Ranges : sync3 . SliceRanges {
[ 2 ] int64 { 0 , 2 } , // first 3 rooms
} ,
RoomSubscription : sync3 . RoomSubscription {
TimelineLimit : 1 ,
} ,
} ,
} ,
} , WithPos ( res . Pos ) )
2022-12-20 13:32:39 +00:00
m . MatchResponse ( t , res , m . MatchList ( "a" , m . MatchV3Count ( len ( roomIDs ) ) , m . MatchV3Ops (
2022-07-26 17:54:58 +01:00
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 {
2023-10-11 12:23:46 +01:00
createContent [ "initial_state" ] = [ ] b . Event {
2022-07-26 17:54:58 +01:00
NewEncryptionEvent ( ) ,
}
}
if isDM {
createContent [ "is_direct" ] = true
createContent [ "invite" ] = [ ] string { dmUserID }
}
2023-10-11 12:23:46 +01:00
roomID := alice . MustCreateRoom ( t , createContent )
2022-07-26 17:54:58 +01:00
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 )
2023-10-11 12:23:46 +01:00
alice . MustSetGlobalAccountData ( t , "m.direct" , dmContent )
2022-07-26 17:54:58 +01:00
// 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 -- {
2023-10-11 12:23:46 +01:00
roomToEventID [ allRoomIDs [ i ] ] = alice . SendEventSynced ( t , allRoomIDs [ i ] , b . Event {
2022-07-26 17:54:58 +01:00
Type : "m.room.message" ,
Content : map [ string ] interface { } { "body" : "ping" , "msgtype" : "m.text" } ,
} )
}
2022-12-14 18:53:55 +00:00
lastEventID := roomToEventID [ allRoomIDs [ 0 ] ]
alice . SlidingSyncUntilEventID ( t , "" , allRoomIDs [ 0 ] , lastEventID )
2022-07-26 17:54:58 +01:00
// 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 {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"enc" : {
2022-07-26 17:54:58 +01:00
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 ,
} ,
} ,
2022-12-20 13:32:39 +00:00
"dm" : {
2022-07-26 17:54:58 +01:00
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 ,
2022-12-20 13:32:39 +00:00
m . MatchList ( "enc" , m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 4 , encryptedRoomIDs [ : 5 ] ) ) ) ,
m . MatchList ( "dm" , m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 4 , dmRoomIDs [ : 5 ] ) ) ) ,
2022-07-26 17:54:58 +01:00
m . MatchRoomSubscriptions ( map [ string ] [ ] m . RoomMatcher {
// encrypted rooms just come from the encrypted only list
encryptedRoomIDs [ 0 ] : {
m . MatchRoomInitial ( true ) ,
2023-05-23 17:29:14 +01:00
MatchRoomRequiredStateStrict ( [ ] Event {
2022-07-26 17:54:58 +01:00
{
Type : "m.room.join_rules" ,
StateKey : ptr ( "" ) ,
} ,
} ) ,
MatchRoomTimeline ( [ ] Event {
{ Type : "m.room.encryption" , StateKey : ptr ( "" ) } ,
{ ID : roomToEventID [ encryptedRoomIDs [ 0 ] ] } ,
} ) ,
} ,
encryptedRoomIDs [ 1 ] : {
m . MatchRoomInitial ( true ) ,
2023-05-23 17:29:14 +01:00
MatchRoomRequiredStateStrict ( [ ] Event {
2022-07-26 17:54:58 +01:00
{
Type : "m.room.join_rules" ,
StateKey : ptr ( "" ) ,
} ,
} ) ,
MatchRoomTimeline ( [ ] Event {
{ Type : "m.room.encryption" , StateKey : ptr ( "" ) } ,
{ ID : roomToEventID [ encryptedRoomIDs [ 1 ] ] } ,
} ) ,
} ,
encryptedRoomIDs [ 2 ] : {
m . MatchRoomInitial ( true ) ,
2023-05-23 17:29:14 +01:00
MatchRoomRequiredStateStrict ( [ ] Event {
2022-07-26 17:54:58 +01:00
{
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 ) ,
2023-05-23 17:29:14 +01:00
MatchRoomRequiredStateStrict ( [ ] Event {
2022-07-26 17:54:58 +01:00
{
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 ) ,
2023-05-23 17:29:14 +01:00
MatchRoomRequiredStateStrict ( [ ] Event {
2022-07-26 17:54:58 +01:00
{
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 ) ,
2023-05-23 17:29:14 +01:00
MatchRoomRequiredStateStrict ( [ ] Event {
2022-07-26 17:54:58 +01:00
{
Type : "m.room.power_levels" ,
StateKey : ptr ( "" ) ,
} ,
} ) ,
MatchRoomTimelineMostRecent ( 1 , [ ] Event { { ID : roomToEventID [ dmRoomIDs [ 2 ] ] } } ) ,
} ,
dmRoomIDs [ 3 ] : {
m . MatchRoomInitial ( true ) ,
2023-05-23 17:29:14 +01:00
MatchRoomRequiredStateStrict ( [ ] Event {
2022-07-26 17:54:58 +01:00
{
Type : "m.room.power_levels" ,
StateKey : ptr ( "" ) ,
} ,
} ) ,
MatchRoomTimelineMostRecent ( 1 , [ ] Event { { ID : roomToEventID [ dmRoomIDs [ 3 ] ] } } ) ,
} ,
dmRoomIDs [ 4 ] : {
m . MatchRoomInitial ( true ) ,
2023-05-23 17:29:14 +01:00
MatchRoomRequiredStateStrict ( [ ] Event {
2022-07-26 17:54:58 +01:00
{
Type : "m.room.power_levels" ,
StateKey : ptr ( "" ) ,
} ,
} ) ,
MatchRoomTimelineMostRecent ( 1 , [ ] Event { { ID : roomToEventID [ dmRoomIDs [ 4 ] ] } } ) ,
} ,
} ) ,
)
}
2022-08-10 19:48:03 +01:00
// Regression test for a panic when new rooms were live-streamed to the client in Element-Web
func TestNot500OnNewRooms ( t * testing . T ) {
boolTrue := true
boolFalse := false
mSpace := "m.space"
alice := registerNewUser ( t )
res := alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-10 19:48:03 +01:00
SlowGetAllRooms : & boolTrue ,
Filters : & sync3 . RequestFilters {
RoomTypes : [ ] * string { & mSpace } ,
} ,
} ,
} ,
} )
2023-10-11 12:23:46 +01:00
alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" } )
2022-08-10 19:48:03 +01:00
alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-10 19:48:03 +01:00
SlowGetAllRooms : & boolTrue ,
Filters : & sync3 . RequestFilters {
RoomTypes : [ ] * string { & mSpace } ,
} ,
} ,
2022-12-20 13:32:39 +00:00
"b" : {
2022-08-10 19:48:03 +01:00
Filters : & sync3 . RequestFilters {
IsDM : & boolFalse ,
} ,
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
} ,
} ,
} , WithPos ( res . Pos ) )
2023-10-11 12:23:46 +01:00
alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" } )
2022-12-20 13:32:39 +00:00
// should not 500
2022-08-10 19:48:03 +01:00
alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-10 19:48:03 +01:00
SlowGetAllRooms : & boolTrue ,
} ,
2022-12-20 13:32:39 +00:00
"b" : {
2022-08-10 19:48:03 +01:00
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
} ,
} ,
} , WithPos ( res . Pos ) )
}
2022-08-11 15:07:36 +01:00
// Regression test for room name calculations, which could be incorrect for new rooms due to caches
// not being populated yet e.g "and 1 others" or "Empty room" when a room name has been set.
func TestNewRoomNameCalculations ( t * testing . T ) {
alice := registerNewUser ( t )
res := alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-11 15:07:36 +01:00
SlowGetAllRooms : & boolTrue ,
} ,
} ,
} )
2022-12-20 13:32:39 +00:00
m . MatchResponse ( t , res , m . MatchList ( "a" , m . MatchV3Count ( 0 ) ) )
2022-08-11 15:07:36 +01:00
// create 10 room in parallel and at the same time spam sliding sync to ensure we get bits of
// rooms before they are fully loaded.
numRooms := 10
ch := make ( chan int , numRooms )
var roomIDToName sync . Map
// start the goroutines
for i := 0 ; i < numRooms ; i ++ {
go func ( ) {
for i := range ch {
roomName := fmt . Sprintf ( "room %d" , i )
2023-10-11 12:23:46 +01:00
roomID := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" , "name" : roomName } )
2022-08-11 15:07:36 +01:00
roomIDToName . Store ( roomID , roomName )
}
} ( )
}
// inject the work
for i := 0 ; i < numRooms ; i ++ {
ch <- i
}
close ( ch )
seenRoomNames := make ( map [ string ] string )
start := time . Now ( )
var err error
for {
res = alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-11 15:07:36 +01:00
SlowGetAllRooms : & boolTrue ,
} ,
} ,
} , WithPos ( res . Pos ) )
for roomID , sub := range res . Rooms {
if sub . Name != "" {
seenRoomNames [ roomID ] = sub . Name
}
}
2022-12-14 18:53:55 +00:00
if time . Since ( start ) > 15 * time . Second {
2022-08-11 15:07:36 +01:00
t . Errorf ( "timed out, did not see all rooms, seen %d/%d" , len ( seenRoomNames ) , numRooms )
break
}
// do assertions and bail if they all pass
err = nil
seenRooms := 0
roomIDToName . Range ( func ( key , value interface { } ) bool {
seenRooms ++
createRoomID := key . ( string )
name := value . ( string )
gotName := seenRoomNames [ createRoomID ]
if name != gotName {
err = fmt . Errorf ( "[%s: got %s want %s] %w" , createRoomID , gotName , name , err )
}
return true
} )
if seenRooms != numRooms {
continue // wait for all /createRoom calls to return
}
if err == nil {
t . Logf ( "%+v\n" , seenRoomNames )
break // we saw all the rooms with the right names
}
}
if err != nil {
// we didn't see all the right room names after timeout secs
t . Errorf ( err . Error ( ) )
}
}
2022-08-18 13:11:05 +01:00
// Regression test for when you swap from sorting by recency to sorting by name and it didn't take effect.
func TestChangeSortOrder ( t * testing . T ) {
alice := registerNewUser ( t )
res := alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-18 13:11:05 +01:00
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
Sort : [ ] string { sync3 . SortByRecency } ,
RoomSubscription : sync3 . RoomSubscription {
RequiredState : [ ] [ 2 ] string { { "m.room.name" , "" } } ,
} ,
} ,
} ,
} )
m . MatchResponse ( t , res , m . MatchRoomSubscriptionsStrict ( nil ) , m . MatchNoV3Ops ( ) )
roomNames := [ ] string {
"Kiwi" , "Lemon" , "Apple" , "Orange" ,
}
roomIDs := make ( [ ] string , len ( roomNames ) )
gotNameToIDs := make ( map [ string ] string )
waitFor := func ( roomID , roomName string ) {
start := time . Now ( )
for {
if time . Since ( start ) > time . Second {
t . Fatalf ( "didn't see room name '%s' for room %s" , roomName , roomID )
break
}
res = alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-18 13:11:05 +01:00
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
} ,
} ,
} , WithPos ( res . Pos ) )
for roomID , sub := range res . Rooms {
gotNameToIDs [ sub . Name ] = roomID
}
if gotNameToIDs [ roomName ] == roomID {
break
}
time . Sleep ( 10 * time . Millisecond )
}
}
for i , name := range roomNames {
2023-10-11 12:23:46 +01:00
roomIDs [ i ] = alice . MustCreateRoom ( t , map [ string ] interface { } {
2022-08-18 13:11:05 +01:00
"name" : name ,
} )
// we cannot guarantee we will see the right state yet, so just keep track of the room names
waitFor ( roomIDs [ i ] , name )
}
// now change the sort order: first send the request up then keep hitting sliding sync until
// we see the txn ID confirming it has been applied
txnID := "a"
res = alice . SlidingSync ( t , sync3 . Request {
TxnID : "a" ,
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-18 13:11:05 +01:00
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
Sort : [ ] string { sync3 . SortByName } ,
} ,
} ,
} , WithPos ( res . Pos ) )
for res . TxnID != txnID {
res = alice . SlidingSync ( t , sync3 . Request {
2022-12-20 13:32:39 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2022-08-18 13:11:05 +01:00
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
} ,
} ,
} , WithPos ( res . Pos ) )
}
2022-12-20 13:32:39 +00:00
m . MatchResponse ( t , res , m . MatchList ( "a" , m . MatchV3Count ( 4 ) , m . MatchV3Ops (
2023-04-04 23:10:39 +01:00
m . MatchV3InvalidateOp ( 0 , 3 ) ,
m . MatchV3SyncOp ( 0 , 3 , [ ] string { gotNameToIDs [ "Apple" ] , gotNameToIDs [ "Kiwi" ] , gotNameToIDs [ "Lemon" ] , gotNameToIDs [ "Orange" ] } ) ,
2022-08-18 13:11:05 +01:00
) ) )
2023-01-03 13:39:46 +00:00
}
2022-08-18 13:11:05 +01:00
2023-01-03 13:39:46 +00:00
// Regression test that a window can be shrunk and the INVALIDATE appears correctly for the low/high ends of the range
func TestShrinkRange ( t * testing . T ) {
alice := registerNewUser ( t )
var roomIDs [ ] string // most recent first
for i := 0 ; i < 10 ; i ++ {
time . Sleep ( time . Millisecond ) // ensure creation timestamp changes
2023-10-11 12:23:46 +01:00
roomIDs = append ( [ ] string { alice . MustCreateRoom ( t , map [ string ] interface { } {
2023-01-03 13:39:46 +00:00
"preset" : "public_chat" ,
"name" : fmt . Sprintf ( "Room %d" , i ) ,
} ) } , roomIDs ... )
}
res := alice . SlidingSync ( t , sync3 . Request {
2023-01-18 15:31:44 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2023-01-03 13:39:46 +00:00
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
} ,
} ,
} )
2023-01-18 15:31:44 +00:00
m . MatchResponse ( t , res , m . MatchList ( "a" , m . MatchV3Count ( 10 ) , m . MatchV3Ops (
2023-04-05 01:07:07 +01:00
m . MatchV3SyncOp ( 0 , 9 , roomIDs ) ,
2023-01-03 13:39:46 +00:00
) ) )
// now shrink the window on both ends
res = alice . SlidingSync ( t , sync3 . Request {
2023-01-18 15:31:44 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2023-01-03 13:39:46 +00:00
Ranges : sync3 . SliceRanges { { 2 , 6 } } ,
} ,
} ,
} , WithPos ( res . Pos ) )
2023-01-18 15:31:44 +00:00
m . MatchResponse ( t , res , m . MatchList ( "a" , m . MatchV3Count ( 10 ) , m . MatchV3Ops (
2023-01-03 13:39:46 +00:00
m . MatchV3InvalidateOp ( 0 , 1 ) ,
2023-04-05 01:07:07 +01:00
m . MatchV3InvalidateOp ( 7 , 9 ) ,
2023-01-03 13:39:46 +00:00
) ) )
}
2022-08-18 13:11:05 +01:00
2023-01-03 13:39:46 +00:00
// Regression test that you can expand a window without it causing problems. Previously, it would cause
// a spurious {"op":"SYNC","range":[11,20],"room_ids":["!jHVHDxEWqTIcbDYoah:synapse"]} op for a bad index
func TestExpandRange ( t * testing . T ) {
alice := registerNewUser ( t )
var roomIDs [ ] string // most recent first
for i := 0 ; i < 10 ; i ++ {
time . Sleep ( time . Millisecond ) // ensure creation timestamp changes
2023-10-11 12:23:46 +01:00
roomIDs = append ( [ ] string { alice . MustCreateRoom ( t , map [ string ] interface { } {
2023-01-03 13:39:46 +00:00
"preset" : "public_chat" ,
"name" : fmt . Sprintf ( "Room %d" , i ) ,
} ) } , roomIDs ... )
}
res := alice . SlidingSync ( t , sync3 . Request {
2023-01-18 15:31:44 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2023-01-03 13:39:46 +00:00
Ranges : sync3 . SliceRanges { { 0 , 10 } } ,
} ,
} ,
} )
2023-01-18 15:31:44 +00:00
m . MatchResponse ( t , res , m . MatchList ( "a" , m . MatchV3Count ( 10 ) , m . MatchV3Ops (
2023-04-04 23:10:39 +01:00
m . MatchV3SyncOp ( 0 , 9 , roomIDs ) ,
2023-01-03 13:39:46 +00:00
) ) )
// now expand the window
res = alice . SlidingSync ( t , sync3 . Request {
2023-01-18 15:31:44 +00:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
2023-01-03 13:39:46 +00:00
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
} ,
} ,
} , WithPos ( res . Pos ) )
2023-01-18 15:31:44 +00:00
m . MatchResponse ( t , res , m . MatchList ( "a" , m . MatchV3Count ( 10 ) , m . MatchV3Ops ( ) ) )
2023-01-03 13:39:46 +00:00
}
// Regression test for Element X which has 2 identical lists and then changes the ranges in weird ways,
// causing the proxy to send back bad data. The request data here matches what EX sent.
func TestMultipleSameList ( t * testing . T ) {
alice := registerNewUser ( t )
// the account had 16 rooms, so do this now
var roomIDs [ ] string // most recent first
for i := 0 ; i < 16 ; i ++ {
time . Sleep ( time . Millisecond ) // ensure creation timestamp changes
2023-10-11 12:23:46 +01:00
roomIDs = append ( [ ] string { alice . MustCreateRoom ( t , map [ string ] interface { } {
2023-01-03 13:39:46 +00:00
"preset" : "public_chat" ,
"name" : fmt . Sprintf ( "Room %d" , i ) ,
} ) } , roomIDs ... )
}
firstList := sync3 . RequestList {
Sort : [ ] string { sync3 . SortByRecency , sync3 . SortByName } ,
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
RoomSubscription : sync3 . RoomSubscription {
RequiredState : [ ] [ 2 ] string { { "m.room.avatar" , "" } , { "m.room.encryption" , "" } } ,
TimelineLimit : 10 ,
} ,
}
secondList := sync3 . RequestList {
Sort : [ ] string { sync3 . SortByRecency , sync3 . SortByName } ,
Ranges : sync3 . SliceRanges { { 0 , 16 } } ,
RoomSubscription : sync3 . RoomSubscription {
RequiredState : [ ] [ 2 ] string { { "m.room.avatar" , "" } , { "m.room.encryption" , "" } } ,
} ,
}
res := alice . SlidingSync ( t , sync3 . Request {
2023-01-18 15:31:44 +00:00
Lists : map [ string ] sync3 . RequestList {
"1" : firstList , "2" : secondList ,
2023-01-03 13:39:46 +00:00
} ,
} )
m . MatchResponse ( t , res ,
2023-01-18 15:31:44 +00:00
m . MatchList ( "1" , m . MatchV3Count ( 16 ) , m . MatchV3Ops (
2023-04-04 23:10:39 +01:00
m . MatchV3SyncOp ( 0 , 15 , roomIDs , false ) ,
2023-01-03 13:39:46 +00:00
) ) ,
2023-01-18 15:31:44 +00:00
m . MatchList ( "2" , m . MatchV3Count ( 16 ) , m . MatchV3Ops (
2023-04-04 23:10:39 +01:00
m . MatchV3SyncOp ( 0 , 15 , roomIDs , false ) ,
2023-01-03 13:39:46 +00:00
) ) ,
)
// now change both list ranges in a valid but strange way, and get back bad responses
firstList . Ranges = sync3 . SliceRanges { { 2 , 15 } } // from [0,20]
secondList . Ranges = sync3 . SliceRanges { { 0 , 20 } } // from [0,16]
res = alice . SlidingSync ( t , sync3 . Request {
2023-01-18 15:31:44 +00:00
Lists : map [ string ] sync3 . RequestList {
"1" : firstList , "2" : secondList ,
2023-01-03 13:39:46 +00:00
} ,
} , WithPos ( res . Pos ) )
m . MatchResponse ( t , res ,
2023-01-18 15:31:44 +00:00
m . MatchList ( "1" , m . MatchV3Count ( 16 ) , m . MatchV3Ops (
2023-01-03 13:39:46 +00:00
m . MatchV3InvalidateOp ( 0 , 1 ) ,
) ) ,
2023-01-18 15:31:44 +00:00
m . MatchList ( "2" , m . MatchV3Count ( 16 ) , m . MatchV3Ops ( ) ) ,
2023-01-03 13:39:46 +00:00
)
2022-08-18 13:11:05 +01:00
}
2023-03-15 12:46:15 +00:00
// NB: assumes bump_event_types is sticky
func TestBumpEventTypesHandling ( t * testing . T ) {
2023-05-24 18:00:52 +01:00
alice := registerNamedUser ( t , "alice" )
bob := registerNamedUser ( t , "bob" )
charlie := registerNamedUser ( t , "charlie" )
2023-03-15 12:46:15 +00:00
t . Log ( "Alice creates two rooms" )
2023-10-11 12:23:46 +01:00
room1 := alice . MustCreateRoom (
2023-03-15 12:46:15 +00:00
t ,
map [ string ] interface { } {
"preset" : "public_chat" ,
"name" : "room1" ,
} ,
)
2023-10-11 12:23:46 +01:00
room2 := alice . MustCreateRoom (
2023-03-15 12:46:15 +00:00
t ,
map [ string ] interface { } {
"preset" : "public_chat" ,
"name" : "room2" ,
} ,
)
t . Logf ( "room1=%s room2=%s" , room1 , room2 )
t . Log ( "Bob joins both rooms." )
bob . JoinRoom ( t , room1 , nil )
bob . JoinRoom ( t , room2 , nil )
t . Log ( "Bob sends a message in room 2 then room 1." )
2023-10-11 12:23:46 +01:00
bob . SendEventSynced ( t , room2 , b . Event {
2023-03-15 12:46:15 +00:00
Type : "m.room.message" ,
Content : map [ string ] interface { } {
"body" : "Hi room 2" ,
"msgtype" : "m.text" ,
} ,
} )
2023-10-11 12:23:46 +01:00
bob . SendEventSynced ( t , room1 , b . Event {
2023-03-15 12:46:15 +00:00
Type : "m.room.message" ,
Content : map [ string ] interface { } {
"body" : "Hello world" ,
"msgtype" : "m.text" ,
} ,
} )
t . Log ( "Alice requests a sliding sync that bumps rooms on messages only." )
aliceReqList := sync3 . RequestList {
Sort : [ ] string { sync3 . SortByRecency , sync3 . SortByName } ,
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
RoomSubscription : sync3 . RoomSubscription {
RequiredState : [ ] [ 2 ] string { { "m.room.avatar" , "" } , { "m.room.encryption" , "" } } ,
TimelineLimit : 10 ,
} ,
2023-05-24 15:50:53 +01:00
BumpEventTypes : [ ] string { "m.room.message" , "m.room.encrypted" } ,
2023-03-15 12:46:15 +00:00
}
aliceSyncRequest := sync3 . Request {
Lists : map [ string ] sync3 . RequestList {
2023-05-24 18:00:52 +01:00
"alice_list" : aliceReqList ,
2023-03-15 12:46:15 +00:00
} ,
}
2023-03-21 13:20:14 +00:00
// This sync should include both of Bob's messages. The proxy will make an initial
// V2 sync to the HS, which should include the latest event in both rooms.
// TODO: we could capture the event IDs above and assert this explicitly.
2023-03-15 12:46:15 +00:00
aliceRes := alice . SlidingSync ( t , aliceSyncRequest )
t . Log ( "Alice's sync response should include room1 ahead of room 2." )
2023-05-24 18:00:52 +01:00
matchRoom1ThenRoom2 := [ ] m . ListMatcher {
2023-03-15 12:46:15 +00:00
m . MatchV3Count ( 2 ) ,
m . MatchV3Ops (
2023-04-04 23:10:39 +01:00
m . MatchV3SyncOp ( 0 , 1 , [ ] string { room1 , room2 } , false ) ,
2023-05-24 18:00:52 +01:00
) ,
}
m . MatchResponse ( t , aliceRes , m . MatchList ( "alice_list" , matchRoom1ThenRoom2 ... ) )
2023-03-15 12:46:15 +00:00
t . Log ( "Bob requests a sliding sync that bumps rooms on messages and memberships." )
bobReqList := sync3 . RequestList {
Sort : [ ] string { sync3 . SortByRecency , sync3 . SortByName } ,
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
RoomSubscription : sync3 . RoomSubscription {
RequiredState : [ ] [ 2 ] string { { "m.room.avatar" , "" } , { "m.room.encryption" , "" } } ,
TimelineLimit : 10 ,
} ,
2023-05-24 15:50:53 +01:00
BumpEventTypes : [ ] string { "m.room.message" , "m.room.encrypted" , "m.room.member" } ,
2023-03-15 12:46:15 +00:00
}
bobSyncRequest := sync3 . Request {
Lists : map [ string ] sync3 . RequestList {
2023-05-24 18:00:52 +01:00
"bob_list" : bobReqList ,
2023-03-15 12:46:15 +00:00
} ,
}
bobRes := bob . SlidingSync ( t , bobSyncRequest )
t . Log ( "Bob should also see room 1 ahead of room 2 in his sliding sync response." )
2023-05-24 18:00:52 +01:00
m . MatchResponse ( t , bobRes , m . MatchList ( "bob_list" , matchRoom1ThenRoom2 ... ) )
2023-03-15 12:46:15 +00:00
t . Log ( "Charlie joins room 2." )
2023-05-25 18:09:52 +01:00
charlie . JoinRoom ( t , room2 , nil )
2023-03-15 12:46:15 +00:00
t . Log ( "Alice syncs until she sees Charlie's membership." )
aliceRes = alice . SlidingSyncUntilMembership ( t , aliceRes . Pos , room2 , charlie , "join" )
t . Log ( "Alice shouldn't see any rooms' positions change." )
2023-03-21 13:20:14 +00:00
m . MatchResponse (
t ,
aliceRes ,
2023-05-24 18:00:52 +01:00
m . MatchList ( "alice_list" , m . MatchV3Count ( 2 ) ) ,
2023-03-21 13:20:14 +00:00
m . MatchNoV3Ops ( ) ,
2023-03-15 12:46:15 +00:00
)
t . Log ( "Bob syncs until he sees Charlie's membership." )
bobRes = bob . SlidingSyncUntilMembership ( t , bobRes . Pos , room2 , charlie , "join" )
t . Log ( "Bob should see room 2 at the top of his list." )
2023-05-24 18:00:52 +01:00
matchBobSeesRoom2Bumped := m . MatchList ( "bob_list" ,
2023-03-15 12:46:15 +00:00
m . MatchV3Count ( 2 ) ,
m . MatchV3Ops (
m . MatchV3DeleteOp ( 1 ) ,
m . MatchV3InsertOp ( 0 , room2 ) ,
) ,
)
m . MatchResponse ( t , bobRes , matchBobSeesRoom2Bumped )
2023-04-25 17:45:18 +01:00
// The read receipt stuff here specifically checks for the bug in
// https://github.com/matrix-org/sliding-sync/issues/83
2023-05-25 18:09:52 +01:00
aliceRoom2Timeline := aliceRes . Rooms [ room2 ] . Timeline
aliceLastSeenEvent := aliceRoom2Timeline [ len ( aliceRoom2Timeline ) - 1 ]
aliceLastSeenEventID := gjson . ParseBytes ( aliceLastSeenEvent ) . Get ( "event_id" ) . Str
if aliceLastSeenEventID == "" {
t . Error ( "Could not find event ID for the last event in Alice's timeline." )
}
2023-04-25 17:45:18 +01:00
t . Log ( "Alice marks herself as having seen Charlie's join." )
2023-05-25 18:09:52 +01:00
alice . SendReceipt ( t , room2 , aliceLastSeenEventID , "m.read" )
2023-04-25 17:45:18 +01:00
t . Log ( "Alice syncs until she sees her receipt. At no point should see see any room list operations." )
alice . SlidingSyncUntil (
t ,
aliceRes . Pos ,
sync3 . Request { Extensions : extensions . Request {
Receipts : & extensions . ReceiptsRequest {
Core : extensions . Core { Enabled : & boolTrue } ,
} ,
} } ,
func ( response * sync3 . Response ) error {
if err := m . MatchNoV3Ops ( ) ( response ) ; err != nil {
t . Fatalf ( "expected no ops while waiting for receipt: %s" , err )
}
matchReceipt := m . MatchReceipts ( room2 , [ ] m . Receipt { {
2023-05-25 18:09:52 +01:00
EventID : aliceLastSeenEventID ,
2023-04-25 17:45:18 +01:00
UserID : alice . UserID ,
Type : "m.read" ,
} } )
return matchReceipt ( response )
} ,
)
}
2023-04-11 12:46:32 +01:00
2023-05-25 18:10:27 +01:00
func TestBumpEventTypesInOverlappingLists ( t * testing . T ) {
alice := registerNamedUser ( t , "alice" )
bob := registerNamedUser ( t , "bob" )
t . Log ( "Alice creates four rooms" )
2023-10-11 12:23:46 +01:00
room1 := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" , "name" : "room1" } )
room2 := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" , "name" : "room2" } )
room3 := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" , "name" : "room3" } )
room4 := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" , "name" : "room4" } )
2023-05-25 18:10:27 +01:00
t . Log ( "Alice writes a message in all four rooms." )
// Note: all lists bump on messages, so this will ensure the recency order is sensible.
helloWorld := map [ string ] interface { } { "body" : "Hello world" , "msgtype" : "m.text" }
2023-10-11 12:23:46 +01:00
alice . Unsafe_SendEventUnsynced ( t , room1 , b . Event { Type : "m.room.message" , Content : helloWorld } )
alice . Unsafe_SendEventUnsynced ( t , room2 , b . Event { Type : "m.room.message" , Content : helloWorld } )
alice . Unsafe_SendEventUnsynced ( t , room3 , b . Event { Type : "m.room.message" , Content : helloWorld } )
alice . SendEventSynced ( t , room4 , b . Event { Type : "m.room.message" , Content : helloWorld } )
2023-05-25 18:10:27 +01:00
t . Log ( "Alice requests a sync with three lists: one bumping on messages, a second bumping on messages and memberships, and a third bumping on all events." )
const listMsg = "message"
const listMsgMember = "message_membership"
const listAll = "all"
req := sync3 . Request {
Lists : map [ string ] sync3 . RequestList {
listMsg : {
Sort : [ ] string { sync3 . SortByRecency } ,
RoomSubscription : sync3 . RoomSubscription { TimelineLimit : 10 } ,
Ranges : sync3 . SliceRanges { { 0 , 10 } } ,
BumpEventTypes : [ ] string { "m.room.message" } ,
} ,
listMsgMember : {
Sort : [ ] string { sync3 . SortByRecency } ,
RoomSubscription : sync3 . RoomSubscription { TimelineLimit : 10 } ,
Ranges : sync3 . SliceRanges { { 0 , 10 } } ,
BumpEventTypes : [ ] string { "m.room.message" , "m.room.member" } ,
} ,
listAll : {
Sort : [ ] string { sync3 . SortByRecency } ,
RoomSubscription : sync3 . RoomSubscription { TimelineLimit : 10 } ,
Ranges : sync3 . SliceRanges { { 0 , 10 } } ,
BumpEventTypes : nil ,
} ,
} ,
}
res := alice . SlidingSync ( t , req )
t . Log ( "Alice sees the rooms in order 4, 3, 2, 1." )
// Note: first sync, so first poll, so should see all four message events.
see4321 := [ ] m . ListMatcher {
m . MatchV3Count ( 4 ) ,
m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 3 , [ ] string { room4 , room3 , room2 , room1 } , false ) ) ,
}
m . MatchResponse ( t , res , m . MatchLists ( map [ string ] [ ] m . ListMatcher {
listMsg : see4321 ,
listMsgMember : see4321 ,
listAll : see4321 ,
} ) )
t . Log ( "Bob joins room 1. Alice syncs until she sees Bob's join." )
bob . JoinRoom ( t , room1 , nil )
res = alice . SlidingSyncUntilMembership ( t , res . Pos , room1 , bob , "join" )
t . Logf ( "Alice should see room1 bumped in the lists %s and %s, but not %s" , listMsgMember , listAll , listMsg )
noMovement := [ ] m . ListMatcher {
m . MatchV3Count ( 4 ) ,
m . MatchV3Ops ( ) ,
}
bumpToTop := func ( roomID string , fromIdx int ) [ ] m . ListMatcher {
return [ ] m . ListMatcher {
m . MatchV3Count ( 4 ) ,
m . MatchV3Ops ( m . MatchV3DeleteOp ( fromIdx ) , m . MatchV3InsertOp ( 0 , roomID ) ) ,
}
}
m . MatchResponse ( t , res , m . MatchLists ( map [ string ] [ ] m . ListMatcher {
listMsg : noMovement , // 4321
listMsgMember : bumpToTop ( room1 , 3 ) , // 4321 -> 1432
listAll : bumpToTop ( room1 , 3 ) , // 4321 -> 1432
} ) )
t . Log ( "Alice sets a room topic in room 3, and syncs until she sees the topic." )
2023-10-11 12:23:46 +01:00
topicEventID := alice . Unsafe_SendEventUnsynced ( t , room3 , b . Event {
Type : "m.room.topic" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } { "topic" : "spicy meatballs" } ,
} )
2023-05-25 18:10:27 +01:00
res = alice . SlidingSyncUntilEventID ( t , res . Pos , room3 , topicEventID )
t . Logf ( "Alice sees room3 bump in the %s list only" , listAll )
m . MatchResponse ( t , res , m . MatchLists ( map [ string ] [ ] m . ListMatcher {
listMsg : noMovement , // 4321
listMsgMember : noMovement , // 1432
listAll : bumpToTop ( room3 , 2 ) , // 1432 -> 3142
} ) )
t . Logf ( "Alice sends a message in room 2, and syncs until she sees it." )
2023-10-11 12:23:46 +01:00
msgEventID := alice . Unsafe_SendEventUnsynced ( t , room2 , b . Event { Type : "m.room.message" , Content : helloWorld } )
2023-05-25 18:10:27 +01:00
res = alice . SlidingSyncUntilEventID ( t , res . Pos , room2 , msgEventID )
t . Logf ( "Alice sees room2 bump in all lists" )
m . MatchResponse ( t , res , m . MatchLists ( map [ string ] [ ] m . ListMatcher {
listMsg : bumpToTop ( room2 , 2 ) , // 4321 -> 2431
listMsgMember : bumpToTop ( room2 , 3 ) , // 1432 -> 2143
listAll : bumpToTop ( room2 , 3 ) , // 3142 -> 2314
} ) )
}
2023-06-06 17:36:31 +01:00
func TestBumpEventTypesDoesntLeakOnNewConnAfterJoin ( t * testing . T ) {
alice := registerNamedUser ( t , "alice" )
bob := registerNamedUser ( t , "bob" )
t . Log ( "Alice creates a room and sends a secret state event." )
2023-10-11 12:23:46 +01:00
room1 := alice . MustCreateRoom (
2023-06-06 17:36:31 +01:00
t ,
map [ string ] interface { } {
"preset" : "public_chat" ,
"name" : "room1" ,
} ,
)
2023-10-11 12:23:46 +01:00
alice . Unsafe_SendEventUnsynced ( t , room1 , b . Event {
Type : "secret" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } { } ,
} )
2023-06-06 17:36:31 +01:00
t . Log ( "Bob creates a room and sends a secret state event." )
time . Sleep ( 1 * time . Millisecond )
2023-10-11 12:23:46 +01:00
room2 := bob . MustCreateRoom (
2023-06-06 17:36:31 +01:00
t ,
map [ string ] interface { } {
"preset" : "public_chat" ,
"name" : "room1" ,
} ,
)
2023-10-11 12:23:46 +01:00
bob . Unsafe_SendEventUnsynced ( t , room2 , b . Event {
Type : "secret" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } { } ,
} )
2023-06-06 17:36:31 +01:00
t . Log ( "Alice invites Bob, who accepts." )
alice . InviteRoom ( t , room1 , bob . UserID )
bob . JoinRoom ( t , room1 , nil )
t . Log ( "Bob sliding syncs, requesting that rooms are bumped on the secret event type." )
res := bob . SlidingSync ( t , sync3 . Request {
Lists : map [ string ] sync3 . RequestList {
"a" : {
RoomSubscription : sync3 . RoomSubscription { } ,
Ranges : sync3 . SliceRanges { { 0 , 1 } } ,
Sort : [ ] string { sync3 . SortByRecency } ,
BumpEventTypes : [ ] string { "secret" } ,
} ,
} ,
} )
t . Log ( "Bob should see room1 ahead of room2." )
// The order in which things happen:
// 1. alice: room1 secret event
// 2. bob: room2 secret event
// 3. alice: invite bob to room 1
// 4. bob: join room 1
// Bob can only see (2), (3) and (4), which means that room 1 has had most recent activity.
// (If we use the secret events' timestamps alone, without considering what Bob has
// permission to see, we will only consider (1) and (2), which would mean room 2
// has had the most recent activity.)
m . MatchResponse (
t ,
res ,
m . MatchList (
"a" ,
m . MatchV3Count ( 2 ) ,
m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 1 , [ ] string { room1 , room2 } ) ) ,
) ,
)
}
// Like TestBumpEventTypesDoesntLeakOnNewConnAfterJoin, but Bob never accepts the invite.
func TestBumpEventTypesDoesntLeakOnNewConnAfterInvite ( t * testing . T ) {
alice := registerNamedUser ( t , "alice" )
bob := registerNamedUser ( t , "bob" )
t . Log ( "Alice creates a room and sends a secret state event." )
2023-10-11 12:23:46 +01:00
room1 := alice . MustCreateRoom (
2023-06-06 17:36:31 +01:00
t ,
map [ string ] interface { } {
"preset" : "public_chat" ,
"name" : "room1" ,
} ,
)
2023-10-11 12:23:46 +01:00
alice . Unsafe_SendEventUnsynced ( t , room1 , b . Event {
Type : "secret" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } { } ,
} )
2023-06-06 17:36:31 +01:00
t . Log ( "Bob creates a room and sends a secret state event." )
time . Sleep ( 1 * time . Millisecond )
2023-10-11 12:23:46 +01:00
room2 := bob . MustCreateRoom (
2023-06-06 17:36:31 +01:00
t ,
map [ string ] interface { } {
"preset" : "public_chat" ,
"name" : "room1" ,
} ,
)
2023-10-11 12:23:46 +01:00
bob . Unsafe_SendEventUnsynced ( t , room2 , b . Event {
Type : "secret" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } { } ,
} )
2023-06-06 17:36:31 +01:00
t . Log ( "Alice invites Bob, who does not respond." )
alice . InviteRoom ( t , room1 , bob . UserID )
t . Log ( "Bob sliding syncs, requesting that rooms are bumped on the secret event type." )
res := bob . SlidingSync ( t , sync3 . Request {
Lists : map [ string ] sync3 . RequestList {
"a" : {
RoomSubscription : sync3 . RoomSubscription { } ,
Ranges : sync3 . SliceRanges { { 0 , 1 } } ,
Sort : [ ] string { sync3 . SortByRecency } ,
BumpEventTypes : [ ] string { "secret" } ,
} ,
} ,
} )
t . Log ( "Bob should see room1 ahead of room2." )
// The order in which things happen:
// 1. alice: room1 secret event
// 2. bob: room2 secret event
// 3. alice: invite bob to room 1
// Bob can only see (2) and (3), which means that room 1 has had most recent activity.
// (If we use the secret events' timestamps alone, without considering what Bob has
// permission to see, we will only consider (1) and (2), which would mean room 2
// has had the most recent activity.)
m . MatchResponse (
t ,
res ,
m . MatchList (
"a" ,
m . MatchV3Count ( 2 ) ,
m . MatchV3Ops ( m . MatchV3SyncOp ( 0 , 1 , [ ] string { room1 , room2 } ) ) ,
) ,
)
}
2023-04-11 12:46:32 +01:00
// Tests the scenario described at
// https://github.com/matrix-org/sliding-sync/pull/58#discussion_r1159850458
func TestRangeOutsideTotalRooms ( t * testing . T ) {
alice := registerNewUser ( t )
t . Log ( "Alice makes three public rooms." )
2023-10-11 12:23:46 +01:00
room0 := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" , "name" : "A" } )
room1 := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" , "name" : "B" } )
room2 := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" , "name" : "C" } )
2023-04-11 12:46:32 +01:00
t . Log ( "Alice initial syncs, requesting room ranges [0, 1] and [8, 9]" )
2023-04-11 14:01:34 +01:00
syncRes := alice . SlidingSync ( t , sync3 . Request {
2023-04-11 12:46:32 +01:00
Lists : map [ string ] sync3 . RequestList {
"a" : {
Sort : [ ] string { sync3 . SortByName } ,
Ranges : sync3 . SliceRanges { { 0 , 1 } , { 8 , 9 } } ,
} ,
} ,
2023-04-11 14:01:34 +01:00
} )
2023-04-11 12:46:32 +01:00
t . Log ( "Alice should only see rooms 0– 1 in the sync response." )
m . MatchResponse (
t ,
syncRes ,
m . MatchList (
"a" ,
m . MatchV3Count ( 3 ) ,
m . MatchV3Ops (
m . MatchV3SyncOp ( 0 , 1 , [ ] string { room0 , room1 } ) ,
) ,
) ,
)
2023-04-11 14:01:34 +01:00
t . Log ( "Alice changes the sort order" )
syncRes = alice . SlidingSync (
t ,
sync3 . Request {
Lists : map [ string ] sync3 . RequestList {
"a" : {
Sort : [ ] string { sync3 . SortByRecency } ,
} ,
} ,
} ,
WithPos ( syncRes . Pos ) ,
)
m . MatchResponse (
t ,
syncRes ,
m . MatchList (
"a" ,
m . MatchV3Count ( 3 ) ,
m . MatchV3Ops (
m . MatchV3InvalidateOp ( 0 , 1 ) ,
m . MatchV3SyncOp ( 0 , 1 , [ ] string { room2 , room1 } ) ,
) ,
) ,
)
2023-04-11 12:46:32 +01:00
}
2023-07-18 11:00:10 +01:00
// Nicked from Synapse's tests, see
// https://github.com/matrix-org/synapse/blob/2cacd0849a02d43f88b6c15ee862398159ab827c/tests/test_utils/__init__.py#L154-L161
// Resolution: 1× 1, MIME type: image/png, Extension: png, Size: 67 B
var smallPNG = [ ] byte (
"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01\r\n-\xb4\x00\x00\x00\x00IEND\xaeB`\x82" ,
)
func TestAvatarFieldInRoomResponse ( t * testing . T ) {
alice := registerNamedUser ( t , "alice" )
bob := registerNamedUser ( t , "bob" )
chris := registerNamedUser ( t , "chris" )
avatarURLs := map [ string ] struct { } { }
uploadAvatar := func ( client * CSAPI , filename string ) string {
avatar := alice . UploadContent ( t , smallPNG , filename , "image/png" )
if _ , exists := avatarURLs [ avatar ] ; exists {
t . Fatalf ( "New avatar %s has already been uploaded" , avatar )
}
t . Logf ( "%s is uploaded as %s" , filename , avatar )
avatarURLs [ avatar ] = struct { } { }
return avatar
}
t . Log ( "Alice, Bob and Chris upload and set an avatar." )
aliceAvatar := uploadAvatar ( alice , "alice.png" )
bobAvatar := uploadAvatar ( bob , "bob.png" )
chrisAvatar := uploadAvatar ( chris , "chris.png" )
alice . SetAvatar ( t , aliceAvatar )
bob . SetAvatar ( t , bobAvatar )
chris . SetAvatar ( t , chrisAvatar )
t . Log ( "Alice makes a public room, a DM with herself, a DM with Bob, a DM with Chris, and a group-DM with Bob and Chris." )
2023-10-11 12:23:46 +01:00
public := alice . MustCreateRoom ( t , map [ string ] interface { } { "preset" : "public_chat" } )
2023-07-18 11:00:10 +01:00
// TODO: you can create a DM with yourself e.g. as below. It probably ought to have
// your own face as an avatar.
2023-10-11 12:23:46 +01:00
// dmAlice := alice.MustCreateRoom(t, map[string]interface{}{
2023-07-18 11:00:10 +01:00
// "preset": "trusted_private_chat",
// "is_direct": true,
// })
2023-10-11 12:23:46 +01:00
dmBob := alice . MustCreateRoom ( t , map [ string ] interface { } {
2023-07-18 11:00:10 +01:00
"preset" : "trusted_private_chat" ,
"is_direct" : true ,
"invite" : [ ] string { bob . UserID } ,
} )
2023-10-11 12:23:46 +01:00
dmChris := alice . MustCreateRoom ( t , map [ string ] interface { } {
2023-07-18 11:00:10 +01:00
"preset" : "trusted_private_chat" ,
"is_direct" : true ,
"invite" : [ ] string { chris . UserID } ,
} )
2023-10-11 12:23:46 +01:00
dmBobChris := alice . MustCreateRoom ( t , map [ string ] interface { } {
2023-07-18 11:00:10 +01:00
"preset" : "trusted_private_chat" ,
"is_direct" : true ,
"invite" : [ ] string { bob . UserID , chris . UserID } ,
} )
2024-01-22 10:34:06 +00:00
alice . MustSetGlobalAccountData ( t , "m.direct" , map [ string ] any {
bob . UserID : [ ] string { dmBob , dmBobChris } ,
chris . UserID : [ ] string { dmChris , dmBobChris } ,
} )
2023-07-18 11:00:10 +01:00
t . Logf ( "Rooms:\npublic=%s\ndmBob=%s\ndmChris=%s\ndmBobChris=%s" , public , dmBob , dmChris , dmBobChris )
t . Log ( "Bob accepts his invites. Chris accepts none." )
bob . JoinRoom ( t , dmBob , nil )
bob . JoinRoom ( t , dmBobChris , nil )
t . Log ( "Alice makes an initial sliding sync." )
res := alice . SlidingSync ( t , sync3 . Request {
Lists : map [ string ] sync3 . RequestList {
"rooms" : {
Ranges : sync3 . SliceRanges { { 0 , 4 } } ,
} ,
} ,
} )
2024-01-22 10:34:06 +00:00
t . Log ( "Alice should see each room in the sync response with an appropriate avatar and DM flag" )
2023-07-18 11:00:10 +01:00
m . MatchResponse (
t ,
res ,
2024-01-22 10:34:06 +00:00
m . MatchRoomSubscription ( public , m . MatchRoomUnsetAvatar ( ) , m . MatchRoomIsDM ( false ) ) ,
m . MatchRoomSubscription ( dmBob , m . MatchRoomAvatar ( bob . AvatarURL ) , m . MatchRoomIsDM ( true ) ) ,
m . MatchRoomSubscription ( dmChris , m . MatchRoomAvatar ( chris . AvatarURL ) , m . MatchRoomIsDM ( true ) ) ,
m . MatchRoomSubscription ( dmBobChris , m . MatchRoomUnsetAvatar ( ) , m . MatchRoomIsDM ( true ) ) ,
2023-07-18 11:00:10 +01:00
)
t . Run ( "Avatar not resent on message" , func ( t * testing . T ) {
t . Log ( "Bob sends a sentinel message." )
2023-10-11 12:23:46 +01:00
sentinel := bob . SendEventSynced ( t , dmBob , b . Event {
2023-07-18 11:00:10 +01:00
Type : "m.room.message" ,
Content : map [ string ] interface { } {
"body" : "Hello world" ,
"msgtype" : "m.text" ,
} ,
} )
t . Log ( "Alice syncs until she sees the sentinel. She should not see the DM avatar change." )
res = alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request { } , func ( response * sync3 . Response ) error {
matchNoAvatarChange := m . MatchRoomSubscription ( dmBob , m . MatchRoomUnchangedAvatar ( ) )
if err := matchNoAvatarChange ( response ) ; err != nil {
t . Fatalf ( "Saw DM avatar change: %s" , err )
}
matchSentinel := m . MatchRoomSubscription ( dmBob , MatchRoomTimelineMostRecent ( 1 , [ ] Event { { ID : sentinel } } ) )
return matchSentinel ( response )
} )
} )
t . Run ( "DM declined" , func ( t * testing . T ) {
t . Log ( "Chris leaves his DM with Alice." )
chris . LeaveRoom ( t , dmChris )
t . Log ( "Alice syncs until she sees Chris's leave." )
res = alice . SlidingSyncUntilMembership ( t , res . Pos , dmChris , chris , "leave" )
t . Log ( "Alice sees Chris's avatar vanish." )
m . MatchResponse ( t , res , m . MatchRoomSubscription ( dmChris , m . MatchRoomUnsetAvatar ( ) ) )
} )
t . Run ( "Group DM declined" , func ( t * testing . T ) {
t . Log ( "Chris leaves his group DM with Alice and Bob." )
chris . LeaveRoom ( t , dmBobChris )
t . Log ( "Alice syncs until she sees Chris's leave." )
res = alice . SlidingSyncUntilMembership ( t , res . Pos , dmBobChris , chris , "leave" )
t . Log ( "Alice sees the room's avatar change to Bob's avatar." )
// Because this is now a DM room with exactly one other (joined|invited) member.
m . MatchResponse ( t , res , m . MatchRoomSubscription ( dmBobChris , m . MatchRoomAvatar ( bob . AvatarURL ) ) )
} )
t . Run ( "Bob's avatar change propagates" , func ( t * testing . T ) {
t . Log ( "Bob changes his avatar." )
bobAvatar2 := uploadAvatar ( bob , "bob2.png" )
bob . SetAvatar ( t , bobAvatar2 )
avatarChangeInDM := false
avatarChangeInGroupDM := false
t . Log ( "Alice syncs until she sees Bob's new avatar." )
res = alice . SlidingSyncUntil (
t ,
res . Pos ,
sync3 . Request { } ,
func ( response * sync3 . Response ) error {
if ! avatarChangeInDM {
err := m . MatchRoomSubscription ( dmBob , m . MatchRoomAvatar ( bob . AvatarURL ) ) ( response )
if err == nil {
avatarChangeInDM = true
}
}
if ! avatarChangeInGroupDM {
err := m . MatchRoomSubscription ( dmBobChris , m . MatchRoomAvatar ( bob . AvatarURL ) ) ( response )
if err == nil {
avatarChangeInGroupDM = true
}
}
if avatarChangeInDM && avatarChangeInGroupDM {
return nil
}
return fmt . Errorf ( "still waiting: avatarChangeInDM=%t avatarChangeInGroupDM=%t" , avatarChangeInDM , avatarChangeInGroupDM )
} ,
)
t . Log ( "Bob removes his avatar." )
bob . SetAvatar ( t , "" )
avatarChangeInDM = false
avatarChangeInGroupDM = false
t . Log ( "Alice syncs until she sees Bob's avatars vanish." )
res = alice . SlidingSyncUntil (
t ,
res . Pos ,
sync3 . Request { } ,
func ( response * sync3 . Response ) error {
if ! avatarChangeInDM {
err := m . MatchRoomSubscription ( dmBob , m . MatchRoomUnsetAvatar ( ) ) ( response )
if err == nil {
avatarChangeInDM = true
} else {
t . Log ( err )
}
}
if ! avatarChangeInGroupDM {
err := m . MatchRoomSubscription ( dmBobChris , m . MatchRoomUnsetAvatar ( ) ) ( response )
if err == nil {
avatarChangeInGroupDM = true
} else {
t . Log ( err )
}
}
if avatarChangeInDM && avatarChangeInGroupDM {
return nil
}
return fmt . Errorf ( "still waiting: avatarChangeInDM=%t avatarChangeInGroupDM=%t" , avatarChangeInDM , avatarChangeInGroupDM )
} ,
)
} )
t . Run ( "Explicit avatar propagates in non-DM room" , func ( t * testing . T ) {
t . Log ( "Alice sets an avatar for the public room." )
publicAvatar := uploadAvatar ( alice , "public.png" )
2023-10-11 12:23:46 +01:00
alice . Unsafe_SendEventUnsynced ( t , public , b . Event {
Type : "m.room.avatar" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } {
"url" : publicAvatar ,
} ,
2023-07-18 11:00:10 +01:00
} )
t . Log ( "Alice syncs until she sees that avatar." )
res = alice . SlidingSyncUntil (
t ,
res . Pos ,
sync3 . Request { } ,
m . MatchRoomSubscriptions ( map [ string ] [ ] m . RoomMatcher {
public : { m . MatchRoomAvatar ( publicAvatar ) } ,
} ) ,
)
t . Log ( "Alice changes the avatar for the public room." )
publicAvatar2 := uploadAvatar ( alice , "public2.png" )
2023-10-11 12:23:46 +01:00
alice . Unsafe_SendEventUnsynced ( t , public , b . Event {
Type : "m.room.avatar" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } {
"url" : publicAvatar2 ,
} ,
2023-07-18 11:00:10 +01:00
} )
t . Log ( "Alice syncs until she sees that avatar." )
res = alice . SlidingSyncUntil (
t ,
res . Pos ,
sync3 . Request { } ,
m . MatchRoomSubscriptions ( map [ string ] [ ] m . RoomMatcher {
public : { m . MatchRoomAvatar ( publicAvatar2 ) } ,
} ) ,
)
t . Log ( "Alice removes the avatar for the public room." )
2023-10-11 12:23:46 +01:00
alice . Unsafe_SendEventUnsynced ( t , public , b . Event {
Type : "m.room.avatar" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } { } ,
} )
2023-07-18 11:00:10 +01:00
t . Log ( "Alice syncs until she sees that avatar vanish." )
res = alice . SlidingSyncUntil (
t ,
res . Pos ,
sync3 . Request { } ,
m . MatchRoomSubscriptions ( map [ string ] [ ] m . RoomMatcher {
public : { m . MatchRoomUnsetAvatar ( ) } ,
} ) ,
)
} )
t . Run ( "Explicit avatar propagates in DM room" , func ( t * testing . T ) {
t . Log ( "Alice re-invites Chris to their DM." )
alice . InviteRoom ( t , dmChris , chris . UserID )
t . Log ( "Alice syncs until she sees her invitation to Chris." )
res = alice . SlidingSyncUntilMembership ( t , res . Pos , dmChris , chris , "invite" )
t . Log ( "Alice should see the DM with Chris's avatar." )
m . MatchResponse ( t , res , m . MatchRoomSubscription ( dmChris , m . MatchRoomAvatar ( chris . AvatarURL ) ) )
t . Log ( "Chris joins the room." )
chris . JoinRoom ( t , dmChris , nil )
t . Log ( "Alice syncs until she sees Chris's join." )
res = alice . SlidingSyncUntilMembership ( t , res . Pos , dmChris , chris , "join" )
t . Log ( "Alice shouldn't see the DM's avatar change.." )
m . MatchResponse ( t , res , m . MatchRoomSubscription ( dmChris , m . MatchRoomUnchangedAvatar ( ) ) )
t . Log ( "Chris gives their DM a bespoke avatar." )
dmAvatar := uploadAvatar ( chris , "dm.png" )
2023-10-11 12:23:46 +01:00
chris . Unsafe_SendEventUnsynced ( t , dmChris , b . Event {
Type : "m.room.avatar" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } {
"url" : dmAvatar ,
} ,
2023-07-18 11:00:10 +01:00
} )
t . Log ( "Alice syncs until she sees that avatar." )
alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request { } , m . MatchRoomSubscription ( dmChris , m . MatchRoomAvatar ( dmAvatar ) ) )
t . Log ( "Chris changes his global avatar, which adds a join event to the room." )
chrisAvatar2 := uploadAvatar ( chris , "chris2.png" )
chris . SetAvatar ( t , chrisAvatar2 )
t . Log ( "Alice syncs until she sees that join event." )
res = alice . SlidingSyncUntilMembership ( t , res . Pos , dmChris , chris , "join" )
t . Log ( "Her response should have either no avatar change, or the same bespoke avatar." )
// No change, ideally, but repeating the same avatar isn't _wrong_
m . MatchResponse ( t , res , m . MatchRoomSubscription ( dmChris , func ( r sync3 . Room ) error {
noChangeErr := m . MatchRoomUnchangedAvatar ( ) ( r )
sameBespokeAvatarErr := m . MatchRoomAvatar ( dmAvatar ) ( r )
if noChangeErr == nil || sameBespokeAvatarErr == nil {
return nil
}
return fmt . Errorf ( "expected no change or the same bespoke avatar (%s), got '%s'" , dmAvatar , r . AvatarChange )
} ) )
t . Log ( "Chris updates the DM's avatar." )
dmAvatar2 := uploadAvatar ( chris , "dm2.png" )
2023-10-11 12:23:46 +01:00
chris . Unsafe_SendEventUnsynced ( t , dmChris , b . Event {
Type : "m.room.avatar" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } {
"url" : dmAvatar2 ,
} ,
2023-07-18 11:00:10 +01:00
} )
t . Log ( "Alice syncs until she sees that avatar." )
res = alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request { } , m . MatchRoomSubscription ( dmChris , m . MatchRoomAvatar ( dmAvatar2 ) ) )
t . Log ( "Chris removes the DM's avatar." )
2023-10-11 12:23:46 +01:00
chris . Unsafe_SendEventUnsynced ( t , dmChris , b . Event {
Type : "m.room.avatar" ,
StateKey : ptr ( "" ) ,
Content : map [ string ] interface { } { } ,
} )
2023-07-18 11:00:10 +01:00
t . Log ( "Alice syncs until the DM avatar returns to Chris's most recent avatar." )
res = alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request { } , m . MatchRoomSubscription ( dmChris , m . MatchRoomAvatar ( chris . AvatarURL ) ) )
} )
t . Run ( "Changing DM flag" , func ( t * testing . T ) {
t . Skip ( "TODO: unimplemented" )
t . Log ( "Alice clears the DM flag on Bob's room." )
2023-10-11 12:23:46 +01:00
alice . MustSetGlobalAccountData ( t , "m.direct" , map [ string ] interface { } {
2023-07-18 11:00:10 +01:00
"content" : map [ string ] [ ] string {
bob . UserID : { } , // no dmBob here
chris . UserID : { dmChris , dmBobChris } ,
} ,
} )
t . Log ( "Alice syncs until she sees a new set of account data." )
res = alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request {
Extensions : extensions . Request {
AccountData : & extensions . AccountDataRequest {
extensions . Core { Enabled : & boolTrue } ,
} ,
} ,
} , func ( response * sync3 . Response ) error {
if response . Extensions . AccountData == nil {
return fmt . Errorf ( "no account data yet" )
}
if len ( response . Extensions . AccountData . Global ) == 0 {
return fmt . Errorf ( "no global account data yet" )
}
return nil
} )
t . Log ( "The DM with Bob should no longer be a DM and should no longer have an avatar." )
m . MatchResponse ( t , res , m . MatchRoomSubscription ( dmBob , func ( r sync3 . Room ) error {
if r . IsDM {
return fmt . Errorf ( "dmBob is still a DM" )
}
return m . MatchRoomUnsetAvatar ( ) ( r )
} ) )
t . Log ( "Alice sets the DM flag on Bob's room." )
2023-10-11 12:23:46 +01:00
alice . MustSetGlobalAccountData ( t , "m.direct" , map [ string ] interface { } {
2023-07-18 11:00:10 +01:00
"content" : map [ string ] [ ] string {
bob . UserID : { dmBob } , // dmBob reinstated
chris . UserID : { dmChris , dmBobChris } ,
} ,
} )
t . Log ( "Alice syncs until she sees a new set of account data." )
res = alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request {
Extensions : extensions . Request {
AccountData : & extensions . AccountDataRequest {
extensions . Core { Enabled : & boolTrue } ,
} ,
} ,
} , func ( response * sync3 . Response ) error {
if response . Extensions . AccountData == nil {
return fmt . Errorf ( "no account data yet" )
}
if len ( response . Extensions . AccountData . Global ) == 0 {
return fmt . Errorf ( "no global account data yet" )
}
return nil
} )
t . Log ( "The room should have Bob's avatar again." )
m . MatchResponse ( t , res , m . MatchRoomSubscription ( dmBob , func ( r sync3 . Room ) error {
if ! r . IsDM {
return fmt . Errorf ( "dmBob is still not a DM" )
}
return m . MatchRoomAvatar ( bob . AvatarURL ) ( r )
} ) )
} )
t . Run ( "See avatar when invited" , func ( t * testing . T ) {
t . Log ( "Chris invites Alice to a DM." )
2023-10-11 12:23:46 +01:00
dmInvited := chris . MustCreateRoom ( t , map [ string ] interface { } {
2023-07-18 11:00:10 +01:00
"preset" : "trusted_private_chat" ,
"is_direct" : true ,
"invite" : [ ] string { alice . UserID } ,
} )
t . Log ( "Alice syncs until she sees the invite." )
res = alice . SlidingSyncUntilMembership ( t , res . Pos , dmInvited , alice , "invite" )
2024-01-22 11:33:09 +00:00
t . Log ( "The new room should appear as a DM and use Chris's avatar." )
m . MatchResponse ( t , res , m . MatchRoomSubscription ( dmInvited , m . MatchRoomIsDM ( true ) , m . MatchRoomAvatar ( chris . AvatarURL ) ) )
t . Run ( "Creator of a non-DM never sees an avatar" , func ( t * testing . T ) {
t . Log ( "Alice makes a new room which is not a DM." )
privateGroup := alice . MustCreateRoom ( t , map [ string ] interface { } {
"preset" : "trusted_private_chat" ,
"is_direct" : false ,
} )
t . Log ( "Alice sees the group. It has no avatar." )
res = alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request { } , m . MatchRoomSubscription ( privateGroup , m . MatchRoomUnsetAvatar ( ) ) )
m . MatchResponse ( t , res , m . MatchRoomSubscription ( privateGroup , m . MatchRoomIsDM ( false ) ) )
t . Log ( "Alice invites Bob to the group, who accepts." )
alice . MustInviteRoom ( t , privateGroup , bob . UserID )
bob . MustJoinRoom ( t , privateGroup , nil )
t . Log ( "Alice sees Bob join. The room still has no avatar." )
res = alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request { } , func ( response * sync3 . Response ) error {
matchNoAvatarChange := m . MatchRoomSubscription ( privateGroup , m . MatchRoomUnchangedAvatar ( ) )
if err := matchNoAvatarChange ( response ) ; err != nil {
t . Fatalf ( "Saw group avatar change: %s" , err )
}
matchJoin := m . MatchRoomSubscription ( privateGroup , MatchRoomTimelineMostRecent ( 1 , [ ] Event {
{
Type : "m.room.member" ,
Sender : bob . UserID ,
StateKey : ptr ( bob . UserID ) ,
} ,
} ) )
return matchJoin ( response )
} )
t . Log ( "Alice invites Chris to the group, who accepts." )
alice . MustInviteRoom ( t , privateGroup , chris . UserID )
chris . MustJoinRoom ( t , privateGroup , nil )
t . Log ( "Alice sees Chris join. The room still has no avatar." )
res = alice . SlidingSyncUntil ( t , res . Pos , sync3 . Request { } , func ( response * sync3 . Response ) error {
matchNoAvatarChange := m . MatchRoomSubscription ( privateGroup , m . MatchRoomUnchangedAvatar ( ) )
if err := matchNoAvatarChange ( response ) ; err != nil {
t . Fatalf ( "Saw group avatar change: %s" , err )
}
matchJoin := m . MatchRoomSubscription ( privateGroup , MatchRoomTimelineMostRecent ( 1 , [ ] Event {
{
Type : "m.room.member" ,
Sender : chris . UserID ,
StateKey : ptr ( chris . UserID ) ,
} ,
} ) )
return matchJoin ( response )
} )
} )
2023-07-18 11:00:10 +01:00
} )
}
2024-01-22 10:05:32 +00:00
// Regression test for https://github.com/element-hq/element-x-ios/issues/2003
// Ensure that a group chat with 1 other person has no avatar field set. Only DMs should have this set.
func TestAvatarUnsetInTwoPersonRoom ( t * testing . T ) {
alice := registerNamedUser ( t , "alice" )
bob := registerNamedUser ( t , "bob" )
2024-01-22 10:34:06 +00:00
bobAvatar := alice . UploadContent ( t , smallPNG , "bob.png" , "image/png" )
bob . SetAvatar ( t , bobAvatar )
2024-01-22 10:05:32 +00:00
roomID := alice . MustCreateRoom ( t , map [ string ] interface { } {
"preset" : "trusted_private_chat" ,
"name" : "Nice test room" ,
"invite" : [ ] string { bob . UserID } ,
} )
res := alice . SlidingSync ( t , sync3 . Request {
Lists : map [ string ] sync3 . RequestList {
"a" : {
Ranges : sync3 . SliceRanges { { 0 , 20 } } ,
} ,
} ,
} )
m . MatchResponse ( t , res , m . MatchRoomSubscriptionsStrict ( map [ string ] [ ] m . RoomMatcher {
roomID : {
m . MatchRoomUnsetAvatar ( ) ,
m . MatchInviteCount ( 1 ) ,
m . MatchRoomName ( "Nice test room" ) ,
} ,
} ) )
}