2023-03-31 15:28:03 +01:00
|
|
|
package syncv3_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2023-08-02 10:30:29 +01:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2024-04-11 16:08:33 +01:00
|
|
|
"github.com/matrix-org/complement/b"
|
2023-03-31 15:28:03 +01:00
|
|
|
"github.com/matrix-org/sliding-sync/sync3"
|
|
|
|
"github.com/matrix-org/sliding-sync/sync3/extensions"
|
|
|
|
"github.com/matrix-org/sliding-sync/testutils"
|
|
|
|
"github.com/matrix-org/sliding-sync/testutils/m"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestAccountDataRespectsExtensionScope(t *testing.T) {
|
|
|
|
alice := registerNewUser(t)
|
|
|
|
|
|
|
|
var syncResp *sync3.Response
|
|
|
|
|
|
|
|
// Want at least one test of the initial sync behaviour (which hits `ProcessInitial`)
|
|
|
|
// separate to the incremental sync behaviour (hits `AppendLive`)
|
|
|
|
t.Log("Alice creates rooms 1 and 2.")
|
2023-10-11 12:23:46 +01:00
|
|
|
room1 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"})
|
|
|
|
room2 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"})
|
2023-03-31 15:28:03 +01:00
|
|
|
t.Logf("room1=%s room2=%s", room1, room2)
|
|
|
|
|
|
|
|
t.Log("Alice uploads account data for both rooms, plus global account data.")
|
|
|
|
globalAccountDataEvent := putGlobalAccountData(
|
|
|
|
t,
|
|
|
|
alice,
|
|
|
|
"com.example.global",
|
2023-03-31 17:28:08 +01:00
|
|
|
map[string]interface{}{"global": "GLOBAL!", "version": 1},
|
2023-03-31 15:28:03 +01:00
|
|
|
)
|
|
|
|
putRoomAccountData(
|
|
|
|
t,
|
|
|
|
alice,
|
|
|
|
room1,
|
|
|
|
"com.example.room",
|
2023-03-31 17:28:08 +01:00
|
|
|
map[string]interface{}{"room": 1, "version": 1},
|
2023-03-31 15:28:03 +01:00
|
|
|
)
|
|
|
|
putRoomAccountData(
|
|
|
|
t,
|
|
|
|
alice,
|
|
|
|
room2,
|
|
|
|
"com.example.room",
|
2023-03-31 17:28:08 +01:00
|
|
|
map[string]interface{}{"room": 2, "version": 2},
|
2023-03-31 15:28:03 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
t.Log("Alice makes an initial sync request, requesting global account data only.")
|
|
|
|
syncResp = alice.SlidingSync(t, sync3.Request{
|
|
|
|
Extensions: extensions.Request{
|
|
|
|
AccountData: &extensions.AccountDataRequest{
|
|
|
|
Core: extensions.Core{Enabled: &boolTrue, Lists: []string{}, Rooms: []string{}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Lists: map[string]sync3.RequestList{
|
|
|
|
"window": {
|
|
|
|
Ranges: sync3.SliceRanges{{0, 20}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Log("Alice should see her global account data only.")
|
|
|
|
m.MatchResponse(
|
|
|
|
t,
|
|
|
|
syncResp,
|
2023-03-31 17:28:08 +01:00
|
|
|
m.MatchHasGlobalAccountData(globalAccountDataEvent),
|
2023-03-31 15:28:03 +01:00
|
|
|
m.MatchNoRoomAccountData([]string{room1, room2}),
|
|
|
|
)
|
2023-03-31 17:28:08 +01:00
|
|
|
|
2023-04-03 19:50:17 +01:00
|
|
|
t.Log("Alice updates her global account data.")
|
2023-03-31 17:28:08 +01:00
|
|
|
globalAccountDataEvent = putGlobalAccountData(
|
|
|
|
t,
|
|
|
|
alice,
|
|
|
|
"com.example.global",
|
|
|
|
map[string]interface{}{"global": "GLOBAL!", "version": 2},
|
|
|
|
)
|
2023-04-03 19:50:17 +01:00
|
|
|
|
|
|
|
t.Log("Alice syncs until she sees the new data, requesting—but not expecting—account data for room 2.")
|
|
|
|
syncResp = alice.SlidingSyncUntil(
|
2023-03-31 17:28:08 +01:00
|
|
|
t,
|
2023-04-03 19:50:17 +01:00
|
|
|
syncResp.Pos,
|
|
|
|
sync3.Request{
|
|
|
|
Extensions: extensions.Request{
|
|
|
|
AccountData: &extensions.AccountDataRequest{
|
|
|
|
Core: extensions.Core{Enabled: &boolTrue, Lists: []string{}, Rooms: []string{room2}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
func(response *sync3.Response) error {
|
|
|
|
err := m.MatchNoRoomAccountData([]string{room1, room2})(response)
|
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
return m.MatchHasGlobalAccountData(globalAccountDataEvent)(response)
|
|
|
|
},
|
2023-03-31 17:28:08 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
t.Log("Alice updates account data in both rooms")
|
|
|
|
putRoomAccountData(
|
|
|
|
t,
|
|
|
|
alice,
|
|
|
|
room1,
|
|
|
|
"com.example.room",
|
2023-08-02 12:29:31 +01:00
|
|
|
map[string]interface{}{"room": 1, "version": 11},
|
2023-03-31 17:28:08 +01:00
|
|
|
)
|
|
|
|
room2AccountDataEvent := putRoomAccountData(
|
|
|
|
t,
|
|
|
|
alice,
|
|
|
|
room2,
|
|
|
|
"com.example.room",
|
2023-08-02 12:29:31 +01:00
|
|
|
map[string]interface{}{"room": 2, "version": 22},
|
2023-03-31 17:28:08 +01:00
|
|
|
)
|
2023-04-03 19:50:17 +01:00
|
|
|
|
|
|
|
t.Log("Alice syncs until she sees the account data for room 2. She shouldn't see account data for room 1")
|
|
|
|
syncResp = alice.SlidingSyncUntil(
|
2023-03-31 17:28:08 +01:00
|
|
|
t,
|
2023-04-03 19:50:17 +01:00
|
|
|
syncResp.Pos,
|
|
|
|
sync3.Request{}, // as before
|
|
|
|
func(response *sync3.Response) error {
|
|
|
|
err := m.MatchNoRoomAccountData([]string{room1})(response)
|
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
return m.MatchAccountData(nil, map[string][]json.RawMessage{room2: {room2AccountDataEvent}})(response)
|
|
|
|
},
|
2023-03-31 17:28:08 +01:00
|
|
|
)
|
2023-08-02 10:30:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Regression test for https://github.com/matrix-org/sliding-sync/issues/189
|
|
|
|
func TestAccountDataDoesntDupe(t *testing.T) {
|
|
|
|
alice := registerNewUser(t)
|
|
|
|
alice2 := *alice
|
|
|
|
alice2.Login(t, "password", "device2")
|
|
|
|
|
|
|
|
// send some initial account data
|
|
|
|
putGlobalAccountData(t, alice, "initial", map[string]interface{}{"foo": "bar"})
|
|
|
|
|
|
|
|
// no devices are polling.
|
|
|
|
// syncing with both devices => only shows 1 copy of this event per connection
|
|
|
|
for _, client := range []*CSAPI{alice, &alice2} {
|
|
|
|
res := client.SlidingSync(t, sync3.Request{
|
|
|
|
Extensions: extensions.Request{
|
|
|
|
AccountData: &extensions.AccountDataRequest{
|
|
|
|
Core: extensions.Core{
|
|
|
|
Enabled: &boolTrue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
m.MatchResponse(t, res, MatchGlobalAccountData([]Event{
|
|
|
|
{
|
|
|
|
Type: "m.push_rules",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: "initial",
|
|
|
|
Content: map[string]interface{}{"foo": "bar"},
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
}
|
2023-03-31 17:28:08 +01:00
|
|
|
|
2023-08-02 10:30:29 +01:00
|
|
|
// now both devices are polling, we're going to do the same thing to make sure we only see only 1 copy still.
|
|
|
|
putGlobalAccountData(t, alice, "initial2", map[string]interface{}{"foo2": "bar2"})
|
|
|
|
time.Sleep(time.Second) // TODO: we need to make sure the pollers have seen this and explciitly don't want to use SlidingSyncUntil...
|
|
|
|
var responses []*sync3.Response
|
|
|
|
for _, client := range []*CSAPI{alice, &alice2} {
|
|
|
|
res := client.SlidingSync(t, sync3.Request{
|
|
|
|
Extensions: extensions.Request{
|
|
|
|
AccountData: &extensions.AccountDataRequest{
|
|
|
|
Core: extensions.Core{
|
|
|
|
Enabled: &boolTrue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
m.MatchResponse(t, res, MatchGlobalAccountData([]Event{
|
|
|
|
{
|
|
|
|
Type: "m.push_rules",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: "initial",
|
|
|
|
Content: map[string]interface{}{"foo": "bar"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: "initial2",
|
|
|
|
Content: map[string]interface{}{"foo2": "bar2"},
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
responses = append(responses, res) // we need the pos values later
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we're going to do an incremental sync with account data to make sure we don't see dupes either.
|
|
|
|
putGlobalAccountData(t, alice, "incremental", map[string]interface{}{"foo3": "bar3"})
|
|
|
|
time.Sleep(time.Second) // TODO: we need to make sure the pollers have seen this and explciitly don't want to use SlidingSyncUntil...
|
|
|
|
for i, client := range []*CSAPI{alice, &alice2} {
|
|
|
|
res := client.SlidingSync(t, sync3.Request{
|
|
|
|
Extensions: extensions.Request{
|
|
|
|
AccountData: &extensions.AccountDataRequest{
|
|
|
|
Core: extensions.Core{
|
|
|
|
Enabled: &boolTrue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, WithPos(responses[i].Pos))
|
|
|
|
m.MatchResponse(t, res, MatchGlobalAccountData([]Event{
|
|
|
|
{
|
|
|
|
Type: "incremental",
|
|
|
|
Content: map[string]interface{}{"foo3": "bar3"},
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
}
|
2023-03-31 15:28:03 +01:00
|
|
|
}
|
|
|
|
|
2024-04-11 16:08:33 +01:00
|
|
|
// Regression test for https://github.com/matrix-org/sliding-sync/issues/417
|
|
|
|
func TestAccountDataDoesntDupeWithUpdates(t *testing.T) {
|
|
|
|
alice := registerNewUser(t)
|
|
|
|
roomID := alice.MustCreateRoom(t, map[string]interface{}{
|
|
|
|
"preset": "public_chat",
|
|
|
|
})
|
|
|
|
roomAccountData1 := putRoomAccountData(t, alice, roomID, "foo", map[string]interface{}{"a": "b"})
|
|
|
|
roomAccountData2 := putRoomAccountData(t, alice, roomID, "bar", map[string]interface{}{"a2": "b2"})
|
|
|
|
|
|
|
|
// all_rooms with timeline limit 1 initially
|
|
|
|
res := alice.SlidingSync(t, sync3.Request{
|
|
|
|
Extensions: extensions.Request{
|
|
|
|
AccountData: &extensions.AccountDataRequest{
|
|
|
|
Core: extensions.Core{
|
|
|
|
Enabled: &boolTrue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Lists: map[string]sync3.RequestList{
|
|
|
|
"all_rooms": {
|
|
|
|
Ranges: sync3.SliceRanges{{0, 10}},
|
|
|
|
RoomSubscription: sync3.RoomSubscription{
|
|
|
|
TimelineLimit: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
m.MatchResponse(t, res, m.MatchList("all_rooms", m.MatchV3Count(1)), m.MatchAccountData(nil, map[string][]json.RawMessage{
|
|
|
|
roomID: {roomAccountData1, roomAccountData2},
|
|
|
|
}))
|
|
|
|
|
|
|
|
// now send some events into the room
|
|
|
|
alice.SendTyping(t, roomID, true, 5000)
|
|
|
|
alice.SendEventSynced(t, roomID, b.Event{
|
|
|
|
Type: "custom",
|
|
|
|
Content: map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
// now sync with a visible_rooms list, we should not see duplicate account data (though do expect 2 as we're changing the subscription)
|
|
|
|
res = alice.SlidingSync(t, sync3.Request{
|
|
|
|
Lists: map[string]sync3.RequestList{
|
|
|
|
"all_rooms": {
|
|
|
|
Ranges: sync3.SliceRanges{{0, 10}},
|
|
|
|
RoomSubscription: sync3.RoomSubscription{
|
|
|
|
TimelineLimit: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"visible_rooms": {
|
|
|
|
Ranges: sync3.SliceRanges{{0, 10}},
|
|
|
|
RoomSubscription: sync3.RoomSubscription{
|
|
|
|
TimelineLimit: 10,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, WithPos(res.Pos))
|
|
|
|
m.MatchResponse(t, res, m.MatchList("all_rooms", m.MatchV3Count(1)), m.MatchAccountData(nil, map[string][]json.RawMessage{
|
|
|
|
roomID: {roomAccountData1, roomAccountData2},
|
|
|
|
}))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-03-31 15:28:03 +01:00
|
|
|
// putAccountData is a wrapper around SetGlobalAccountData. It returns the account data
|
|
|
|
// event as a json.RawMessage, automatically including top-level `type` and `content`
|
|
|
|
// fields. This is useful because it can be compared with account data events in a
|
|
|
|
// sync response with bytes.Equal.
|
|
|
|
func putGlobalAccountData(t *testing.T, client *CSAPI, eventType string, content map[string]interface{}) json.RawMessage {
|
|
|
|
t.Helper()
|
2023-10-11 12:23:46 +01:00
|
|
|
client.MustSetGlobalAccountData(t, eventType, content)
|
2023-03-31 15:28:03 +01:00
|
|
|
serialised := testutils.NewAccountData(t, eventType, content)
|
|
|
|
return serialised
|
|
|
|
}
|
|
|
|
|
|
|
|
// putRoomAccountData is like putGlobalAccountData, but for room-specific account data.
|
|
|
|
func putRoomAccountData(t *testing.T, client *CSAPI, roomID, eventType string, content map[string]interface{}) json.RawMessage {
|
|
|
|
t.Helper()
|
2023-10-11 12:23:46 +01:00
|
|
|
client.MustSetRoomAccountData(t, roomID, eventType, content)
|
2023-03-31 15:28:03 +01:00
|
|
|
serialised := testutils.NewAccountData(t, eventType, content)
|
|
|
|
return serialised
|
|
|
|
}
|