sliding-sync/state/invites_table_test.go

288 lines
8.2 KiB
Go
Raw Normal View History

package state
import (
"encoding/json"
"github.com/jmoiron/sqlx"
"github.com/matrix-org/sliding-sync/sqlutil"
"reflect"
"testing"
)
func TestInviteTable(t *testing.T) {
db, close := connectToDB(t)
defer close()
table := NewInvitesTable(db)
alice := "@alice:localhost"
bob := "@bob:localhost"
roomA := "!a:localhost"
roomB := "!b:localhost"
inviteStateA := []json.RawMessage{[]byte(`{"foo":"bar"}`)}
inviteStateB := []json.RawMessage{[]byte(`{"foo":"bar"}`), []byte(`{"baz":"quuz"}`)}
// Add some invites
if err := table.InsertInvite(alice, roomA, inviteStateA); err != nil {
t.Fatalf("failed to InsertInvite: %s", err)
}
if err := table.InsertInvite(bob, roomA, inviteStateB); err != nil {
t.Fatalf("failed to InsertInvite: %s", err)
}
if err := table.InsertInvite(alice, roomB, inviteStateB); err != nil {
t.Fatalf("failed to InsertInvite: %s", err)
}
// Assert alice's invites (multiple)
invites, err := table.SelectAllInvitesForUser(alice)
if err != nil {
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
}
if len(invites) != 2 {
t.Fatalf("got %d invites, want 2", len(invites))
}
if !reflect.DeepEqual(invites[roomA], inviteStateA) {
t.Errorf("room %s got %s want %s", roomA, jsonArrStr(invites[roomA]), jsonArrStr(inviteStateA))
}
if !reflect.DeepEqual(invites[roomB], inviteStateB) {
t.Errorf("room %s got %s want %s", roomA, jsonArrStr(invites[roomA]), jsonArrStr(inviteStateA))
}
// Assert Bob's invites
invites, err = table.SelectAllInvitesForUser(bob)
if err != nil {
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
}
if len(invites) != 1 {
t.Fatalf("got %d invites, want 1", len(invites))
}
if !reflect.DeepEqual(invites[roomA], inviteStateB) {
t.Errorf("room %s got %s want %s", roomA, jsonArrStr(invites[roomA]), jsonArrStr(inviteStateB))
}
add extensions for typing and receipts; bugfixes and additional perf improvements Features: - Add `typing` extension. - Add `receipts` extension. - Add comprehensive prometheus `/metrics` activated via `SYNCV3_PROM`. - Add `SYNCV3_PPROF` support. - Add `by_notification_level` sort order. - Add `include_old_rooms` support. - Add support for `$ME` and `$LAZY`. - Add correct filtering when `*,*` is used as `required_state`. - Add `num_live` to each room response to indicate how many timeline entries are live. Bug fixes: - Use a stricter comparison function on ranges: fixes an issue whereby UTs fail on go1.19 due to change in sorting algorithm. - Send back an `errcode` on HTTP errors (e.g expired sessions). - Remove `unsigned.txn_id` on insertion into the DB. Otherwise other users would see other users txn IDs :( - Improve range delta algorithm: previously it didn't handle cases like `[0,20] -> [20,30]` and would panic. - Send HTTP 400 for invalid range requests. - Don't publish no-op unread counts which just adds extra noise. - Fix leaking DB connections which could eventually consume all available connections. - Ensure we always unblock WaitUntilInitialSync even on invalid access tokens. Other code relies on WaitUntilInitialSync() actually returning at _some_ point e.g on startup we have N workers which bound the number of concurrent pollers made at any one time, we need to not just hog a worker forever. Improvements: - Greatly improve startup times of sync3 handlers by improving `JoinedRoomsTracker`: a modest amount of data would take ~28s to create the handler, now it takes 4s. - Massively improve initial initial v3 sync times, by refactoring `JoinedRoomsTracker`, from ~47s to <1s. - Add `SlidingSyncUntil...` in tests to reduce races. - Tweak the API shape of JoinedUsersForRoom to reduce state block processing time for large rooms from 63s to 39s. - Add trace task for initial syncs. - Include the proxy version in UA strings. - HTTP errors now wait 1s before returning to stop clients tight-looping on error. - Pending event buffer is now 2000. - Index the room ID first to cull the most events when returning timeline entries. Speeds up `SelectLatestEventsBetween` by a factor of 8. - Remove cancelled `m.room_key_requests` from the to-device inbox. Cuts down the amount of events in the inbox by ~94% for very large (20k+) inboxes, ~50% for moderate sized (200 events) inboxes. Adds book-keeping to remember the unacked to-device position for each client.
2022-12-14 18:53:55 +00:00
bobInvite, err := table.SelectInviteState(bob, roomA)
if err != nil {
t.Fatalf("failed to SelectInviteState: %s", err)
}
if !reflect.DeepEqual(bobInvite, inviteStateB) {
t.Errorf("SelectInviteState: got %v want %v", bobInvite, inviteStateB)
}
// Assert no-ones invites
invites, err = table.SelectAllInvitesForUser("no one")
if err != nil {
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
}
if len(invites) != 0 {
t.Fatalf("got %d invites, want 0", len(invites))
}
// Update alice's invite, clobber and re-query (inviteState A -> B)
if err := table.InsertInvite(alice, roomA, inviteStateB); err != nil {
t.Fatalf("failed to InsertInvite: %s", err)
}
invites, err = table.SelectAllInvitesForUser(alice)
if err != nil {
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
}
if !reflect.DeepEqual(invites[roomA], inviteStateB) {
t.Errorf("room %s got %s want %s", roomA, jsonArrStr(invites[roomA]), jsonArrStr(inviteStateB))
}
// Retire one of Alice's invites and re-query
if err = table.RemoveInvite(alice, roomA); err != nil {
t.Fatalf("failed to RemoveInvite: %s", err)
}
invites, err = table.SelectAllInvitesForUser(alice)
if err != nil {
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
}
if len(invites) != 1 {
t.Fatalf("got %v invites, want 1", len(invites))
}
if !reflect.DeepEqual(invites[roomB], inviteStateB) {
t.Errorf("room %s got %s want %s", roomB, jsonArrStr(invites[roomB]), jsonArrStr(inviteStateB))
}
// Retire Bob's non-existent invite
if err = table.RemoveInvite(bob, "!nothing"); err != nil {
t.Fatalf("failed to RemoveInvite: %s", err)
}
invites, err = table.SelectAllInvitesForUser(bob)
if err != nil {
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
}
if len(invites) != 1 {
t.Fatalf("got %v invites, want 1", len(invites))
}
// Retire Bob's invite
if err = table.RemoveInvite(bob, roomA); err != nil {
t.Fatalf("failed to RemoveInvite: %s", err)
}
invites, err = table.SelectAllInvitesForUser(bob)
if err != nil {
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
}
if len(invites) != 0 {
t.Fatalf("got %v invites, want 0", len(invites))
}
// Retire no-ones invite, no-ops
if err = table.RemoveInvite("no one", roomA); err != nil {
t.Fatalf("failed to RemoveInvite: %s", err)
}
}
func TestInviteTable_RemoveSupersededInvites(t *testing.T) {
db, close := connectToDB(t)
defer close()
alice := "@alice:localhost"
bob := "@bob:localhost"
roomA := "!a:localhost"
roomB := "!b:localhost"
inviteState := []json.RawMessage{[]byte(`{"foo":"bar"}`)}
table := NewInvitesTable(db)
t.Log("Invite Alice and Bob to both rooms.")
// Add some invites
if err := table.InsertInvite(alice, roomA, inviteState); err != nil {
t.Fatalf("failed to InsertInvite: %s", err)
}
if err := table.InsertInvite(bob, roomA, inviteState); err != nil {
t.Fatalf("failed to InsertInvite: %s", err)
}
if err := table.InsertInvite(alice, roomB, inviteState); err != nil {
t.Fatalf("failed to InsertInvite: %s", err)
}
if err := table.InsertInvite(bob, roomB, inviteState); err != nil {
t.Fatalf("failed to InsertInvite: %s", err)
}
t.Log("Alice joins room A. Remove her superseded invite.")
newEvents := []Event{
{
Type: "m.room.member",
StateKey: alice,
Membership: "join",
RoomID: roomA,
},
}
err := sqlutil.WithTransaction(db, func(txn *sqlx.Tx) error {
return table.RemoveSupersededInvites(txn, roomA, newEvents)
})
if err != nil {
t.Fatalf("failed to RemoveSupersededInvites: %s", err)
}
t.Log("Alice should still be invited to room B.")
assertInvites(t, table, alice, map[string][]json.RawMessage{roomB: inviteState})
t.Log("Bob should still be invited to rooms A and B.")
assertInvites(t, table, bob, map[string][]json.RawMessage{roomA: inviteState, roomB: inviteState})
t.Log("Bob declines his invitation to room B.")
newEvents = []Event{
{
Type: "m.room.member",
StateKey: bob,
Membership: "leave",
RoomID: roomB,
},
}
err = sqlutil.WithTransaction(db, func(txn *sqlx.Tx) error {
return table.RemoveSupersededInvites(txn, roomB, newEvents)
})
if err != nil {
t.Fatalf("failed to RemoveSupersededInvites: %s", err)
}
t.Log("Alice should still be invited to room B.")
assertInvites(t, table, alice, map[string][]json.RawMessage{roomB: inviteState})
t.Log("Bob should still be invited to room A.")
assertInvites(t, table, bob, map[string][]json.RawMessage{roomA: inviteState})
// Now try multiple membership changes in one call.
t.Log("Alice joins, changes profile, leaves and is re-invited to room B.")
newEvents = []Event{
{
Type: "m.room.member",
StateKey: alice,
Membership: "join",
RoomID: roomB,
},
{
Type: "m.room.member",
StateKey: alice,
Membership: "_join",
RoomID: roomB,
},
{
Type: "m.room.member",
StateKey: alice,
Membership: "leave",
RoomID: roomB,
},
{
Type: "m.room.member",
StateKey: alice,
Membership: "invite",
RoomID: roomB,
},
}
err = sqlutil.WithTransaction(db, func(txn *sqlx.Tx) error {
return table.RemoveSupersededInvites(txn, roomB, newEvents)
})
if err != nil {
t.Fatalf("failed to RemoveSupersededInvites: %s", err)
}
t.Log("Alice should still be invited to room B.")
assertInvites(t, table, alice, map[string][]json.RawMessage{roomB: inviteState})
t.Log("Bob declines, is reinvited to and joins room A.")
newEvents = []Event{
{
Type: "m.room.member",
StateKey: bob,
Membership: "leave",
RoomID: roomA,
},
{
Type: "m.room.member",
StateKey: bob,
Membership: "invite",
RoomID: roomA,
},
{
Type: "m.room.member",
StateKey: bob,
Membership: "join",
RoomID: roomA,
},
}
err = sqlutil.WithTransaction(db, func(txn *sqlx.Tx) error {
return table.RemoveSupersededInvites(txn, roomA, newEvents)
})
if err != nil {
t.Fatalf("failed to RemoveSupersededInvites: %s", err)
}
assertInvites(t, table, bob, map[string][]json.RawMessage{})
}
func assertInvites(t *testing.T, table *InvitesTable, user string, expected map[string][]json.RawMessage) {
invites, err := table.SelectAllInvitesForUser(user)
if err != nil {
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
}
if !reflect.DeepEqual(invites, expected) {
t.Fatalf("got %v invites, want %v", invites, expected)
}
}
func jsonArrStr(a []json.RawMessage) (result string) {
for _, e := range a {
result += string(e) + "\n"
}
return
}