2021-08-20 16:26:11 +01:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
2021-10-07 13:59:53 +01:00
|
|
|
"bytes"
|
2022-04-26 14:42:30 +01:00
|
|
|
"context"
|
2021-08-20 16:26:11 +01:00
|
|
|
"encoding/json"
|
2023-07-17 16:25:28 +01:00
|
|
|
"fmt"
|
2024-04-22 08:55:05 +01:00
|
|
|
"math/rand"
|
2021-08-23 15:40:45 +01:00
|
|
|
"reflect"
|
2023-01-18 14:54:26 +00:00
|
|
|
"sort"
|
2021-08-20 16:26:11 +01:00
|
|
|
"testing"
|
2021-10-26 10:01:45 +01:00
|
|
|
"time"
|
2021-08-20 16:26:11 +01:00
|
|
|
|
2024-04-22 08:55:05 +01:00
|
|
|
"github.com/matrix-org/gomatrixserverlib"
|
2023-11-06 11:54:15 +00:00
|
|
|
"github.com/matrix-org/sliding-sync/sync2"
|
|
|
|
|
2022-03-31 15:10:42 +01:00
|
|
|
"github.com/jmoiron/sqlx"
|
2023-08-31 17:06:44 +01:00
|
|
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
2022-12-15 11:08:50 +00:00
|
|
|
"github.com/matrix-org/sliding-sync/internal"
|
|
|
|
"github.com/matrix-org/sliding-sync/sqlutil"
|
|
|
|
"github.com/matrix-org/sliding-sync/testutils"
|
2022-03-31 15:10:42 +01:00
|
|
|
"github.com/tidwall/gjson"
|
2021-08-20 16:26:11 +01:00
|
|
|
)
|
|
|
|
|
2021-10-07 13:59:53 +01:00
|
|
|
func TestStorageRoomStateBeforeAndAfterEventPosition(t *testing.T) {
|
2022-04-26 14:42:30 +01:00
|
|
|
ctx := context.Background()
|
2021-10-07 13:59:53 +01:00
|
|
|
store := NewStorage(postgresConnectionString)
|
2023-01-18 14:54:26 +00:00
|
|
|
defer store.Teardown()
|
2021-10-07 13:59:53 +01:00
|
|
|
roomID := "!TestStorageRoomStateAfterEventPosition:localhost"
|
|
|
|
alice := "@alice:localhost"
|
|
|
|
bob := "@bob:localhost"
|
|
|
|
events := []json.RawMessage{
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", alice, map[string]interface{}{"creator": alice}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2021-10-07 13:59:53 +01:00
|
|
|
testutils.NewStateEvent(t, "m.room.join_rules", "", alice, map[string]interface{}{"join_rule": "invite"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", bob, alice, map[string]interface{}{"membership": "invite"}),
|
|
|
|
}
|
2023-09-19 12:41:25 +01:00
|
|
|
accResult, err := store.Accumulate(userID, roomID, sync2.TimelineResponse{Events: events})
|
2021-10-07 13:59:53 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Accumulate returned error: %s", err)
|
|
|
|
}
|
2023-09-07 19:21:44 +01:00
|
|
|
latest := accResult.TimelineNIDs[len(accResult.TimelineNIDs)-1]
|
2021-10-07 13:59:53 +01:00
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
getEvents func() []Event
|
|
|
|
wantEvents []json.RawMessage
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "room state after the latest position includes the invite event",
|
|
|
|
getEvents: func() []Event {
|
2022-04-26 14:42:30 +01:00
|
|
|
events, err := store.RoomStateAfterEventPosition(ctx, []string{roomID}, latest, nil)
|
2021-10-07 13:59:53 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("RoomStateAfterEventPosition: %s", err)
|
|
|
|
}
|
2022-04-25 17:12:00 +01:00
|
|
|
return events[roomID]
|
2021-10-07 13:59:53 +01:00
|
|
|
},
|
|
|
|
wantEvents: events[:],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "room state after the latest position filtered for join_rule returns a single event",
|
|
|
|
getEvents: func() []Event {
|
2022-04-26 14:42:30 +01:00
|
|
|
events, err := store.RoomStateAfterEventPosition(ctx, []string{roomID}, latest, map[string][]string{"m.room.join_rules": nil})
|
2021-10-07 13:59:53 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("RoomStateAfterEventPosition: %s", err)
|
|
|
|
}
|
2022-04-25 17:12:00 +01:00
|
|
|
return events[roomID]
|
2021-10-07 13:59:53 +01:00
|
|
|
},
|
|
|
|
wantEvents: []json.RawMessage{
|
|
|
|
events[2],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "room state after the latest position filtered for join_rule and create event excludes member events",
|
|
|
|
getEvents: func() []Event {
|
2022-04-26 14:42:30 +01:00
|
|
|
events, err := store.RoomStateAfterEventPosition(ctx, []string{roomID}, latest, map[string][]string{
|
2022-04-22 12:12:51 +01:00
|
|
|
"m.room.join_rules": []string{""},
|
|
|
|
"m.room.create": nil, // all matching state events with this event type
|
|
|
|
})
|
2021-10-07 13:59:53 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("RoomStateAfterEventPosition: %s", err)
|
|
|
|
}
|
2022-04-25 17:12:00 +01:00
|
|
|
return events[roomID]
|
2021-10-07 13:59:53 +01:00
|
|
|
},
|
|
|
|
wantEvents: []json.RawMessage{
|
|
|
|
events[0], events[2],
|
|
|
|
},
|
|
|
|
},
|
2022-04-22 12:12:51 +01:00
|
|
|
{
|
|
|
|
name: "room state after the latest position filtered for all members returns all member events",
|
|
|
|
getEvents: func() []Event {
|
2022-04-26 14:42:30 +01:00
|
|
|
events, err := store.RoomStateAfterEventPosition(ctx, []string{roomID}, latest, map[string][]string{
|
2022-04-22 12:12:51 +01:00
|
|
|
"m.room.member": nil, // all matching state events with this event type
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("RoomStateAfterEventPosition: %s", err)
|
|
|
|
}
|
2022-04-25 17:12:00 +01:00
|
|
|
return events[roomID]
|
2022-04-22 12:12:51 +01:00
|
|
|
},
|
|
|
|
wantEvents: []json.RawMessage{
|
|
|
|
events[1], events[3],
|
|
|
|
},
|
|
|
|
},
|
2021-10-07 13:59:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
gotEvents := tc.getEvents()
|
|
|
|
if len(gotEvents) != len(tc.wantEvents) {
|
2022-04-22 12:12:51 +01:00
|
|
|
t.Errorf("%s: got %d events want %d : got %+v", tc.name, len(gotEvents), len(tc.wantEvents), gotEvents)
|
2021-10-07 13:59:53 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
for i, eventJSON := range tc.wantEvents {
|
|
|
|
if !bytes.Equal(eventJSON, gotEvents[i].JSON) {
|
|
|
|
t.Errorf("%s: pos %d\ngot %s\nwant %s", tc.name, i, string(gotEvents[i].JSON), string(eventJSON))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-20 16:26:11 +01:00
|
|
|
func TestStorageJoinedRoomsAfterPosition(t *testing.T) {
|
2023-06-05 18:10:35 +01:00
|
|
|
// Clean DB. If we don't, other tests' events will be in the DB, but we won't
|
|
|
|
// provide keys in the metadata dict we pass to MetadataForAllRooms, leading to a
|
|
|
|
// panic.
|
|
|
|
if err := cleanDB(t); err != nil {
|
|
|
|
t.Fatalf("failed to wipe DB: %s", err)
|
|
|
|
}
|
2021-08-20 16:26:11 +01:00
|
|
|
store := NewStorage(postgresConnectionString)
|
2023-01-18 14:54:26 +00:00
|
|
|
defer store.Teardown()
|
2021-08-20 16:26:11 +01:00
|
|
|
joinedRoomID := "!joined:bar"
|
|
|
|
invitedRoomID := "!invited:bar"
|
|
|
|
leftRoomID := "!left:bar"
|
|
|
|
banRoomID := "!ban:bar"
|
|
|
|
bobJoinedRoomID := "!bobjoined:bar"
|
|
|
|
alice := "@aliceTestStorageJoinedRoomsAfterPosition:localhost"
|
|
|
|
bob := "@bobTestStorageJoinedRoomsAfterPosition:localhost"
|
2021-10-27 11:01:28 +01:00
|
|
|
charlie := "@charlieTestStorageJoinedRoomsAfterPosition:localhost"
|
2021-08-20 16:26:11 +01:00
|
|
|
roomIDToEventMap := map[string][]json.RawMessage{
|
|
|
|
joinedRoomID: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", alice, map[string]interface{}{"creator": alice}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2021-08-20 16:26:11 +01:00
|
|
|
},
|
|
|
|
invitedRoomID: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, bob),
|
2021-08-20 16:26:11 +01:00
|
|
|
testutils.NewStateEvent(t, "m.room.member", alice, bob, map[string]interface{}{"membership": "invite"}),
|
|
|
|
},
|
|
|
|
leftRoomID: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", alice, map[string]interface{}{"creator": alice}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2021-08-20 16:26:11 +01:00
|
|
|
testutils.NewStateEvent(t, "m.room.member", alice, alice, map[string]interface{}{"membership": "leave"}),
|
|
|
|
},
|
|
|
|
banRoomID: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, bob),
|
|
|
|
testutils.NewJoinEvent(t, alice),
|
2021-08-20 16:26:11 +01:00
|
|
|
testutils.NewStateEvent(t, "m.room.member", alice, bob, map[string]interface{}{"membership": "ban"}),
|
|
|
|
},
|
|
|
|
bobJoinedRoomID: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, bob),
|
|
|
|
testutils.NewJoinEvent(t, charlie),
|
2021-08-20 16:26:11 +01:00
|
|
|
},
|
|
|
|
}
|
2021-09-23 17:46:34 +01:00
|
|
|
var latestPos int64
|
|
|
|
var err error
|
2021-08-20 16:26:11 +01:00
|
|
|
for roomID, eventMap := range roomIDToEventMap {
|
2023-09-19 12:41:25 +01:00
|
|
|
accResult, err := store.Accumulate(userID, roomID, sync2.TimelineResponse{Events: eventMap})
|
2021-08-20 17:05:17 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Accumulate on %s failed: %s", roomID, err)
|
|
|
|
}
|
2023-09-07 19:21:44 +01:00
|
|
|
latestPos = accResult.TimelineNIDs[len(accResult.TimelineNIDs)-1]
|
2021-08-20 16:26:11 +01:00
|
|
|
}
|
2023-06-06 14:22:51 +01:00
|
|
|
aliceJoinTimingsByRoomID, err := store.JoinedRoomsAfterPosition(alice, latestPos)
|
2021-08-20 16:26:11 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to JoinedRoomsAfterPosition: %s", err)
|
|
|
|
}
|
2023-06-06 14:22:51 +01:00
|
|
|
if len(aliceJoinTimingsByRoomID) != 1 {
|
|
|
|
t.Fatalf("JoinedRoomsAfterPosition at %v for %s got %v, want room %s only", latestPos, alice, aliceJoinTimingsByRoomID, joinedRoomID)
|
2021-08-20 16:26:11 +01:00
|
|
|
}
|
2023-06-06 14:22:51 +01:00
|
|
|
for gotRoomID, _ := range aliceJoinTimingsByRoomID {
|
2023-06-05 14:03:30 +01:00
|
|
|
if gotRoomID != joinedRoomID {
|
|
|
|
t.Fatalf("JoinedRoomsAfterPosition at %v for %s got %v want %v", latestPos, alice, gotRoomID, joinedRoomID)
|
|
|
|
}
|
|
|
|
}
|
2023-06-06 14:22:51 +01:00
|
|
|
bobJoinTimingsByRoomID, err := store.JoinedRoomsAfterPosition(bob, latestPos)
|
2021-08-20 16:26:11 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to JoinedRoomsAfterPosition: %s", err)
|
|
|
|
}
|
2023-06-06 14:22:51 +01:00
|
|
|
if len(bobJoinTimingsByRoomID) != 3 {
|
|
|
|
t.Fatalf("JoinedRoomsAfterPosition for %s got %v rooms want %v", bob, len(bobJoinTimingsByRoomID), 3)
|
2021-08-20 16:26:11 +01:00
|
|
|
}
|
2021-09-30 13:30:49 +01:00
|
|
|
|
2023-01-12 17:11:09 +00:00
|
|
|
// also test currentNotMembershipStateEventsInAllRooms
|
2022-12-14 18:53:55 +00:00
|
|
|
txn := store.DB.MustBeginTx(context.Background(), nil)
|
2023-01-12 17:11:09 +00:00
|
|
|
roomIDToCreateEvents, err := store.currentNotMembershipStateEventsInAllRooms(txn, []string{"m.room.create"})
|
2021-09-30 13:30:49 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("CurrentStateEventsInAllRooms returned error: %s", err)
|
|
|
|
}
|
|
|
|
for roomID := range roomIDToEventMap {
|
|
|
|
if _, ok := roomIDToCreateEvents[roomID]; !ok {
|
|
|
|
t.Fatalf("CurrentStateEventsInAllRooms missed room ID %s", roomID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for roomID := range roomIDToEventMap {
|
|
|
|
createEvents := roomIDToCreateEvents[roomID]
|
|
|
|
if createEvents == nil {
|
|
|
|
t.Errorf("CurrentStateEventsInAllRooms: unknown room %v", roomID)
|
|
|
|
}
|
|
|
|
if len(createEvents) != 1 {
|
|
|
|
t.Fatalf("CurrentStateEventsInAllRooms got %d events, want 1", len(createEvents))
|
|
|
|
}
|
|
|
|
if len(createEvents[0].JSON) < 20 { // make sure there's something here
|
|
|
|
t.Errorf("CurrentStateEventsInAllRooms: got wrong json for event, got %s", string(createEvents[0].JSON))
|
|
|
|
}
|
|
|
|
}
|
2021-10-27 11:01:28 +01:00
|
|
|
|
2023-07-17 10:48:21 +01:00
|
|
|
newMetadata := func(roomID string, joinCount, inviteCount int) internal.RoomMetadata {
|
2023-06-05 14:03:30 +01:00
|
|
|
m := internal.NewRoomMetadata(roomID)
|
|
|
|
m.JoinCount = joinCount
|
2023-07-17 10:48:21 +01:00
|
|
|
m.InviteCount = inviteCount
|
2023-06-05 14:03:30 +01:00
|
|
|
return *m
|
|
|
|
}
|
|
|
|
|
2021-10-27 18:16:43 +01:00
|
|
|
// also test MetadataForAllRooms
|
2023-01-03 14:43:31 +00:00
|
|
|
roomIDToMetadata := map[string]internal.RoomMetadata{
|
2023-07-17 10:48:21 +01:00
|
|
|
joinedRoomID: newMetadata(joinedRoomID, 1, 0),
|
|
|
|
invitedRoomID: newMetadata(invitedRoomID, 1, 1),
|
|
|
|
banRoomID: newMetadata(banRoomID, 1, 0),
|
|
|
|
bobJoinedRoomID: newMetadata(bobJoinedRoomID, 2, 0),
|
2023-01-03 14:43:31 +00:00
|
|
|
}
|
2023-06-05 14:03:30 +01:00
|
|
|
|
perf: improve startup speeds by using temp tables
When the proxy is run with large DBs (10m+ events), the
startup queries are very slow (around 30min to load the initial snapshot.
After much EXPLAIN ANALYZEing, the cause is due to Postgres' query planner
not making good decisions when the the tables are that large. Specifically,
the startup queries need to pull all joined members in all rooms, which
ends up being nearly 50% of the entire events table of 10m rows. When this
query is embedded in a subselect, the query planner assumes that the subselect
will return only a few rows, and decides to pull those rows via an index. In this
particular case, indexes are the wrong choice, as there are SO MANY rows a Seq Scan
is often more appropriate. By using an index (which is a btree), this ends up doing
log(n) operations _per row_ or `O(0.5 * n * log(n))` assuming we pull 50% of the
table of n rows. As n increases, this is increasingly the wrong call over a basic
O(n) seq scan. When n=10m, a seq scan has a cost of 10m, but using indexes has a
cost of 16.6m. By dumping the result of the subselect to a temporary table, this
allows the query planner to notice that using an index is the wrong thing to do,
resulting in better performance. On large DBs, this decreases the startup time
from 30m to ~5m.
2023-05-18 16:45:02 +01:00
|
|
|
tempTableName, err := store.PrepareSnapshot(txn)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("PrepareSnapshot: %s", err)
|
|
|
|
}
|
|
|
|
err = store.MetadataForAllRooms(txn, tempTableName, roomIDToMetadata)
|
2022-12-14 18:53:55 +00:00
|
|
|
txn.Commit()
|
2021-10-27 11:01:28 +01:00
|
|
|
if err != nil {
|
2022-07-29 15:19:20 +01:00
|
|
|
t.Fatalf("MetadataForAllRooms: %s", err)
|
2021-10-27 11:01:28 +01:00
|
|
|
}
|
2021-10-27 18:16:43 +01:00
|
|
|
wantHeroInfos := map[string]internal.RoomMetadata{
|
2021-10-27 11:01:28 +01:00
|
|
|
joinedRoomID: {
|
|
|
|
JoinCount: 1,
|
|
|
|
},
|
|
|
|
invitedRoomID: {
|
|
|
|
JoinCount: 1,
|
|
|
|
InviteCount: 1,
|
|
|
|
},
|
|
|
|
banRoomID: {
|
|
|
|
JoinCount: 1,
|
|
|
|
},
|
|
|
|
bobJoinedRoomID: {
|
|
|
|
JoinCount: 2,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for roomID, wantHI := range wantHeroInfos {
|
2021-10-27 18:16:43 +01:00
|
|
|
gotHI := roomIDToMetadata[roomID]
|
2021-10-27 11:01:28 +01:00
|
|
|
if gotHI.InviteCount != wantHI.InviteCount {
|
|
|
|
t.Errorf("hero info for %s got %d invited users, want %d", roomID, gotHI.InviteCount, wantHI.InviteCount)
|
|
|
|
}
|
|
|
|
if gotHI.JoinCount != wantHI.JoinCount {
|
|
|
|
t.Errorf("hero info for %s got %d joined users, want %d", roomID, gotHI.JoinCount, wantHI.JoinCount)
|
|
|
|
}
|
|
|
|
}
|
2021-08-20 16:26:11 +01:00
|
|
|
}
|
2021-08-20 17:05:17 +01:00
|
|
|
|
|
|
|
// Test the examples on VisibleEventNIDsBetween docs
|
|
|
|
func TestVisibleEventNIDsBetween(t *testing.T) {
|
|
|
|
store := NewStorage(postgresConnectionString)
|
2023-01-18 14:54:26 +00:00
|
|
|
defer store.Teardown()
|
2021-08-20 17:05:17 +01:00
|
|
|
roomA := "!a:localhost"
|
|
|
|
roomB := "!b:localhost"
|
|
|
|
roomC := "!c:localhost"
|
2021-08-23 15:58:37 +01:00
|
|
|
roomD := "!d:localhost"
|
|
|
|
roomE := "!e:localhost"
|
2021-08-20 17:05:17 +01:00
|
|
|
alice := "@alice_TestVisibleEventNIDsBetween:localhost"
|
|
|
|
bob := "@bob_TestVisibleEventNIDsBetween:localhost"
|
|
|
|
|
|
|
|
// bob makes all these rooms first, alice is already joined to C
|
|
|
|
roomIDToEventMap := map[string][]json.RawMessage{
|
|
|
|
roomA: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, bob),
|
2021-08-20 17:05:17 +01:00
|
|
|
},
|
|
|
|
roomB: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, bob),
|
2021-08-20 17:05:17 +01:00
|
|
|
},
|
|
|
|
roomC: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, bob),
|
|
|
|
testutils.NewJoinEvent(t, alice),
|
2021-08-20 17:05:17 +01:00
|
|
|
},
|
2021-08-23 15:58:37 +01:00
|
|
|
roomD: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, bob),
|
2021-08-23 15:58:37 +01:00
|
|
|
},
|
|
|
|
roomE: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, bob),
|
2021-08-23 15:58:37 +01:00
|
|
|
},
|
2021-08-20 17:05:17 +01:00
|
|
|
}
|
|
|
|
for roomID, eventMap := range roomIDToEventMap {
|
2023-04-17 18:21:58 +01:00
|
|
|
_, err := store.Initialise(roomID, eventMap)
|
2021-08-20 17:05:17 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Initialise on %s failed: %s", roomID, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
startPos, err := store.LatestEventNID()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("LatestEventNID: %s", err)
|
|
|
|
}
|
|
|
|
|
2023-08-31 17:06:44 +01:00
|
|
|
baseTimestamp := spec.Timestamp(1632131678061).Time()
|
2021-08-20 17:05:17 +01:00
|
|
|
// Test the examples
|
|
|
|
// Stream Positions
|
|
|
|
// 1 2 3 4 5 6 7 8 9 10
|
|
|
|
// Room A Maj E E E
|
|
|
|
// Room B E Maj E
|
|
|
|
// Room C E Mal E (a already joined to this room)
|
|
|
|
timelineInjections := []struct {
|
|
|
|
RoomID string
|
|
|
|
Events []json.RawMessage
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
RoomID: roomA,
|
|
|
|
Events: []json.RawMessage{
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2021-08-20 17:05:17 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
RoomID: roomB,
|
|
|
|
Events: []json.RawMessage{
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2021-08-20 17:05:17 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
RoomID: roomA,
|
|
|
|
Events: []json.RawMessage{
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2021-08-20 17:05:17 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
RoomID: roomC,
|
|
|
|
Events: []json.RawMessage{
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2021-08-20 17:05:17 +01:00
|
|
|
testutils.NewStateEvent(t, "m.room.member", alice, alice, map[string]interface{}{"membership": "leave"}),
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2021-08-20 17:05:17 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tl := range timelineInjections {
|
2023-09-19 12:41:25 +01:00
|
|
|
accResult, err := store.Accumulate(userID, tl.RoomID, sync2.TimelineResponse{Events: tl.Events})
|
2021-08-20 17:05:17 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Accumulate on %s failed: %s", tl.RoomID, err)
|
|
|
|
}
|
2023-09-07 19:21:44 +01:00
|
|
|
t.Logf("%s added %d new events", tl.RoomID, accResult.NumNew)
|
2021-08-20 17:05:17 +01:00
|
|
|
}
|
|
|
|
latestPos, err := store.LatestEventNID()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("LatestEventNID: %s", err)
|
|
|
|
}
|
2021-08-23 15:58:37 +01:00
|
|
|
t.Logf("ABC Start=%d Latest=%d", startPos, latestPos)
|
2023-11-06 11:54:15 +00:00
|
|
|
roomIDToVisibleRange, err := store.VisibleEventNIDsBetween(alice, startPos, latestPos)
|
2021-08-20 17:05:17 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("VisibleEventNIDsBetween to %d: %s", latestPos, err)
|
|
|
|
}
|
2023-11-06 11:54:15 +00:00
|
|
|
for roomID, r := range roomIDToVisibleRange {
|
|
|
|
t.Logf("%v => [%d,%d]", roomID, r[0]-startPos, r[1]-startPos)
|
2021-08-23 15:40:45 +01:00
|
|
|
}
|
2023-11-06 11:54:15 +00:00
|
|
|
if len(roomIDToVisibleRange) != 3 {
|
|
|
|
t.Errorf("VisibleEventNIDsBetween: wrong number of rooms, want 3 got %+v", roomIDToVisibleRange)
|
2021-08-20 17:05:17 +01:00
|
|
|
}
|
|
|
|
|
2021-10-21 18:27:19 +01:00
|
|
|
// check that we can query subsets too
|
2021-10-22 18:18:02 +01:00
|
|
|
roomIDToVisibleRangesSubset, err := store.visibleEventNIDsBetweenForRooms(alice, []string{roomA, roomB}, startPos, latestPos)
|
2021-10-21 18:27:19 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("VisibleEventNIDsBetweenForRooms to %d: %s", latestPos, err)
|
|
|
|
}
|
|
|
|
if len(roomIDToVisibleRangesSubset) != 2 {
|
2023-11-06 11:54:15 +00:00
|
|
|
t.Errorf("VisibleEventNIDsBetweenForRooms: wrong number of rooms, want 2 got %+v", roomIDToVisibleRange)
|
2021-10-21 18:27:19 +01:00
|
|
|
}
|
|
|
|
|
2021-08-23 15:40:45 +01:00
|
|
|
// For Room A: from=1, to=10, returns { RoomA: [ [1,10] ]} (tests events in joined room)
|
2023-11-06 11:54:15 +00:00
|
|
|
verifyRange(t, roomIDToVisibleRange, roomA, [2]int64{
|
|
|
|
1 + startPos, 10 + startPos,
|
2021-08-23 15:40:45 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
// For Room B: from=1, to=10, returns { RoomB: [ [5,10] ]} (tests joining a room starts events)
|
2023-11-06 11:54:15 +00:00
|
|
|
verifyRange(t, roomIDToVisibleRange, roomB, [2]int64{
|
|
|
|
5 + startPos, 10 + startPos,
|
2021-08-23 15:40:45 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
// For Room C: from=1, to=10, returns { RoomC: [ [0,9] ]} (tests leaving a room stops events)
|
|
|
|
// We start at 0 because it's the earliest event (we were joined since the beginning of the room state)
|
2023-11-06 11:54:15 +00:00
|
|
|
verifyRange(t, roomIDToVisibleRange, roomC, [2]int64{
|
|
|
|
0 + startPos, 9 + startPos,
|
2021-08-23 15:40:45 +01:00
|
|
|
})
|
2021-08-23 15:58:37 +01:00
|
|
|
|
|
|
|
// change the users else we will still have some rooms from A,B,C present if the user is still joined
|
|
|
|
// to those rooms.
|
|
|
|
alice = "@aliceDE:localhost"
|
|
|
|
bob = "@bobDE:localhost"
|
|
|
|
|
|
|
|
// Stream Positions
|
|
|
|
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
|
|
// Room D Maj E Mal E Maj E Mal E
|
|
|
|
// Room E E Mai E E Maj E E
|
|
|
|
timelineInjections = []struct {
|
|
|
|
RoomID string
|
|
|
|
Events []json.RawMessage
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
RoomID: roomD,
|
|
|
|
Events: []json.RawMessage{
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2021-08-23 15:58:37 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
RoomID: roomE,
|
|
|
|
Events: []json.RawMessage{
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2021-08-23 15:58:37 +01:00
|
|
|
testutils.NewStateEvent(t, "m.room.member", alice, bob, map[string]interface{}{"membership": "invite"}),
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp.Add(1*time.Second))),
|
2021-08-23 15:58:37 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
RoomID: roomD,
|
|
|
|
Events: []json.RawMessage{
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2021-08-23 15:58:37 +01:00
|
|
|
testutils.NewStateEvent(t, "m.room.member", alice, alice, map[string]interface{}{"membership": "leave"}),
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp.Add(1*time.Second))),
|
2021-08-23 15:58:37 +01:00
|
|
|
testutils.NewStateEvent(t, "m.room.member", alice, alice, map[string]interface{}{"membership": "leave"}),
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp.Add(1*time.Second))),
|
2021-08-23 15:58:37 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
RoomID: roomE,
|
|
|
|
Events: []json.RawMessage{
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2022-03-28 15:52:25 +01:00
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
|
|
|
testutils.NewEvent(t, "m.room.message", bob, map[string]interface{}{}, testutils.WithTimestamp(baseTimestamp)),
|
2021-08-23 15:58:37 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
startPos, err = store.LatestEventNID()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("LatestEventNID: %s", err)
|
|
|
|
}
|
|
|
|
for _, tl := range timelineInjections {
|
2023-09-19 12:41:25 +01:00
|
|
|
accResult, err := store.Accumulate(userID, tl.RoomID, sync2.TimelineResponse{Events: tl.Events})
|
2021-08-23 15:58:37 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Accumulate on %s failed: %s", tl.RoomID, err)
|
|
|
|
}
|
2023-09-07 19:21:44 +01:00
|
|
|
t.Logf("%s added %d new events", tl.RoomID, accResult.NumNew)
|
2021-08-23 15:58:37 +01:00
|
|
|
}
|
|
|
|
latestPos, err = store.LatestEventNID()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("LatestEventNID: %s", err)
|
|
|
|
}
|
|
|
|
t.Logf("DE Start=%d Latest=%d", startPos, latestPos)
|
2023-11-06 11:54:15 +00:00
|
|
|
roomIDToVisibleRange, err = store.VisibleEventNIDsBetween(alice, startPos, latestPos)
|
2021-08-23 15:58:37 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("VisibleEventNIDsBetween to %d: %s", latestPos, err)
|
|
|
|
}
|
2023-11-06 11:54:15 +00:00
|
|
|
for roomID, r := range roomIDToVisibleRange {
|
|
|
|
t.Logf("%v => [%d,%d]", roomID, r[0]-startPos, r[1]-startPos)
|
2021-08-23 15:58:37 +01:00
|
|
|
}
|
2023-11-06 11:54:15 +00:00
|
|
|
if len(roomIDToVisibleRange) != 2 {
|
|
|
|
t.Errorf("VisibleEventNIDsBetween: wrong number of rooms, want 2 got %+v", roomIDToVisibleRange)
|
2021-08-23 15:58:37 +01:00
|
|
|
}
|
|
|
|
|
2023-11-06 11:54:15 +00:00
|
|
|
// For Room D: from=1, to=15 returns { RoomD: [ 8,10 ] } (tests multi-join/leave)
|
|
|
|
verifyRange(t, roomIDToVisibleRange, roomD, [2]int64{
|
|
|
|
8 + startPos, 10 + startPos,
|
2021-08-23 15:58:37 +01:00
|
|
|
})
|
|
|
|
|
2023-11-06 11:54:15 +00:00
|
|
|
// For Room E: from=1, to=15 returns { RoomE: [ 13,15 ] } (tests invites)
|
|
|
|
verifyRange(t, roomIDToVisibleRange, roomE, [2]int64{
|
|
|
|
13 + startPos, 15 + startPos,
|
2021-08-23 15:58:37 +01:00
|
|
|
})
|
|
|
|
|
2021-08-23 15:40:45 +01:00
|
|
|
}
|
|
|
|
|
2022-03-31 15:10:42 +01:00
|
|
|
func TestStorageLatestEventsInRoomsPrevBatch(t *testing.T) {
|
|
|
|
store := NewStorage(postgresConnectionString)
|
2023-01-18 14:54:26 +00:00
|
|
|
defer store.Teardown()
|
2022-03-31 15:10:42 +01:00
|
|
|
roomID := "!joined:bar"
|
|
|
|
alice := "@alice_TestStorageLatestEventsInRoomsPrevBatch:localhost"
|
|
|
|
stateEvents := []json.RawMessage{
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", alice, map[string]interface{}{"creator": alice}),
|
2022-07-12 15:12:02 +01:00
|
|
|
testutils.NewJoinEvent(t, alice),
|
2022-03-31 15:10:42 +01:00
|
|
|
}
|
|
|
|
timelines := []struct {
|
|
|
|
timeline []json.RawMessage
|
|
|
|
prevBatch string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
prevBatch: "batch A",
|
|
|
|
timeline: []json.RawMessage{
|
|
|
|
testutils.NewEvent(t, "m.room.message", alice, map[string]interface{}{"body": "1"}), // prev batch should be associated with this event
|
|
|
|
testutils.NewEvent(t, "m.room.message", alice, map[string]interface{}{"body": "2"}),
|
|
|
|
testutils.NewEvent(t, "m.room.message", alice, map[string]interface{}{"body": "3"}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
prevBatch: "batch B",
|
|
|
|
timeline: []json.RawMessage{
|
|
|
|
testutils.NewEvent(t, "m.room.message", alice, map[string]interface{}{"body": "4"}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
prevBatch: "batch C",
|
|
|
|
timeline: []json.RawMessage{
|
|
|
|
testutils.NewEvent(t, "m.room.message", alice, map[string]interface{}{"body": "5"}),
|
|
|
|
testutils.NewEvent(t, "m.room.message", alice, map[string]interface{}{"body": "6"}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-04-17 18:21:58 +01:00
|
|
|
_, err := store.Initialise(roomID, stateEvents)
|
2022-03-31 15:10:42 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to initialise: %s", err)
|
|
|
|
}
|
|
|
|
eventIDs := []string{}
|
|
|
|
for _, timeline := range timelines {
|
2023-09-19 12:41:25 +01:00
|
|
|
_, err = store.Accumulate(userID, roomID, sync2.TimelineResponse{Events: timeline.timeline, PrevBatch: timeline.prevBatch})
|
2022-03-31 15:10:42 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to accumulate: %s", err)
|
|
|
|
}
|
|
|
|
for _, ev := range timeline.timeline {
|
|
|
|
eventIDs = append(eventIDs, gjson.ParseBytes(ev).Get("event_id").Str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t.Logf("events: %v", eventIDs)
|
2022-06-08 18:20:10 +01:00
|
|
|
var idsToNIDs map[string]int64
|
2022-03-31 15:10:42 +01:00
|
|
|
sqlutil.WithTransaction(store.EventsTable.db, func(txn *sqlx.Tx) error {
|
2022-06-08 18:20:10 +01:00
|
|
|
idsToNIDs, err = store.EventsTable.SelectNIDsByIDs(txn, eventIDs)
|
2022-03-31 15:10:42 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to get nids for events: %s", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2022-06-08 18:20:10 +01:00
|
|
|
t.Logf("nids: %v", idsToNIDs)
|
2022-03-31 15:10:42 +01:00
|
|
|
wantPrevBatches := []string{
|
|
|
|
// first chunk
|
|
|
|
timelines[0].prevBatch,
|
|
|
|
timelines[1].prevBatch,
|
|
|
|
timelines[1].prevBatch,
|
|
|
|
// second chunk
|
|
|
|
timelines[1].prevBatch,
|
|
|
|
// third chunk
|
|
|
|
timelines[2].prevBatch,
|
|
|
|
"",
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range wantPrevBatches {
|
|
|
|
wantPrevBatch := wantPrevBatches[i]
|
2022-06-08 18:20:10 +01:00
|
|
|
eventNID := idsToNIDs[eventIDs[i]]
|
2022-03-31 15:10:42 +01:00
|
|
|
// closest batch to the last event in the chunk (latest nid) is always the next prev batch token
|
2023-06-19 17:58:56 +01:00
|
|
|
var pb string
|
|
|
|
_ = sqlutil.WithTransaction(store.DB, func(txn *sqlx.Tx) (err error) {
|
|
|
|
pb, err = store.EventsTable.SelectClosestPrevBatch(txn, roomID, eventNID)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to SelectClosestPrevBatch: %s", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2022-03-31 15:10:42 +01:00
|
|
|
if pb != wantPrevBatch {
|
|
|
|
t.Fatalf("SelectClosestPrevBatch: got %v want %v", pb, wantPrevBatch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-18 14:54:26 +00:00
|
|
|
func TestGlobalSnapshot(t *testing.T) {
|
|
|
|
alice := "@TestGlobalSnapshot_alice:localhost"
|
|
|
|
bob := "@TestGlobalSnapshot_bob:localhost"
|
|
|
|
roomAlice := "!alice"
|
|
|
|
roomBob := "!bob"
|
|
|
|
roomAliceBob := "!alicebob"
|
|
|
|
roomSpace := "!space"
|
|
|
|
oldRoomID := "!old"
|
|
|
|
newRoomID := "!new"
|
|
|
|
roomType := "room_type_here"
|
|
|
|
spaceRoomType := "m.space"
|
|
|
|
roomIDToEventMap := map[string][]json.RawMessage{
|
|
|
|
roomAlice: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", alice, map[string]interface{}{"creator": alice, "predecessor": map[string]string{
|
|
|
|
"room_id": oldRoomID,
|
|
|
|
"event_id": "$something",
|
|
|
|
}}),
|
|
|
|
testutils.NewJoinEvent(t, alice),
|
|
|
|
testutils.NewStateEvent(t, "m.room.encryption", "", alice, map[string]interface{}{"algorithm": "m.megolm.v1.aes-sha2"}),
|
|
|
|
},
|
|
|
|
roomBob: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob, "type": roomType}),
|
|
|
|
testutils.NewJoinEvent(t, bob),
|
|
|
|
testutils.NewStateEvent(t, "m.room.name", "", alice, map[string]interface{}{"name": "My Room"}),
|
|
|
|
},
|
|
|
|
roomAliceBob: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob}),
|
|
|
|
testutils.NewJoinEvent(t, bob),
|
|
|
|
testutils.NewJoinEvent(t, alice),
|
|
|
|
testutils.NewStateEvent(t, "m.room.canonical_alias", "", alice, map[string]interface{}{"alias": "#alias"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.tombstone", "", alice, map[string]interface{}{"replacement_room": newRoomID, "body": "yep"}),
|
|
|
|
},
|
|
|
|
roomSpace: {
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", bob, map[string]interface{}{"creator": bob, "type": spaceRoomType}),
|
|
|
|
testutils.NewJoinEvent(t, bob),
|
|
|
|
testutils.NewStateEvent(t, "m.space.child", newRoomID, bob, map[string]interface{}{"via": []string{"somewhere"}}),
|
|
|
|
testutils.NewStateEvent(t, "m.space.child", "!no_via", bob, map[string]interface{}{}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", alice, bob, map[string]interface{}{"membership": "invite"}),
|
|
|
|
},
|
|
|
|
}
|
2023-06-05 18:10:35 +01:00
|
|
|
if err := cleanDB(t); err != nil {
|
2023-01-18 14:54:26 +00:00
|
|
|
t.Fatalf("failed to wipe DB: %s", err)
|
|
|
|
}
|
2023-06-05 18:10:35 +01:00
|
|
|
|
2023-01-18 14:54:26 +00:00
|
|
|
store := NewStorage(postgresConnectionString)
|
|
|
|
defer store.Teardown()
|
|
|
|
for roomID, stateEvents := range roomIDToEventMap {
|
2023-04-17 18:21:58 +01:00
|
|
|
_, err := store.Initialise(roomID, stateEvents)
|
2023-01-18 14:54:26 +00:00
|
|
|
assertNoError(t, err)
|
|
|
|
}
|
|
|
|
snapshot, err := store.GlobalSnapshot()
|
|
|
|
assertNoError(t, err)
|
|
|
|
wantJoinedMembers := map[string][]string{
|
|
|
|
roomAlice: {alice},
|
|
|
|
roomBob: {bob},
|
|
|
|
roomAliceBob: {bob, alice}, // user IDs are ordered by event nid, and bob joined first so he is first
|
|
|
|
roomSpace: {bob},
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(snapshot.AllJoinedMembers, wantJoinedMembers) {
|
|
|
|
t.Errorf("Snapshot.AllJoinedMembers:\ngot: %+v\nwant: %+v", snapshot.AllJoinedMembers, wantJoinedMembers)
|
|
|
|
}
|
|
|
|
wantMetadata := map[string]internal.RoomMetadata{
|
|
|
|
roomAlice: {
|
|
|
|
RoomID: roomAlice,
|
|
|
|
JoinCount: 1,
|
|
|
|
LastMessageTimestamp: gjson.ParseBytes(roomIDToEventMap[roomAlice][len(roomIDToEventMap[roomAlice])-1]).Get("origin_server_ts").Uint(),
|
|
|
|
Heroes: []internal.Hero{{ID: alice}},
|
|
|
|
Encrypted: true,
|
|
|
|
PredecessorRoomID: &oldRoomID,
|
2023-05-12 10:11:25 +01:00
|
|
|
ChildSpaceRooms: make(map[string]struct{}),
|
2023-01-18 14:54:26 +00:00
|
|
|
},
|
|
|
|
roomBob: {
|
|
|
|
RoomID: roomBob,
|
|
|
|
JoinCount: 1,
|
|
|
|
LastMessageTimestamp: gjson.ParseBytes(roomIDToEventMap[roomBob][len(roomIDToEventMap[roomBob])-1]).Get("origin_server_ts").Uint(),
|
|
|
|
Heroes: []internal.Hero{{ID: bob}},
|
|
|
|
NameEvent: "My Room",
|
|
|
|
RoomType: &roomType,
|
2023-05-12 10:11:25 +01:00
|
|
|
ChildSpaceRooms: make(map[string]struct{}),
|
2023-01-18 14:54:26 +00:00
|
|
|
},
|
|
|
|
roomAliceBob: {
|
|
|
|
RoomID: roomAliceBob,
|
|
|
|
JoinCount: 2,
|
|
|
|
LastMessageTimestamp: gjson.ParseBytes(roomIDToEventMap[roomAliceBob][len(roomIDToEventMap[roomAliceBob])-1]).Get("origin_server_ts").Uint(),
|
|
|
|
Heroes: []internal.Hero{{ID: bob}, {ID: alice}},
|
|
|
|
CanonicalAlias: "#alias",
|
|
|
|
UpgradedRoomID: &newRoomID,
|
2023-05-12 10:11:25 +01:00
|
|
|
ChildSpaceRooms: make(map[string]struct{}),
|
2023-01-18 14:54:26 +00:00
|
|
|
},
|
|
|
|
roomSpace: {
|
|
|
|
RoomID: roomSpace,
|
|
|
|
JoinCount: 1,
|
|
|
|
InviteCount: 1,
|
|
|
|
LastMessageTimestamp: gjson.ParseBytes(roomIDToEventMap[roomSpace][len(roomIDToEventMap[roomSpace])-1]).Get("origin_server_ts").Uint(),
|
|
|
|
Heroes: []internal.Hero{{ID: bob}, {ID: alice}},
|
|
|
|
RoomType: &spaceRoomType,
|
|
|
|
ChildSpaceRooms: map[string]struct{}{
|
|
|
|
newRoomID: {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for roomID, want := range wantMetadata {
|
|
|
|
assertRoomMetadata(t, snapshot.GlobalMetadata[roomID], want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-17 10:48:21 +01:00
|
|
|
func TestAllJoinedMembers(t *testing.T) {
|
|
|
|
assertNoError(t, cleanDB(t))
|
2023-07-17 15:55:10 +01:00
|
|
|
store := NewStorage(postgresConnectionString)
|
|
|
|
defer store.Teardown()
|
|
|
|
|
|
|
|
alice := "@alice:localhost"
|
|
|
|
bob := "@bob:localhost"
|
|
|
|
charlie := "@charlie:localhost"
|
|
|
|
doris := "@doris:localhost"
|
|
|
|
eve := "@eve:localhost"
|
|
|
|
frank := "@frank:localhost"
|
|
|
|
|
2023-07-17 16:25:28 +01:00
|
|
|
// Alice is always the creator and the inviter for simplicity's sake
|
2023-07-17 15:55:10 +01:00
|
|
|
testCases := []struct {
|
|
|
|
Name string
|
|
|
|
InitMemberships [][2]string
|
|
|
|
AccumulateMemberships [][2]string
|
2023-07-17 16:25:28 +01:00
|
|
|
RoomID string // tests set this dynamically
|
2023-07-17 15:55:10 +01:00
|
|
|
WantJoined []string
|
|
|
|
WantInvited []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
Name: "basic joined users",
|
|
|
|
InitMemberships: [][2]string{{alice, "join"}},
|
|
|
|
AccumulateMemberships: [][2]string{{bob, "join"}},
|
|
|
|
WantJoined: []string{alice, bob},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "basic invited users",
|
2023-07-17 16:25:28 +01:00
|
|
|
InitMemberships: [][2]string{{alice, "join"}, {charlie, "invite"}},
|
2023-07-17 15:55:10 +01:00
|
|
|
AccumulateMemberships: [][2]string{{bob, "invite"}},
|
|
|
|
WantJoined: []string{alice},
|
2023-07-17 16:25:28 +01:00
|
|
|
WantInvited: []string{bob, charlie},
|
2023-07-17 15:55:10 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "many join/leaves, use latest",
|
|
|
|
InitMemberships: [][2]string{{alice, "join"}, {charlie, "join"}, {frank, "join"}},
|
|
|
|
AccumulateMemberships: [][2]string{{bob, "join"}, {charlie, "leave"}, {frank, "leave"}, {charlie, "join"}, {eve, "join"}},
|
|
|
|
WantJoined: []string{alice, bob, charlie, eve},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "many invites, use latest",
|
|
|
|
InitMemberships: [][2]string{{alice, "join"}, {doris, "join"}},
|
|
|
|
AccumulateMemberships: [][2]string{{doris, "leave"}, {charlie, "invite"}, {doris, "invite"}},
|
|
|
|
WantJoined: []string{alice},
|
|
|
|
WantInvited: []string{charlie, doris},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "invite and rejection in accumulate",
|
|
|
|
InitMemberships: [][2]string{{alice, "join"}},
|
|
|
|
AccumulateMemberships: [][2]string{{frank, "invite"}, {frank, "leave"}},
|
|
|
|
WantJoined: []string{alice},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "invite in initial, rejection in accumulate",
|
|
|
|
InitMemberships: [][2]string{{alice, "join"}, {frank, "invite"}},
|
|
|
|
AccumulateMemberships: [][2]string{{frank, "leave"}},
|
|
|
|
WantJoined: []string{alice},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-07-17 16:25:28 +01:00
|
|
|
serialise := func(memberships [][2]string) []json.RawMessage {
|
|
|
|
var result []json.RawMessage
|
|
|
|
for _, userWithMembership := range memberships {
|
|
|
|
target := userWithMembership[0]
|
|
|
|
sender := userWithMembership[0]
|
|
|
|
membership := userWithMembership[1]
|
|
|
|
if membership == "invite" {
|
|
|
|
// Alice is always the inviter
|
|
|
|
sender = alice
|
|
|
|
}
|
|
|
|
result = append(result, testutils.NewStateEvent(t, "m.room.member", target, sender, map[string]interface{}{
|
|
|
|
"membership": membership,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
return result
|
2023-07-17 15:55:10 +01:00
|
|
|
}
|
|
|
|
|
2023-07-17 16:25:28 +01:00
|
|
|
for i, tc := range testCases {
|
|
|
|
roomID := fmt.Sprintf("!TestAllJoinedMembers_%d:localhost", i)
|
|
|
|
_, err := store.Initialise(roomID, append([]json.RawMessage{
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", alice, map[string]interface{}{
|
|
|
|
"creator": alice, // alice is always the creator
|
|
|
|
}),
|
|
|
|
}, serialise(tc.InitMemberships)...))
|
2023-07-17 15:55:10 +01:00
|
|
|
assertNoError(t, err)
|
2023-07-17 16:25:28 +01:00
|
|
|
|
2023-09-19 12:41:25 +01:00
|
|
|
_, err = store.Accumulate(userID, roomID, sync2.TimelineResponse{
|
2023-09-13 12:46:57 +01:00
|
|
|
Events: serialise(tc.AccumulateMemberships),
|
|
|
|
PrevBatch: "foo",
|
|
|
|
})
|
2023-07-17 16:25:28 +01:00
|
|
|
assertNoError(t, err)
|
|
|
|
testCases[i].RoomID = roomID // remember this for later
|
2023-07-17 15:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// should get all joined members correctly
|
|
|
|
var joinedMembers map[string][]string
|
|
|
|
// should set join/invite counts correctly
|
|
|
|
var roomMetadatas map[string]internal.RoomMetadata
|
|
|
|
err := sqlutil.WithTransaction(store.DB, func(txn *sqlx.Tx) error {
|
|
|
|
tableName, err := store.PrepareSnapshot(txn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
joinedMembers, roomMetadatas, err = store.AllJoinedMembers(txn, tableName)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
assertNoError(t, err)
|
|
|
|
|
2023-07-17 16:25:28 +01:00
|
|
|
for _, tc := range testCases {
|
|
|
|
roomID := tc.RoomID
|
|
|
|
if roomID == "" {
|
|
|
|
t.Fatalf("test case has no room id set: %+v", tc)
|
|
|
|
}
|
|
|
|
// make sure joined members match
|
|
|
|
sort.Strings(joinedMembers[roomID])
|
|
|
|
sort.Strings(tc.WantJoined)
|
|
|
|
if !reflect.DeepEqual(joinedMembers[roomID], tc.WantJoined) {
|
|
|
|
t.Errorf("%v: got joined members %v want %v", tc.Name, joinedMembers[roomID], tc.WantJoined)
|
|
|
|
}
|
|
|
|
// make sure join/invite counts match
|
|
|
|
wantJoined := len(tc.WantJoined)
|
|
|
|
wantInvited := len(tc.WantInvited)
|
|
|
|
metadata, ok := roomMetadatas[roomID]
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("no room metadata for room %v", roomID)
|
|
|
|
}
|
|
|
|
if metadata.InviteCount != wantInvited {
|
|
|
|
t.Errorf("%v: got invite count %d want %d", tc.Name, metadata.InviteCount, wantInvited)
|
|
|
|
}
|
|
|
|
if metadata.JoinCount != wantJoined {
|
|
|
|
t.Errorf("%v: got join count %d want %d", tc.Name, metadata.JoinCount, wantJoined)
|
|
|
|
}
|
|
|
|
}
|
2023-07-17 15:55:10 +01:00
|
|
|
}
|
2023-07-17 10:48:21 +01:00
|
|
|
|
2023-07-19 18:23:09 +01:00
|
|
|
func TestCircularSlice(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
max int
|
|
|
|
appends []int64
|
|
|
|
want []int64 // these get sorted in the test
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "wraparound",
|
|
|
|
max: 5,
|
|
|
|
appends: []int64{9, 8, 7, 6, 5, 4, 3, 2},
|
|
|
|
want: []int64{2, 3, 4, 5, 6},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "exact",
|
|
|
|
max: 5,
|
|
|
|
appends: []int64{9, 8, 7, 6, 5},
|
|
|
|
want: []int64{5, 6, 7, 8, 9},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unfilled",
|
|
|
|
max: 5,
|
|
|
|
appends: []int64{9, 8, 7},
|
|
|
|
want: []int64{7, 8, 9},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "wraparound x2",
|
|
|
|
max: 5,
|
|
|
|
appends: []int64{9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 10},
|
|
|
|
want: []int64{0, 1, 2, 3, 10},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tc := range testCases {
|
2023-09-08 18:16:38 +01:00
|
|
|
cs := &circularSlice[int64]{
|
2023-07-19 18:23:09 +01:00
|
|
|
max: tc.max,
|
|
|
|
}
|
|
|
|
for _, val := range tc.appends {
|
|
|
|
cs.append(val)
|
|
|
|
}
|
|
|
|
sort.Slice(cs.vals, func(i, j int) bool {
|
|
|
|
return cs.vals[i] < cs.vals[j]
|
|
|
|
})
|
|
|
|
if !reflect.DeepEqual(cs.vals, tc.want) {
|
|
|
|
t.Errorf("%s: got %v want %v", tc.name, cs.vals, tc.want)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-11-02 15:47:17 +00:00
|
|
|
func TestStorage_FetchMemberships(t *testing.T) {
|
|
|
|
assertNoError(t, cleanDB(t))
|
|
|
|
store := NewStorage(postgresConnectionString)
|
|
|
|
defer store.Teardown()
|
|
|
|
|
|
|
|
events := []json.RawMessage{
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", "@alice:test", map[string]any{}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", "@alice:test", "@alice:test", map[string]any{"membership": "join"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", "@brian:test", "@alice:test", map[string]any{"membership": "invite"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", "@chris:test", "@chris:test", map[string]any{"membership": "leave"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", "@david:test", "@alice:test", map[string]any{"membership": "ban"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", "@erika:test", "@erika:test", map[string]any{"membership": "join"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", "@frank:test", "@erika:test", map[string]any{"membership": "invite"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", "@glory:test", "@glory:test", map[string]any{"membership": "leave"}),
|
|
|
|
testutils.NewStateEvent(t, "m.room.member", "@helen:test", "@alice:test", map[string]any{"membership": "ban"}),
|
|
|
|
}
|
|
|
|
|
|
|
|
const roomID = "!unimportant"
|
|
|
|
err := sqlutil.WithTransaction(store.DB, func(txn *sqlx.Tx) (err error) {
|
|
|
|
_, err = store.Accumulator.Initialise(roomID, events)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
assertNoError(t, err)
|
|
|
|
|
|
|
|
joins, invites, leaves, err := store.FetchMemberships(roomID)
|
|
|
|
assertNoError(t, err)
|
|
|
|
|
|
|
|
// Do not assume an order from the DB.
|
|
|
|
sort.Slice(joins, func(i, j int) bool {
|
|
|
|
return joins[i] < joins[j]
|
|
|
|
})
|
|
|
|
sort.Slice(invites, func(i, j int) bool {
|
|
|
|
return invites[i] < invites[j]
|
|
|
|
})
|
|
|
|
sort.Slice(leaves, func(i, j int) bool {
|
|
|
|
return leaves[i] < leaves[j]
|
|
|
|
})
|
|
|
|
|
|
|
|
assertValue(t, "joins", joins, []string{"@alice:test", "@erika:test"})
|
|
|
|
assertValue(t, "invites", invites, []string{"@brian:test", "@frank:test"})
|
|
|
|
assertValue(t, "joins", leaves, []string{"@chris:test", "@david:test", "@glory:test", "@helen:test"})
|
|
|
|
}
|
|
|
|
|
2024-04-22 08:55:05 +01:00
|
|
|
type persistOpts struct {
|
|
|
|
withInitialEvents bool
|
|
|
|
numTimelineEvents int
|
|
|
|
ofWhichNumState int
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustPersistEvents(t *testing.T, roomID string, store *Storage, opts persistOpts) {
|
|
|
|
t.Helper()
|
|
|
|
var events []json.RawMessage
|
|
|
|
if opts.withInitialEvents {
|
|
|
|
events = createInitialEvents(t, userID)
|
|
|
|
}
|
|
|
|
numAddedStateEvents := 0
|
|
|
|
for i := 0; i < opts.numTimelineEvents; i++ {
|
|
|
|
var ev json.RawMessage
|
|
|
|
if numAddedStateEvents < opts.ofWhichNumState {
|
|
|
|
numAddedStateEvents++
|
|
|
|
ev = testutils.NewStateEvent(t, "some_kind_of_state", fmt.Sprintf("%d", rand.Int63()), userID, map[string]interface{}{
|
|
|
|
"num": numAddedStateEvents,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
ev = testutils.NewEvent(t, "some_kind_of_message", userID, map[string]interface{}{
|
|
|
|
"msg": "yep",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
events = append(events, ev)
|
|
|
|
}
|
|
|
|
mustAccumulate(t, store, roomID, events)
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustAccumulate(t *testing.T, store *Storage, roomID string, events []json.RawMessage) {
|
|
|
|
t.Helper()
|
|
|
|
_, err := store.Accumulate(userID, roomID, sync2.TimelineResponse{
|
|
|
|
Events: events,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to accumulate: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustHaveNumSnapshots(t *testing.T, db *sqlx.DB, roomID string, numSnapshots int) {
|
|
|
|
t.Helper()
|
|
|
|
var val int
|
|
|
|
err := db.QueryRow(`SELECT count(*) FROM syncv3_snapshots WHERE room_id=$1`, roomID).Scan(&val)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("mustHaveNumSnapshots: %s", err)
|
|
|
|
}
|
|
|
|
if val != numSnapshots {
|
|
|
|
t.Fatalf("mustHaveNumSnapshots: got %d want %d snapshots", val, numSnapshots)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustNotError(t *testing.T, err error) {
|
|
|
|
t.Helper()
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemoveInaccessibleStateSnapshots(t *testing.T) {
|
|
|
|
store := NewStorage(postgresConnectionString)
|
|
|
|
store.MaxTimelineLimit = 50 // we nuke if we have >50+1 snapshots
|
|
|
|
|
|
|
|
roomOnlyMessages := "!TestRemoveInaccessibleStateSnapshots_roomOnlyMessages:localhost"
|
|
|
|
mustPersistEvents(t, roomOnlyMessages, store, persistOpts{
|
|
|
|
withInitialEvents: true,
|
|
|
|
numTimelineEvents: 100,
|
|
|
|
ofWhichNumState: 0,
|
|
|
|
})
|
|
|
|
roomOnlyState := "!TestRemoveInaccessibleStateSnapshots_roomOnlyState:localhost"
|
|
|
|
mustPersistEvents(t, roomOnlyState, store, persistOpts{
|
|
|
|
withInitialEvents: true,
|
|
|
|
numTimelineEvents: 100,
|
|
|
|
ofWhichNumState: 100,
|
|
|
|
})
|
|
|
|
roomPartialStateAndMessages := "!TestRemoveInaccessibleStateSnapshots_roomPartialStateAndMessages:localhost"
|
|
|
|
mustPersistEvents(t, roomPartialStateAndMessages, store, persistOpts{
|
|
|
|
withInitialEvents: true,
|
|
|
|
numTimelineEvents: 100,
|
|
|
|
ofWhichNumState: 30,
|
|
|
|
})
|
|
|
|
roomOverwriteState := "TestRemoveInaccessibleStateSnapshots_roomOverwriteState:localhost"
|
|
|
|
mustPersistEvents(t, roomOverwriteState, store, persistOpts{
|
|
|
|
withInitialEvents: true,
|
|
|
|
})
|
|
|
|
mustAccumulate(t, store, roomOverwriteState, []json.RawMessage{testutils.NewStateEvent(t, "overwrite", "", userID, map[string]interface{}{"val": 1})})
|
|
|
|
mustAccumulate(t, store, roomOverwriteState, []json.RawMessage{testutils.NewStateEvent(t, "overwrite", "", userID, map[string]interface{}{"val": 2})})
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyMessages, 4) // initial state only, one for each state event
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyState, 104) // initial state + 100 state events
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomPartialStateAndMessages, 34) // initial state + 30 state events
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOverwriteState, 6) // initial state + 2 overwrite state events
|
|
|
|
mustNotError(t, store.RemoveInaccessibleStateSnapshots())
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyMessages, 4) // it should not be touched as 4 < 51
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyState, 51) // it should be capped at 51
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomPartialStateAndMessages, 34) // it should not be touched as 34 < 51
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOverwriteState, 6) // it should not be touched as 6 < 51
|
|
|
|
// calling it again does nothing
|
|
|
|
mustNotError(t, store.RemoveInaccessibleStateSnapshots())
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyMessages, 4)
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyState, 51)
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomPartialStateAndMessages, 34)
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOverwriteState, 6) // it should not be touched as 6 < 51
|
|
|
|
// adding one extra state snapshot to each room and repeating RemoveInaccessibleStateSnapshots
|
|
|
|
mustPersistEvents(t, roomOnlyMessages, store, persistOpts{numTimelineEvents: 1, ofWhichNumState: 1})
|
|
|
|
mustPersistEvents(t, roomOnlyState, store, persistOpts{numTimelineEvents: 1, ofWhichNumState: 1})
|
|
|
|
mustPersistEvents(t, roomPartialStateAndMessages, store, persistOpts{numTimelineEvents: 1, ofWhichNumState: 1})
|
|
|
|
mustNotError(t, store.RemoveInaccessibleStateSnapshots())
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyMessages, 5)
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyState, 51) // still capped
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomPartialStateAndMessages, 35)
|
|
|
|
// adding 51 timeline events and repeating RemoveInaccessibleStateSnapshots does nothing
|
|
|
|
mustPersistEvents(t, roomOnlyMessages, store, persistOpts{numTimelineEvents: 51})
|
|
|
|
mustPersistEvents(t, roomOnlyState, store, persistOpts{numTimelineEvents: 51})
|
|
|
|
mustPersistEvents(t, roomPartialStateAndMessages, store, persistOpts{numTimelineEvents: 51})
|
|
|
|
mustNotError(t, store.RemoveInaccessibleStateSnapshots())
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyMessages, 5)
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOnlyState, 51)
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomPartialStateAndMessages, 35)
|
|
|
|
|
|
|
|
// overwrite 52 times and check the current state is 52 (shows we are keeping the right snapshots)
|
|
|
|
for i := 0; i < 52; i++ {
|
|
|
|
mustAccumulate(t, store, roomOverwriteState, []json.RawMessage{testutils.NewStateEvent(t, "overwrite", "", userID, map[string]interface{}{"val": 1 + i})})
|
|
|
|
}
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOverwriteState, 58)
|
|
|
|
mustNotError(t, store.RemoveInaccessibleStateSnapshots())
|
|
|
|
mustHaveNumSnapshots(t, store.DB, roomOverwriteState, 51)
|
|
|
|
roomsTable := NewRoomsTable(store.DB)
|
|
|
|
mustNotError(t, sqlutil.WithTransaction(store.DB, func(txn *sqlx.Tx) error {
|
|
|
|
snapID, err := roomsTable.CurrentAfterSnapshotID(txn, roomOverwriteState)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
state, err := store.StateSnapshot(snapID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// find the 'overwrite' event and make sure the val is 52
|
|
|
|
for _, ev := range state {
|
|
|
|
evv := gjson.ParseBytes(ev)
|
|
|
|
if evv.Get("type").Str != "overwrite" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if evv.Get("content.val").Int() != 52 {
|
|
|
|
return fmt.Errorf("val for overwrite state event was not 52: %v", evv.Raw)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
func createInitialEvents(t *testing.T, creator string) []json.RawMessage {
|
|
|
|
t.Helper()
|
|
|
|
baseTimestamp := time.Now()
|
|
|
|
var pl gomatrixserverlib.PowerLevelContent
|
|
|
|
pl.Defaults()
|
|
|
|
pl.Users = map[string]int64{
|
|
|
|
creator: 100,
|
|
|
|
}
|
|
|
|
// all with the same timestamp as they get made atomically
|
|
|
|
return []json.RawMessage{
|
|
|
|
testutils.NewStateEvent(t, "m.room.create", "", creator, map[string]interface{}{"creator": creator}, testutils.WithTimestamp(baseTimestamp)),
|
|
|
|
testutils.NewJoinEvent(t, creator, testutils.WithTimestamp(baseTimestamp)),
|
|
|
|
testutils.NewStateEvent(t, "m.room.power_levels", "", creator, pl, testutils.WithTimestamp(baseTimestamp)),
|
|
|
|
testutils.NewStateEvent(t, "m.room.join_rules", "", creator, map[string]interface{}{"join_rule": "public"}, testutils.WithTimestamp(baseTimestamp)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-05 18:10:35 +01:00
|
|
|
func cleanDB(t *testing.T) error {
|
|
|
|
// make a fresh DB which is unpolluted from other tests
|
|
|
|
db, close := connectToDB(t)
|
|
|
|
_, err := db.Exec(`
|
|
|
|
DROP TABLE IF EXISTS syncv3_rooms;
|
|
|
|
DROP TABLE IF EXISTS syncv3_invites;
|
|
|
|
DROP TABLE IF EXISTS syncv3_snapshots;
|
|
|
|
DROP TABLE IF EXISTS syncv3_spaces;`)
|
|
|
|
close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-01-18 14:54:26 +00:00
|
|
|
func assertRoomMetadata(t *testing.T, got, want internal.RoomMetadata) {
|
|
|
|
t.Helper()
|
|
|
|
assertValue(t, "CanonicalAlias", got.CanonicalAlias, want.CanonicalAlias)
|
|
|
|
assertValue(t, "ChildSpaceRooms", got.ChildSpaceRooms, want.ChildSpaceRooms)
|
|
|
|
assertValue(t, "Encrypted", got.Encrypted, want.Encrypted)
|
|
|
|
assertValue(t, "Heroes", sortHeroes(got.Heroes), sortHeroes(want.Heroes))
|
|
|
|
assertValue(t, "InviteCount", got.InviteCount, want.InviteCount)
|
|
|
|
assertValue(t, "JoinCount", got.JoinCount, want.JoinCount)
|
|
|
|
assertValue(t, "LastMessageTimestamp", got.LastMessageTimestamp, want.LastMessageTimestamp)
|
|
|
|
assertValue(t, "NameEvent", got.NameEvent, want.NameEvent)
|
|
|
|
assertValue(t, "PredecessorRoomID", got.PredecessorRoomID, want.PredecessorRoomID)
|
|
|
|
assertValue(t, "RoomID", got.RoomID, want.RoomID)
|
|
|
|
assertValue(t, "RoomType", got.RoomType, want.RoomType)
|
|
|
|
assertValue(t, "TypingEvent", got.TypingEvent, want.TypingEvent)
|
|
|
|
assertValue(t, "UpgradedRoomID", got.UpgradedRoomID, want.UpgradedRoomID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func assertValue(t *testing.T, msg string, got, want interface{}) {
|
2023-09-13 14:12:18 +01:00
|
|
|
t.Helper()
|
2023-01-18 14:54:26 +00:00
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
|
|
t.Errorf("%s: got %v want %v", msg, got, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func sortHeroes(heroes []internal.Hero) []internal.Hero {
|
|
|
|
sort.Slice(heroes, func(i, j int) bool {
|
|
|
|
return heroes[i].ID < heroes[j].ID
|
|
|
|
})
|
|
|
|
return heroes
|
|
|
|
}
|
|
|
|
|
2023-11-06 11:54:15 +00:00
|
|
|
func verifyRange(t *testing.T, result map[string][2]int64, roomID string, wantRange [2]int64) {
|
2021-08-23 15:40:45 +01:00
|
|
|
t.Helper()
|
2023-11-06 11:54:15 +00:00
|
|
|
gotRange := result[roomID]
|
|
|
|
if gotRange == [2]int64{} {
|
2021-08-23 15:40:45 +01:00
|
|
|
t.Fatalf("no range was returned for room %s", roomID)
|
|
|
|
}
|
2023-11-06 11:54:15 +00:00
|
|
|
if !reflect.DeepEqual(gotRange, wantRange) {
|
|
|
|
t.Errorf("%s range got %v want %v", roomID, gotRange, wantRange)
|
2021-08-23 15:40:45 +01:00
|
|
|
}
|
2021-08-20 17:05:17 +01:00
|
|
|
}
|