synapse/tests/storage/test_sliding_sync_tables.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

4175 lines
159 KiB
Python
Raw Normal View History

#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2024 New Vector, Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.
#
# Originally licensed under the Apache License, Version 2.0:
# <http://www.apache.org/licenses/LICENSE-2.0>.
#
# [This file includes modifications made by New Vector Limited]
#
#
import logging
from typing import Dict, List, Optional, Tuple, cast
import attr
from parameterized import parameterized
from twisted.test.proto_helpers import MemoryReactor
from synapse.api.constants import EventContentFields, EventTypes, Membership, RoomTypes
from synapse.api.room_versions import RoomVersions
from synapse.events import EventBase, StrippedStateEvent, make_event_from_dict
from synapse.events.snapshot import EventContext
from synapse.rest import admin
from synapse.rest.client import login, room
from synapse.server import HomeServer
from synapse.storage.databases.main.events import DeltaState
from synapse.storage.databases.main.events_bg_updates import _BackgroundUpdates
from synapse.util import Clock
from tests.test_utils.event_injection import create_event
from tests.unittest import HomeserverTestCase
logger = logging.getLogger(__name__)
@attr.s(slots=True, frozen=True, auto_attribs=True)
class _SlidingSyncJoinedRoomResult:
room_id: str
# `event_stream_ordering` is only optional to allow easier semantics when we make
# expected objects from `event.internal_metadata.stream_ordering`. in the tests.
# `event.internal_metadata.stream_ordering` is marked optional because it only
# exists for persisted events but in the context of these tests, we're only working
# with persisted events and we're making comparisons so we will find any mismatch.
event_stream_ordering: Optional[int]
bump_stamp: Optional[int]
room_type: Optional[str]
room_name: Optional[str]
is_encrypted: bool
tombstone_successor_room_id: Optional[str]
@attr.s(slots=True, frozen=True, auto_attribs=True)
class _SlidingSyncMembershipSnapshotResult:
room_id: str
user_id: str
sender: str
membership_event_id: str
membership: str
# `event_stream_ordering` is only optional to allow easier semantics when we make
# expected objects from `event.internal_metadata.stream_ordering`. in the tests.
# `event.internal_metadata.stream_ordering` is marked optional because it only
# exists for persisted events but in the context of these tests, we're only working
# with persisted events and we're making comparisons so we will find any mismatch.
event_stream_ordering: Optional[int]
has_known_state: bool
room_type: Optional[str]
room_name: Optional[str]
is_encrypted: bool
tombstone_successor_room_id: Optional[str]
# Make this default to "not forgotten" because it doesn't apply to many tests and we
# don't want to force all of the tests to deal with it.
forgotten: bool = False
2024-08-27 00:11:56 +03:00
class SlidingSyncTablesTestCaseBase(HomeserverTestCase):
"""
2024-08-27 00:11:56 +03:00
Helpers to deal with testing that the
`sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` database tables are
populated correctly.
"""
servlets = [
admin.register_servlets,
login.register_servlets,
room.register_servlets,
]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.store = hs.get_datastores().main
self.storage_controllers = hs.get_storage_controllers()
persist_events_store = self.hs.get_datastores().persist_events
assert persist_events_store is not None
self.persist_events_store = persist_events_store
def _get_sliding_sync_joined_rooms(self) -> Dict[str, _SlidingSyncJoinedRoomResult]:
"""
Return the rows from the `sliding_sync_joined_rooms` table.
Returns:
Mapping from room_id to _SlidingSyncJoinedRoomResult.
"""
rows = cast(
List[Tuple[str, int, int, str, str, bool, str]],
self.get_success(
self.store.db_pool.simple_select_list(
"sliding_sync_joined_rooms",
None,
retcols=(
"room_id",
"event_stream_ordering",
"bump_stamp",
"room_type",
"room_name",
"is_encrypted",
"tombstone_successor_room_id",
),
),
),
)
return {
row[0]: _SlidingSyncJoinedRoomResult(
room_id=row[0],
event_stream_ordering=row[1],
bump_stamp=row[2],
room_type=row[3],
room_name=row[4],
is_encrypted=bool(row[5]),
tombstone_successor_room_id=row[6],
)
for row in rows
}
def _get_sliding_sync_membership_snapshots(
self,
) -> Dict[Tuple[str, str], _SlidingSyncMembershipSnapshotResult]:
"""
Return the rows from the `sliding_sync_membership_snapshots` table.
Returns:
Mapping from the (room_id, user_id) to _SlidingSyncMembershipSnapshotResult.
"""
rows = cast(
List[Tuple[str, str, str, str, str, int, int, bool, str, str, bool, str]],
self.get_success(
self.store.db_pool.simple_select_list(
"sliding_sync_membership_snapshots",
None,
retcols=(
"room_id",
"user_id",
"sender",
"membership_event_id",
"membership",
"forgotten",
"event_stream_ordering",
"has_known_state",
"room_type",
"room_name",
"is_encrypted",
"tombstone_successor_room_id",
),
),
),
)
return {
(row[0], row[1]): _SlidingSyncMembershipSnapshotResult(
room_id=row[0],
user_id=row[1],
sender=row[2],
membership_event_id=row[3],
membership=row[4],
forgotten=bool(row[5]),
event_stream_ordering=row[6],
has_known_state=bool(row[7]),
room_type=row[8],
room_name=row[9],
is_encrypted=bool(row[10]),
tombstone_successor_room_id=row[11],
)
for row in rows
}
_remote_invite_count: int = 0
def _create_remote_invite_room_for_user(
self,
invitee_user_id: str,
unsigned_invite_room_state: Optional[List[StrippedStateEvent]],
) -> Tuple[str, EventBase]:
"""
Create a fake invite for a remote room and persist it.
We don't have any state for these kind of rooms and can only rely on the
stripped state included in the unsigned portion of the invite event to identify
the room.
Args:
invitee_user_id: The person being invited
unsigned_invite_room_state: List of stripped state events to assist the
receiver in identifying the room.
Returns:
The room ID of the remote invite room and the persisted remote invite event.
"""
invite_room_id = f"!test_room{self._remote_invite_count}:remote_server"
invite_event_dict = {
"room_id": invite_room_id,
"sender": "@inviter:remote_server",
"state_key": invitee_user_id,
"depth": 1,
"origin_server_ts": 1,
"type": EventTypes.Member,
"content": {"membership": Membership.INVITE},
"auth_events": [],
"prev_events": [],
}
if unsigned_invite_room_state is not None:
serialized_stripped_state_events = []
for stripped_event in unsigned_invite_room_state:
serialized_stripped_state_events.append(
{
"type": stripped_event.type,
"state_key": stripped_event.state_key,
"sender": stripped_event.sender,
"content": stripped_event.content,
}
)
invite_event_dict["unsigned"] = {
"invite_room_state": serialized_stripped_state_events
}
invite_event = make_event_from_dict(
invite_event_dict,
room_version=RoomVersions.V10,
)
invite_event.internal_metadata.outlier = True
invite_event.internal_metadata.out_of_band_membership = True
self.get_success(
self.store.maybe_store_room_on_outlier_membership(
room_id=invite_room_id, room_version=invite_event.room_version
)
)
context = EventContext.for_outlier(self.hs.get_storage_controllers())
persist_controller = self.hs.get_storage_controllers().persistence
assert persist_controller is not None
persisted_event, _, _ = self.get_success(
persist_controller.persist_event(invite_event, context)
)
self._remote_invite_count += 1
return invite_room_id, persisted_event
def _retract_remote_invite_for_user(
self,
user_id: str,
remote_room_id: str,
) -> EventBase:
"""
Create a fake invite retraction for a remote room and persist it.
Retracting an invite just means the person is no longer invited to the room.
This is done by someone with proper power levels kicking the user from the room.
A kick shows up as a leave event for a given person with a different `sender`.
Args:
user_id: The person who was invited and we're going to retract the
invite for.
remote_room_id: The room ID that the invite was for.
Returns:
The persisted leave (kick) event.
"""
kick_event_dict = {
"room_id": remote_room_id,
"sender": "@inviter:remote_server",
"state_key": user_id,
"depth": 1,
"origin_server_ts": 1,
"type": EventTypes.Member,
"content": {"membership": Membership.LEAVE},
"auth_events": [],
"prev_events": [],
}
kick_event = make_event_from_dict(
kick_event_dict,
room_version=RoomVersions.V10,
)
kick_event.internal_metadata.outlier = True
kick_event.internal_metadata.out_of_band_membership = True
self.get_success(
self.store.maybe_store_room_on_outlier_membership(
room_id=remote_room_id, room_version=kick_event.room_version
)
)
context = EventContext.for_outlier(self.hs.get_storage_controllers())
persist_controller = self.hs.get_storage_controllers().persistence
assert persist_controller is not None
persisted_event, _, _ = self.get_success(
persist_controller.persist_event(kick_event, context)
)
return persisted_event
2024-08-27 00:11:56 +03:00
class SlidingSyncTablesTestCase(SlidingSyncTablesTestCaseBase):
"""
Tests to make sure the
`sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` database tables are
populated and updated correctly as new events are sent.
"""
def test_joined_room_with_no_info(self) -> None:
"""
Test joined room that doesn't have a room type, encryption, or name shows up in
`sliding_sync_joined_rooms`.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
room_id1 = self.helper.create_room_as(user1_id, tok=user1_tok)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id1)
)
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
# History visibility just happens to be the last event sent in the room
event_stream_ordering=state_map[
(EventTypes.RoomHistoryVisibility, "")
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
},
exact=True,
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_joined_room_with_info(self) -> None:
"""
Test joined encrypted room with name shows up in `sliding_sync_joined_rooms`.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
# Add a room name
self.helper.send_state(
room_id1,
EventTypes.Name,
{"name": "my super duper room"},
tok=user2_tok,
)
# Encrypt the room
self.helper.send_state(
room_id1,
EventTypes.RoomEncryption,
{EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
tok=user2_tok,
)
# Add a tombstone
self.helper.send_state(
room_id1,
EventTypes.Tombstone,
{EventContentFields.TOMBSTONE_SUCCESSOR_ROOM: "another_room"},
tok=user2_tok,
)
# User1 joins the room
self.helper.join(room_id1, user1_id, tok=user1_tok)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id1)
)
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
# This should be whatever is the last event in the room
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
tombstone_successor_room_id="another_room",
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
tombstone_successor_room_id="another_room",
),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
# Even though this room does have a name, is encrypted, and has a
# tombstone, user2 is the room creator and joined at the room creation
# time which didn't have this state set yet.
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_joined_space_room_with_info(self) -> None:
"""
Test joined space room with name shows up in `sliding_sync_joined_rooms`.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
space_room_id = self.helper.create_room_as(
user2_id,
tok=user2_tok,
extra_content={
"creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
},
)
# Add a room name
self.helper.send_state(
space_room_id,
EventTypes.Name,
{"name": "my super duper space"},
tok=user2_tok,
)
# User1 joins the room
user1_join_response = self.helper.join(space_room_id, user1_id, tok=user1_tok)
user1_join_event_pos = self.get_success(
self.store.get_position_for_event(user1_join_response["event_id"])
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(space_room_id)
)
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{space_room_id},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[space_room_id],
_SlidingSyncJoinedRoomResult(
room_id=space_room_id,
event_stream_ordering=user1_join_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(space_room_id, user1_id),
(space_room_id, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=RoomTypes.SPACE,
# Even though this room does have a name, user2 is the room creator and
# joined at the room creation time which didn't have this state set yet.
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_joined_room_with_state_updated(self) -> None:
"""
Test state derived info in `sliding_sync_joined_rooms` is updated when the
current state is updated.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
# Add a room name
self.helper.send_state(
room_id1,
EventTypes.Name,
{"name": "my super duper room"},
tok=user2_tok,
)
# User1 joins the room
user1_join_response = self.helper.join(room_id1, user1_id, tok=user1_tok)
user1_join_event_pos = self.get_success(
self.store.get_position_for_event(user1_join_response["event_id"])
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id1)
)
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
event_stream_ordering=user1_join_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Update the room name
self.helper.send_state(
room_id1,
EventTypes.Name,
{"name": "my super duper room was renamed"},
tok=user2_tok,
)
# Encrypt the room
encrypt_room_response = self.helper.send_state(
room_id1,
EventTypes.RoomEncryption,
{EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
tok=user2_tok,
)
encrypt_room_event_pos = self.get_success(
self.store.get_position_for_event(encrypt_room_response["event_id"])
)
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
# Make sure we see the new room name
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
event_stream_ordering=encrypt_room_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room was renamed",
is_encrypted=True,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_joined_room_is_bumped(self) -> None:
"""
Test that `event_stream_ordering` and `bump_stamp` is updated when a new bump
event is sent (`sliding_sync_joined_rooms`).
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
# Add a room name
self.helper.send_state(
room_id1,
EventTypes.Name,
{"name": "my super duper room"},
tok=user2_tok,
)
# User1 joins the room
user1_join_response = self.helper.join(room_id1, user1_id, tok=user1_tok)
user1_join_event_pos = self.get_success(
self.store.get_position_for_event(user1_join_response["event_id"])
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id1)
)
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
event_stream_ordering=user1_join_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user joined
user1_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
user1_snapshot,
)
# Holds the info according to the current state when the user joined
user2_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
user2_snapshot,
)
# Send a new message to bump the room
event_response = self.helper.send(room_id1, "some message", tok=user1_tok)
event_pos = self.get_success(
self.store.get_position_for_event(event_response["event_id"])
)
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
# Make sure we see the new room name
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
# Updated `event_stream_ordering`
event_stream_ordering=event_pos.stream,
# And since the event was a bump event, the `bump_stamp` should be updated
bump_stamp=event_pos.stream,
# The state is still the same (it didn't change)
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
user1_snapshot,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
user2_snapshot,
)
def test_joined_room_meta_state_reset(self) -> None:
"""
Test that a state reset on the room name is reflected in the
`sliding_sync_joined_rooms` table.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
# Add a room name
self.helper.send_state(
room_id,
EventTypes.Name,
{"name": "my super duper room"},
tok=user2_tok,
)
# User1 joins the room
self.helper.join(room_id, user1_id, tok=user1_tok)
# Make sure we see the new room name
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id},
exact=True,
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id)
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id],
_SlidingSyncJoinedRoomResult(
room_id=room_id,
# This should be whatever is the last event in the room
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id, user1_id),
(room_id, user2_id),
},
exact=True,
)
user1_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
user1_snapshot,
)
# Holds the info according to the current state when the user joined (no room
# name when the room creator joined)
user2_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
user2_snapshot,
)
# Mock a state reset removing the room name state from the current state
message_tuple = self.get_success(
create_event(
self.hs,
prev_event_ids=[state_map[(EventTypes.Name, "")].event_id],
auth_event_ids=[
state_map[(EventTypes.Create, "")].event_id,
state_map[(EventTypes.Member, user1_id)].event_id,
],
type=EventTypes.Message,
content={"body": "foo", "msgtype": "m.text"},
sender=user1_id,
room_id=room_id,
room_version=RoomVersions.V10.identifier,
)
)
event_chunk = [message_tuple]
self.get_success(
self.persist_events_store._persist_events_and_state_updates(
room_id,
event_chunk,
state_delta_for_room=DeltaState(
# This is the state reset part. We're removing the room name state.
to_delete=[(EventTypes.Name, "")],
to_insert={},
),
new_forward_extremities={message_tuple[0].event_id},
use_negative_stream_ordering=False,
inhibit_local_membership_updates=False,
new_event_links={},
)
)
# Make sure the state reset is reflected in the `sliding_sync_joined_rooms` table
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id},
exact=True,
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id)
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id],
_SlidingSyncJoinedRoomResult(
room_id=room_id,
# This should be whatever is the last event in the room
event_stream_ordering=message_tuple[
0
].internal_metadata.stream_ordering,
bump_stamp=message_tuple[0].internal_metadata.stream_ordering,
room_type=None,
# This was state reset back to None
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# State reset shouldn't be reflected in the `sliding_sync_membership_snapshots`
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id, user1_id),
(room_id, user2_id),
},
exact=True,
)
# Snapshots haven't changed
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
user1_snapshot,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
user2_snapshot,
)
def test_non_join_space_room_with_info(self) -> None:
"""
Test users who was invited shows up in `sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
space_room_id = self.helper.create_room_as(
user2_id,
tok=user2_tok,
extra_content={
"creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
},
)
# Add a room name
self.helper.send_state(
space_room_id,
EventTypes.Name,
{"name": "my super duper space"},
tok=user2_tok,
)
# Encrypt the room
self.helper.send_state(
space_room_id,
EventTypes.RoomEncryption,
{EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
tok=user2_tok,
)
# Add a tombstone
self.helper.send_state(
space_room_id,
EventTypes.Tombstone,
{EventContentFields.TOMBSTONE_SUCCESSOR_ROOM: "another_room"},
tok=user2_tok,
)
# User1 is invited to the room
user1_invited_response = self.helper.invite(
space_room_id, src=user2_id, targ=user1_id, tok=user2_tok
)
user1_invited_event_pos = self.get_success(
self.store.get_position_for_event(user1_invited_response["event_id"])
)
# Update the room name after we are invited just to make sure
# we don't update non-join memberships when the room name changes.
rename_response = self.helper.send_state(
space_room_id,
EventTypes.Name,
{"name": "my super duper space was renamed"},
tok=user2_tok,
)
rename_event_pos = self.get_success(
self.store.get_position_for_event(rename_response["event_id"])
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(space_room_id)
)
# User2 is still joined to the room so we should still have an entry in the
# `sliding_sync_joined_rooms` table.
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{space_room_id},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[space_room_id],
_SlidingSyncJoinedRoomResult(
room_id=space_room_id,
event_stream_ordering=rename_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=RoomTypes.SPACE,
room_name="my super duper space was renamed",
is_encrypted=True,
tombstone_successor_room_id="another_room",
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(space_room_id, user1_id),
(space_room_id, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user was invited
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user1_id,
sender=user2_id,
membership_event_id=user1_invited_response["event_id"],
membership=Membership.INVITE,
event_stream_ordering=user1_invited_event_pos.stream,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=True,
tombstone_successor_room_id="another_room",
),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_non_join_invite_ban(self) -> None:
"""
Test users who have invite/ban membership in room shows up in
`sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
user3_id = self.register_user("user3", "pass")
user3_tok = self.login(user3_id, "pass")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 is invited to the room
user1_invited_response = self.helper.invite(
room_id1, src=user2_id, targ=user1_id, tok=user2_tok
)
user1_invited_event_pos = self.get_success(
self.store.get_position_for_event(user1_invited_response["event_id"])
)
# User3 joins the room
self.helper.join(room_id1, user3_id, tok=user3_tok)
# User3 is banned from the room
user3_ban_response = self.helper.ban(
room_id1, src=user2_id, targ=user3_id, tok=user2_tok
)
user3_ban_event_pos = self.get_success(
self.store.get_position_for_event(user3_ban_response["event_id"])
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id1)
)
# User2 is still joined to the room so we should still have an entry
# in the `sliding_sync_joined_rooms` table.
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
event_stream_ordering=user3_ban_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
(room_id1, user3_id),
},
exact=True,
)
# Holds the info according to the current state when the user was invited
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user2_id,
membership_event_id=user1_invited_response["event_id"],
membership=Membership.INVITE,
event_stream_ordering=user1_invited_event_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Holds the info according to the current state when the user was banned
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user3_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user3_id,
sender=user2_id,
membership_event_id=user3_ban_response["event_id"],
membership=Membership.BAN,
event_stream_ordering=user3_ban_event_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_non_join_reject_invite_empty_room(self) -> None:
"""
In a room where no one is joined (`no_longer_in_room`), test rejecting an invite.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 is invited to the room
self.helper.invite(room_id1, src=user2_id, targ=user1_id, tok=user2_tok)
# User2 leaves the room
user2_leave_response = self.helper.leave(room_id1, user2_id, tok=user2_tok)
user2_leave_event_pos = self.get_success(
self.store.get_position_for_event(user2_leave_response["event_id"])
)
# User1 rejects the invite
user1_leave_response = self.helper.leave(room_id1, user1_id, tok=user1_tok)
user1_leave_event_pos = self.get_success(
self.store.get_position_for_event(user1_leave_response["event_id"])
)
# No one is joined to the room
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user left
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user1_id,
membership_event_id=user1_leave_response["event_id"],
membership=Membership.LEAVE,
event_stream_ordering=user1_leave_event_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Holds the info according to the current state when the left
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user2_id,
sender=user2_id,
membership_event_id=user2_leave_response["event_id"],
membership=Membership.LEAVE,
event_stream_ordering=user2_leave_event_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_membership_changing(self) -> None:
"""
Test latest snapshot evolves when membership changes (`sliding_sync_membership_snapshots`).
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 is invited to the room
# ======================================================
user1_invited_response = self.helper.invite(
room_id1, src=user2_id, targ=user1_id, tok=user2_tok
)
user1_invited_event_pos = self.get_success(
self.store.get_position_for_event(user1_invited_response["event_id"])
)
# Update the room name after the user was invited
room_name_update_response = self.helper.send_state(
room_id1,
EventTypes.Name,
{"name": "my super duper room"},
tok=user2_tok,
)
room_name_update_event_pos = self.get_success(
self.store.get_position_for_event(room_name_update_response["event_id"])
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id1)
)
# Assert joined room status
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
# Latest event in the room
event_stream_ordering=room_name_update_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Assert membership snapshots
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user was invited
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user2_id,
membership_event_id=user1_invited_response["event_id"],
membership=Membership.INVITE,
event_stream_ordering=user1_invited_event_pos.stream,
has_known_state=True,
room_type=None,
# Room name was updated after the user was invited so we should still
# see it unset here
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Holds the info according to the current state when the user joined
user2_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
user2_snapshot,
)
# User1 joins the room
# ======================================================
user1_joined_response = self.helper.join(room_id1, user1_id, tok=user1_tok)
user1_joined_event_pos = self.get_success(
self.store.get_position_for_event(user1_joined_response["event_id"])
)
# Assert joined room status
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
# Latest event in the room
event_stream_ordering=user1_joined_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Assert membership snapshots
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user1_id,
membership_event_id=user1_joined_response["event_id"],
membership=Membership.JOIN,
event_stream_ordering=user1_joined_event_pos.stream,
has_known_state=True,
room_type=None,
# We see the update state because the user joined after the room name
# change
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
user2_snapshot,
)
# User1 is banned from the room
# ======================================================
user1_ban_response = self.helper.ban(
room_id1, src=user2_id, targ=user1_id, tok=user2_tok
)
user1_ban_event_pos = self.get_success(
self.store.get_position_for_event(user1_ban_response["event_id"])
)
# Assert joined room status
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id1},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id1],
_SlidingSyncJoinedRoomResult(
room_id=room_id1,
# Latest event in the room
event_stream_ordering=user1_ban_event_pos.stream,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Assert membership snapshots
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user was banned
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user2_id,
membership_event_id=user1_ban_response["event_id"],
membership=Membership.BAN,
event_stream_ordering=user1_ban_event_pos.stream,
has_known_state=True,
room_type=None,
# We see the update state because the user joined after the room name
# change
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
user2_snapshot,
)
def test_non_join_server_left_room(self) -> None:
"""
Test everyone local leaves the room but their leave membership still shows up in
`sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 joins the room
self.helper.join(room_id1, user1_id, tok=user1_tok)
# User2 leaves the room
user2_leave_response = self.helper.leave(room_id1, user2_id, tok=user2_tok)
user2_leave_event_pos = self.get_success(
self.store.get_position_for_event(user2_leave_response["event_id"])
)
# User1 leaves the room
user1_leave_response = self.helper.leave(room_id1, user1_id, tok=user1_tok)
user1_leave_event_pos = self.get_success(
self.store.get_position_for_event(user1_leave_response["event_id"])
)
# No one is joined to the room anymore so we shouldn't have an entry in the
# `sliding_sync_joined_rooms` table.
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
# We should still see rows for the leave events (non-joins)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
sender=user1_id,
membership_event_id=user1_leave_response["event_id"],
membership=Membership.LEAVE,
event_stream_ordering=user1_leave_event_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user2_id,
sender=user2_id,
membership_event_id=user2_leave_response["event_id"],
membership=Membership.LEAVE,
event_stream_ordering=user2_leave_event_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
@parameterized.expand(
[
# No stripped state provided
("none", None),
# Empty stripped state provided
("empty", []),
]
)
def test_non_join_remote_invite_no_stripped_state(
self, _description: str, stripped_state: Optional[List[StrippedStateEvent]]
) -> None:
"""
Test remote invite with no stripped state provided shows up in
`sliding_sync_membership_snapshots` with `has_known_state=False`.
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
# Create a remote invite room without any `unsigned.invite_room_state`
remote_invite_room_id, remote_invite_event = (
self._create_remote_invite_room_for_user(user1_id, stripped_state)
)
# No one local is joined to the remote room
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(remote_invite_room_id, user1_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(remote_invite_room_id, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=remote_invite_room_id,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=remote_invite_event.event_id,
membership=Membership.INVITE,
event_stream_ordering=remote_invite_event.internal_metadata.stream_ordering,
# No stripped state provided
has_known_state=False,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_non_join_remote_invite_unencrypted_room(self) -> None:
"""
Test remote invite with stripped state (unencrypted room) shows up in
`sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
# Create a remote invite room with some `unsigned.invite_room_state`
# indicating that the room is encrypted.
remote_invite_room_id, remote_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
},
),
StrippedStateEvent(
type=EventTypes.Name,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_NAME: "my super duper room",
},
),
],
)
)
# No one local is joined to the remote room
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(remote_invite_room_id, user1_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(remote_invite_room_id, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=remote_invite_room_id,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=remote_invite_event.event_id,
membership=Membership.INVITE,
event_stream_ordering=remote_invite_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_non_join_remote_invite_encrypted_room(self) -> None:
"""
Test remote invite with stripped state (encrypted room) shows up in
`sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
# Create a remote invite room with some `unsigned.invite_room_state`
# indicating that the room is encrypted.
remote_invite_room_id, remote_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
},
),
StrippedStateEvent(
type=EventTypes.RoomEncryption,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2",
},
),
# This is not one of the stripped state events according to the state
# but we still handle it.
StrippedStateEvent(
type=EventTypes.Tombstone,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.TOMBSTONE_SUCCESSOR_ROOM: "another_room",
},
),
# Also test a random event that we don't care about
StrippedStateEvent(
type="org.matrix.foo_state",
state_key="",
sender="@inviter:remote_server",
content={
"foo": "qux",
},
),
],
)
)
# No one local is joined to the remote room
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(remote_invite_room_id, user1_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(remote_invite_room_id, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=remote_invite_room_id,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=remote_invite_event.event_id,
membership=Membership.INVITE,
event_stream_ordering=remote_invite_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=True,
tombstone_successor_room_id="another_room",
),
)
def test_non_join_remote_invite_space_room(self) -> None:
"""
Test remote invite with stripped state (encrypted space room with name) shows up in
`sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
# Create a remote invite room with some `unsigned.invite_room_state`
# indicating that the room is encrypted.
remote_invite_room_id, remote_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
# Specify that it is a space room
EventContentFields.ROOM_TYPE: RoomTypes.SPACE,
},
),
StrippedStateEvent(
type=EventTypes.RoomEncryption,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2",
},
),
StrippedStateEvent(
type=EventTypes.Name,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_NAME: "my super duper space",
},
),
],
)
)
# No one local is joined to the remote room
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(remote_invite_room_id, user1_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(remote_invite_room_id, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=remote_invite_room_id,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=remote_invite_event.event_id,
membership=Membership.INVITE,
event_stream_ordering=remote_invite_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=True,
tombstone_successor_room_id=None,
),
)
def test_non_join_reject_remote_invite(self) -> None:
"""
Test rejected remote invite (user decided to leave the room) inherits meta data
from when the remote invite stripped state and shows up in
`sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
# Create a remote invite room with some `unsigned.invite_room_state`
# indicating that the room is encrypted.
remote_invite_room_id, remote_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
},
),
StrippedStateEvent(
type=EventTypes.RoomEncryption,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2",
},
),
],
)
)
# User1 decides to leave the room (reject the invite)
user1_leave_response = self.helper.leave(
remote_invite_room_id, user1_id, tok=user1_tok
)
user1_leave_pos = self.get_success(
self.store.get_position_for_event(user1_leave_response["event_id"])
)
# No one local is joined to the remote room
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(remote_invite_room_id, user1_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(remote_invite_room_id, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=remote_invite_room_id,
user_id=user1_id,
sender=user1_id,
membership_event_id=user1_leave_response["event_id"],
membership=Membership.LEAVE,
event_stream_ordering=user1_leave_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=True,
tombstone_successor_room_id=None,
),
)
def test_non_join_retracted_remote_invite(self) -> None:
"""
Test retracted remote invite (Remote inviter kicks the person who was invited)
inherits meta data from when the remote invite stripped state and shows up in
`sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
# Create a remote invite room with some `unsigned.invite_room_state`
# indicating that the room is encrypted.
remote_invite_room_id, remote_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
},
),
StrippedStateEvent(
type=EventTypes.RoomEncryption,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2",
},
),
],
)
)
# `@inviter:remote_server` decides to retract the invite (kicks the user).
# (Note: A kick is just a leave event with a different sender)
remote_invite_retraction_event = self._retract_remote_invite_for_user(
user_id=user1_id,
remote_room_id=remote_invite_room_id,
)
# No one local is joined to the remote room
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(remote_invite_room_id, user1_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(remote_invite_room_id, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=remote_invite_room_id,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=remote_invite_retraction_event.event_id,
membership=Membership.LEAVE,
event_stream_ordering=remote_invite_retraction_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=True,
tombstone_successor_room_id=None,
),
)
def test_non_join_state_reset(self) -> None:
"""
Test a state reset that removes someone from the room.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
# Add a room name
self.helper.send_state(
room_id,
EventTypes.Name,
{"name": "my super duper room"},
tok=user2_tok,
)
# User1 joins the room
self.helper.join(room_id, user1_id, tok=user1_tok)
# Make sure we see the new room name
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id},
exact=True,
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id)
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id],
_SlidingSyncJoinedRoomResult(
room_id=room_id,
# This should be whatever is the last event in the room
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id, user1_id),
(room_id, user2_id),
},
exact=True,
)
user1_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
user1_snapshot,
)
# Holds the info according to the current state when the user joined (no room
# name when the room creator joined)
user2_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
user2_snapshot,
)
# Mock a state reset removing the membership for user1 in the current state
message_tuple = self.get_success(
create_event(
self.hs,
prev_event_ids=[state_map[(EventTypes.Name, "")].event_id],
auth_event_ids=[
state_map[(EventTypes.Create, "")].event_id,
state_map[(EventTypes.Member, user1_id)].event_id,
],
type=EventTypes.Message,
content={"body": "foo", "msgtype": "m.text"},
sender=user1_id,
room_id=room_id,
room_version=RoomVersions.V10.identifier,
)
)
event_chunk = [message_tuple]
self.get_success(
self.persist_events_store._persist_events_and_state_updates(
room_id,
event_chunk,
state_delta_for_room=DeltaState(
# This is the state reset part. We're removing the room name state.
to_delete=[(EventTypes.Member, user1_id)],
to_insert={},
),
new_forward_extremities={message_tuple[0].event_id},
use_negative_stream_ordering=False,
inhibit_local_membership_updates=False,
new_event_links={},
)
)
# State reset on membership doesn't affect the`sliding_sync_joined_rooms` table
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id},
exact=True,
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id)
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id],
_SlidingSyncJoinedRoomResult(
room_id=room_id,
# This should be whatever is the last event in the room
event_stream_ordering=message_tuple[
0
].internal_metadata.stream_ordering,
bump_stamp=message_tuple[0].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# State reset on membership should remove the user's snapshot
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
# We shouldn't see user1 in the snapshots table anymore
(room_id, user2_id),
},
exact=True,
)
# Snapshot for user2 hasn't changed
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
user2_snapshot,
)
2024-08-27 00:11:56 +03:00
def test_membership_snapshot_forget(self) -> None:
"""
Test forgetting a room will update `sliding_sync_membership_snapshots`
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 joins the room
self.helper.join(room_id, user1_id, tok=user1_tok)
# User1 leaves the room (we have to leave in order to forget the room)
self.helper.leave(room_id, user1_id, tok=user1_tok)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id)
)
# Check on the `sliding_sync_membership_snapshots` table (nothing should be
# forgotten yet)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id, user1_id),
(room_id, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user joined
user1_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.LEAVE,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
# Room is not forgotten
forgotten=False,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
user1_snapshot,
)
# Holds the info according to the current state when the user joined
user2_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
user2_snapshot,
)
# Forget the room
channel = self.make_request(
"POST",
f"/_matrix/client/r0/rooms/{room_id}/forget",
content={},
access_token=user1_tok,
)
self.assertEqual(channel.code, 200, channel.result)
# Check on the `sliding_sync_membership_snapshots` table
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id, user1_id),
(room_id, user2_id),
},
exact=True,
)
# Room is now forgotten for user1
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
attr.evolve(user1_snapshot, forgotten=True),
)
# Nothing changed for user2
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
user2_snapshot,
)
def test_membership_snapshot_missing_forget(
self,
) -> None:
"""
Test forgetting a room with no existing row in `sliding_sync_membership_snapshots`.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 joins the room
self.helper.join(room_id, user1_id, tok=user1_tok)
# User1 leaves the room (we have to leave in order to forget the room)
self.helper.leave(room_id, user1_id, tok=user1_tok)
# Clean-up the `sliding_sync_membership_snapshots` table as if the inserts did not
# happen during event creation.
self.get_success(
self.store.db_pool.simple_delete_many(
table="sliding_sync_membership_snapshots",
column="room_id",
iterable=(room_id,),
keyvalues={},
desc="sliding_sync_membership_snapshots.test_membership_snapshots_background_update_forgotten_missing",
)
)
# We shouldn't find anything in the table because we just deleted them in
# preparation for the test.
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
set(),
exact=True,
)
# Forget the room
channel = self.make_request(
"POST",
f"/_matrix/client/r0/rooms/{room_id}/forget",
content={},
access_token=user1_tok,
)
self.assertEqual(channel.code, 200, channel.result)
# It doesn't explode
# We still shouldn't find anything in the table because nothing has re-created them
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
set(),
exact=True,
)
class SlidingSyncTablesBackgroundUpdatesTestCase(SlidingSyncTablesTestCaseBase):
"""
Test the background updates that populate the `sliding_sync_joined_rooms` and
`sliding_sync_membership_snapshots` tables.
"""
def test_joined_background_update_missing(self) -> None:
"""
Test that the background update for `sliding_sync_joined_rooms` populates missing rows
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
# Create rooms with various levels of state that should appear in the table
#
room_id_no_info = self.helper.create_room_as(user1_id, tok=user1_tok)
room_id_with_info = self.helper.create_room_as(user1_id, tok=user1_tok)
# Add a room name
self.helper.send_state(
room_id_with_info,
EventTypes.Name,
{"name": "my super duper room"},
tok=user1_tok,
)
# Encrypt the room
self.helper.send_state(
room_id_with_info,
EventTypes.RoomEncryption,
{EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
tok=user1_tok,
)
space_room_id = self.helper.create_room_as(
user1_id,
tok=user1_tok,
extra_content={
"creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
},
)
# Add a room name
self.helper.send_state(
space_room_id,
EventTypes.Name,
{"name": "my super duper space"},
tok=user1_tok,
)
# Clean-up the `sliding_sync_joined_rooms` table as if the inserts did not
# happen during event creation.
self.get_success(
self.store.db_pool.simple_delete_many(
table="sliding_sync_joined_rooms",
column="room_id",
iterable=(room_id_no_info, room_id_with_info, space_room_id),
keyvalues={},
desc="sliding_sync_joined_rooms.test_joined_background_update_missing",
)
)
# We shouldn't find anything in the table because we just deleted them in
# preparation for the test.
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_JOINED_ROOMS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id_no_info, room_id_with_info, space_room_id},
exact=True,
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id_no_info)
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id_no_info],
_SlidingSyncJoinedRoomResult(
room_id=room_id_no_info,
# History visibility just happens to be the last event sent in the room
event_stream_ordering=state_map[
(EventTypes.RoomHistoryVisibility, "")
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id_with_info)
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id_with_info],
_SlidingSyncJoinedRoomResult(
room_id=room_id_with_info,
# Lastest event sent in the room
event_stream_ordering=state_map[
(EventTypes.RoomEncryption, "")
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
tombstone_successor_room_id=None,
),
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(space_room_id)
)
self.assertEqual(
sliding_sync_joined_rooms_results[space_room_id],
_SlidingSyncJoinedRoomResult(
room_id=space_room_id,
# Lastest event sent in the room
event_stream_ordering=state_map[
(EventTypes.Name, "")
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_joined_background_update_partial(self) -> None:
"""
Test that the background update for `sliding_sync_joined_rooms` populates
partially updated rows.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
# Create rooms with various levels of state that should appear in the table
#
room_id_with_info = self.helper.create_room_as(user1_id, tok=user1_tok)
# Add a room name
self.helper.send_state(
room_id_with_info,
EventTypes.Name,
{"name": "my super duper room"},
tok=user1_tok,
)
# Encrypt the room
self.helper.send_state(
room_id_with_info,
EventTypes.RoomEncryption,
{EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
tok=user1_tok,
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id_with_info)
)
# Clean-up the `sliding_sync_joined_rooms` table as if the the encryption event
# never made it into the table.
self.get_success(
self.store.db_pool.simple_update(
table="sliding_sync_joined_rooms",
keyvalues={"room_id": room_id_with_info},
updatevalues={"is_encrypted": False},
desc="sliding_sync_joined_rooms.test_joined_background_update_partial",
)
)
# We should see the partial row that we made in preparation for the test.
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id_with_info},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id_with_info],
_SlidingSyncJoinedRoomResult(
room_id=room_id_with_info,
# Lastest event sent in the room
event_stream_ordering=state_map[
(EventTypes.RoomEncryption, "")
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_JOINED_ROOMS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
{room_id_with_info},
exact=True,
)
self.assertEqual(
sliding_sync_joined_rooms_results[room_id_with_info],
_SlidingSyncJoinedRoomResult(
room_id=room_id_with_info,
# Lastest event sent in the room
event_stream_ordering=state_map[
(EventTypes.RoomEncryption, "")
].internal_metadata.stream_ordering,
bump_stamp=state_map[
(EventTypes.Create, "")
].internal_metadata.stream_ordering,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
tombstone_successor_room_id=None,
),
)
def test_membership_snapshots_background_update_joined(self) -> None:
"""
Test that the background update for `sliding_sync_membership_snapshots`
populates missing rows for join memberships.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
# Create rooms with various levels of state that should appear in the table
#
room_id_no_info = self.helper.create_room_as(user1_id, tok=user1_tok)
room_id_with_info = self.helper.create_room_as(user1_id, tok=user1_tok)
# Add a room name
self.helper.send_state(
room_id_with_info,
EventTypes.Name,
{"name": "my super duper room"},
tok=user1_tok,
)
# Encrypt the room
self.helper.send_state(
room_id_with_info,
EventTypes.RoomEncryption,
{EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
tok=user1_tok,
)
# Add a tombstone
self.helper.send_state(
room_id_with_info,
EventTypes.Tombstone,
{EventContentFields.TOMBSTONE_SUCCESSOR_ROOM: "another_room"},
tok=user1_tok,
)
space_room_id = self.helper.create_room_as(
user1_id,
tok=user1_tok,
extra_content={
"creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
},
)
# Add a room name
self.helper.send_state(
space_room_id,
EventTypes.Name,
{"name": "my super duper space"},
tok=user1_tok,
)
# Clean-up the `sliding_sync_membership_snapshots` table as if the inserts did not
# happen during event creation.
self.get_success(
self.store.db_pool.simple_delete_many(
table="sliding_sync_membership_snapshots",
column="room_id",
iterable=(room_id_no_info, room_id_with_info, space_room_id),
keyvalues={},
desc="sliding_sync_membership_snapshots.test_membership_snapshots_background_update_joined",
)
)
# We shouldn't find anything in the table because we just deleted them in
# preparation for the test.
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
set(),
exact=True,
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id_no_info, user1_id),
(room_id_with_info, user1_id),
(space_room_id, user1_id),
},
exact=True,
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id_no_info)
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id_no_info, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_no_info,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id_with_info)
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(room_id_with_info, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_with_info,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
tombstone_successor_room_id="another_room",
),
)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(space_room_id)
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_membership_snapshots_background_update_local_invite(self) -> None:
"""
Test that the background update for `sliding_sync_membership_snapshots`
populates missing rows for invite memberships.
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
# Create rooms with various levels of state that should appear in the table
#
room_id_no_info = self.helper.create_room_as(user2_id, tok=user2_tok)
room_id_with_info = self.helper.create_room_as(user2_id, tok=user2_tok)
# Add a room name
self.helper.send_state(
room_id_with_info,
EventTypes.Name,
{"name": "my super duper room"},
tok=user2_tok,
)
# Encrypt the room
self.helper.send_state(
room_id_with_info,
EventTypes.RoomEncryption,
{EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
tok=user2_tok,
)
# Add a tombstone
self.helper.send_state(
room_id_with_info,
EventTypes.Tombstone,
{EventContentFields.TOMBSTONE_SUCCESSOR_ROOM: "another_room"},
tok=user2_tok,
)
space_room_id = self.helper.create_room_as(
user1_id,
tok=user2_tok,
extra_content={
"creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
},
)
# Add a room name
self.helper.send_state(
space_room_id,
EventTypes.Name,
{"name": "my super duper space"},
tok=user2_tok,
)
# Invite user1 to the rooms
user1_invite_room_id_no_info_response = self.helper.invite(
room_id_no_info, src=user2_id, targ=user1_id, tok=user2_tok
)
user1_invite_room_id_with_info_response = self.helper.invite(
room_id_with_info, src=user2_id, targ=user1_id, tok=user2_tok
)
user1_invite_space_room_id_response = self.helper.invite(
space_room_id, src=user2_id, targ=user1_id, tok=user2_tok
)
# Have user2 leave the rooms to make sure that our background update is not just
# reading from `current_state_events`. For invite/knock memberships, we should
# be reading from the stripped state on the invite/knock event itself.
self.helper.leave(room_id_no_info, user2_id, tok=user2_tok)
self.helper.leave(room_id_with_info, user2_id, tok=user2_tok)
self.helper.leave(space_room_id, user2_id, tok=user2_tok)
# Check to make sure we actually don't have any `current_state_events` for the rooms
current_state_check_rows = self.get_success(
self.store.db_pool.simple_select_many_batch(
table="current_state_events",
column="room_id",
iterable=[room_id_no_info, room_id_with_info, space_room_id],
retcols=("event_id",),
keyvalues={},
desc="check current_state_events in test",
)
)
self.assertEqual(len(current_state_check_rows), 0)
# Clean-up the `sliding_sync_membership_snapshots` table as if the inserts did not
# happen during event creation.
self.get_success(
self.store.db_pool.simple_delete_many(
table="sliding_sync_membership_snapshots",
column="room_id",
iterable=(room_id_no_info, room_id_with_info, space_room_id),
keyvalues={},
desc="sliding_sync_membership_snapshots.test_membership_snapshots_background_update_local_invite",
)
)
# We shouldn't find anything in the table because we just deleted them in
# preparation for the test.
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
set(),
exact=True,
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
# The invite memberships for user1
(room_id_no_info, user1_id),
(room_id_with_info, user1_id),
(space_room_id, user1_id),
# The leave memberships for user2
(room_id_no_info, user2_id),
(room_id_with_info, user2_id),
(space_room_id, user2_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id_no_info, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_no_info,
user_id=user1_id,
sender=user2_id,
membership_event_id=user1_invite_room_id_no_info_response["event_id"],
membership=Membership.INVITE,
event_stream_ordering=self.get_success(
self.store.get_position_for_event(
user1_invite_room_id_no_info_response["event_id"]
)
).stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(room_id_with_info, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_with_info,
user_id=user1_id,
sender=user2_id,
membership_event_id=user1_invite_room_id_with_info_response["event_id"],
membership=Membership.INVITE,
event_stream_ordering=self.get_success(
self.store.get_position_for_event(
user1_invite_room_id_with_info_response["event_id"]
)
).stream,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
# The tombstone isn't showing here ("another_room") because it's not one
# of the stripped events that we hand out as part of the invite event.
# Even though we handle this scenario from other remote homservers,
# Synapse does not include the tombstone in the invite event.
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user1_id,
sender=user2_id,
membership_event_id=user1_invite_space_room_id_response["event_id"],
membership=Membership.INVITE,
event_stream_ordering=self.get_success(
self.store.get_position_for_event(
user1_invite_space_room_id_response["event_id"]
)
).stream,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_membership_snapshots_background_update_remote_invite(
self,
) -> None:
"""
Test that the background update for `sliding_sync_membership_snapshots`
populates missing rows for remote invites (out-of-band memberships).
"""
user1_id = self.register_user("user1", "pass")
_user1_tok = self.login(user1_id, "pass")
# Create rooms with various levels of state that should appear in the table
#
room_id_unknown_state, room_id_unknown_state_invite_event = (
self._create_remote_invite_room_for_user(user1_id, None)
)
room_id_no_info, room_id_no_info_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
},
),
],
)
)
room_id_with_info, room_id_with_info_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
},
),
StrippedStateEvent(
type=EventTypes.Name,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_NAME: "my super duper room",
},
),
StrippedStateEvent(
type=EventTypes.RoomEncryption,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2",
},
),
],
)
)
space_room_id, space_room_id_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
EventContentFields.ROOM_TYPE: RoomTypes.SPACE,
},
),
StrippedStateEvent(
type=EventTypes.Name,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_NAME: "my super duper space",
},
),
],
)
)
# Clean-up the `sliding_sync_membership_snapshots` table as if the inserts did not
# happen during event creation.
self.get_success(
self.store.db_pool.simple_delete_many(
table="sliding_sync_membership_snapshots",
column="room_id",
iterable=(
room_id_unknown_state,
room_id_no_info,
room_id_with_info,
space_room_id,
),
keyvalues={},
desc="sliding_sync_membership_snapshots.test_membership_snapshots_background_update_remote_invite",
)
)
# We shouldn't find anything in the table because we just deleted them in
# preparation for the test.
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
set(),
exact=True,
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
# The invite memberships for user1
(room_id_unknown_state, user1_id),
(room_id_no_info, user1_id),
(room_id_with_info, user1_id),
(space_room_id, user1_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(room_id_unknown_state, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_unknown_state,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=room_id_unknown_state_invite_event.event_id,
membership=Membership.INVITE,
event_stream_ordering=room_id_unknown_state_invite_event.internal_metadata.stream_ordering,
has_known_state=False,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id_no_info, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_no_info,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=room_id_no_info_invite_event.event_id,
membership=Membership.INVITE,
event_stream_ordering=room_id_no_info_invite_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(room_id_with_info, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_with_info,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=room_id_with_info_invite_event.event_id,
membership=Membership.INVITE,
event_stream_ordering=room_id_with_info_invite_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=space_room_id_invite_event.event_id,
membership=Membership.INVITE,
event_stream_ordering=space_room_id_invite_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_membership_snapshots_background_update_remote_invite_rejections_and_retractions(
self,
) -> None:
"""
Test that the background update for `sliding_sync_membership_snapshots`
populates missing rows for remote invite rejections/retractions (out-of-band memberships).
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
# Create rooms with various levels of state that should appear in the table
#
room_id_unknown_state, room_id_unknown_state_invite_event = (
self._create_remote_invite_room_for_user(user1_id, None)
)
room_id_no_info, room_id_no_info_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
},
),
],
)
)
room_id_with_info, room_id_with_info_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
},
),
StrippedStateEvent(
type=EventTypes.Name,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_NAME: "my super duper room",
},
),
StrippedStateEvent(
type=EventTypes.RoomEncryption,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2",
},
),
],
)
)
space_room_id, space_room_id_invite_event = (
self._create_remote_invite_room_for_user(
user1_id,
[
StrippedStateEvent(
type=EventTypes.Create,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
EventContentFields.ROOM_TYPE: RoomTypes.SPACE,
},
),
StrippedStateEvent(
type=EventTypes.Name,
state_key="",
sender="@inviter:remote_server",
content={
EventContentFields.ROOM_NAME: "my super duper space",
},
),
],
)
)
# Reject the remote invites.
# Also try retracting a remote invite.
room_id_unknown_state_leave_event_response = self.helper.leave(
room_id_unknown_state, user1_id, tok=user1_tok
)
room_id_no_info_leave_event = self._retract_remote_invite_for_user(
user_id=user1_id,
remote_room_id=room_id_no_info,
)
room_id_with_info_leave_event_response = self.helper.leave(
room_id_with_info, user1_id, tok=user1_tok
)
space_room_id_leave_event = self._retract_remote_invite_for_user(
user_id=user1_id,
remote_room_id=space_room_id,
)
# Clean-up the `sliding_sync_membership_snapshots` table as if the inserts did not
# happen during event creation.
self.get_success(
self.store.db_pool.simple_delete_many(
table="sliding_sync_membership_snapshots",
column="room_id",
iterable=(
room_id_unknown_state,
room_id_no_info,
room_id_with_info,
space_room_id,
),
keyvalues={},
desc="sliding_sync_membership_snapshots.test_membership_snapshots_background_update_remote_invite_rejections_and_retractions",
)
)
# We shouldn't find anything in the table because we just deleted them in
# preparation for the test.
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
set(),
exact=True,
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
# The invite memberships for user1
(room_id_unknown_state, user1_id),
(room_id_no_info, user1_id),
(room_id_with_info, user1_id),
(space_room_id, user1_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(room_id_unknown_state, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_unknown_state,
user_id=user1_id,
sender=user1_id,
membership_event_id=room_id_unknown_state_leave_event_response[
"event_id"
],
membership=Membership.LEAVE,
event_stream_ordering=self.get_success(
self.store.get_position_for_event(
room_id_unknown_state_leave_event_response["event_id"]
)
).stream,
has_known_state=False,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id_no_info, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_no_info,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=room_id_no_info_leave_event.event_id,
membership=Membership.LEAVE,
event_stream_ordering=room_id_no_info_leave_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(room_id_with_info, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_with_info,
user_id=user1_id,
sender=user1_id,
membership_event_id=room_id_with_info_leave_event_response["event_id"],
membership=Membership.LEAVE,
event_stream_ordering=self.get_success(
self.store.get_position_for_event(
room_id_with_info_leave_event_response["event_id"]
)
).stream,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user1_id,
sender="@inviter:remote_server",
membership_event_id=space_room_id_leave_event.event_id,
membership=Membership.LEAVE,
event_stream_ordering=space_room_id_leave_event.internal_metadata.stream_ordering,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
@parameterized.expand(
[
# We'll do a kick for this
(Membership.LEAVE,),
(Membership.BAN,),
]
)
def test_membership_snapshots_background_update_historical_state(
self, test_membership: str
) -> None:
"""
Test that the background update for `sliding_sync_membership_snapshots`
populates missing rows for leave memberships.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
# Create rooms with various levels of state that should appear in the table
#
room_id_no_info = self.helper.create_room_as(user2_id, tok=user2_tok)
room_id_with_info = self.helper.create_room_as(user2_id, tok=user2_tok)
# Add a room name
self.helper.send_state(
room_id_with_info,
EventTypes.Name,
{"name": "my super duper room"},
tok=user2_tok,
)
# Encrypt the room
self.helper.send_state(
room_id_with_info,
EventTypes.RoomEncryption,
{EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
tok=user2_tok,
)
# Add a tombstone
self.helper.send_state(
room_id_with_info,
EventTypes.Tombstone,
{EventContentFields.TOMBSTONE_SUCCESSOR_ROOM: "another_room"},
tok=user2_tok,
)
space_room_id = self.helper.create_room_as(
user1_id,
tok=user2_tok,
extra_content={
"creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
},
)
# Add a room name
self.helper.send_state(
space_room_id,
EventTypes.Name,
{"name": "my super duper space"},
tok=user2_tok,
)
# Join the room in preparation for our test_membership
self.helper.join(room_id_no_info, user1_id, tok=user1_tok)
self.helper.join(room_id_with_info, user1_id, tok=user1_tok)
self.helper.join(space_room_id, user1_id, tok=user1_tok)
if test_membership == Membership.LEAVE:
# Kick user1 from the rooms
user1_membership_room_id_no_info_response = self.helper.change_membership(
room=room_id_no_info,
src=user2_id,
targ=user1_id,
tok=user2_tok,
membership=Membership.LEAVE,
extra_data={
"reason": "Bad manners",
},
)
user1_membership_room_id_with_info_response = self.helper.change_membership(
room=room_id_with_info,
src=user2_id,
targ=user1_id,
tok=user2_tok,
membership=Membership.LEAVE,
extra_data={
"reason": "Bad manners",
},
)
user1_membership_space_room_id_response = self.helper.change_membership(
room=space_room_id,
src=user2_id,
targ=user1_id,
tok=user2_tok,
membership=Membership.LEAVE,
extra_data={
"reason": "Bad manners",
},
)
elif test_membership == Membership.BAN:
# Ban user1 from the rooms
user1_membership_room_id_no_info_response = self.helper.ban(
room_id_no_info, src=user2_id, targ=user1_id, tok=user2_tok
)
user1_membership_room_id_with_info_response = self.helper.ban(
room_id_with_info, src=user2_id, targ=user1_id, tok=user2_tok
)
user1_membership_space_room_id_response = self.helper.ban(
space_room_id, src=user2_id, targ=user1_id, tok=user2_tok
)
else:
raise AssertionError("Unknown test_membership")
# Have user2 leave the rooms to make sure that our background update is not just
# reading from `current_state_events`. For leave memberships, we should be
# reading from the historical state.
self.helper.leave(room_id_no_info, user2_id, tok=user2_tok)
self.helper.leave(room_id_with_info, user2_id, tok=user2_tok)
self.helper.leave(space_room_id, user2_id, tok=user2_tok)
# Check to make sure we actually don't have any `current_state_events` for the rooms
current_state_check_rows = self.get_success(
self.store.db_pool.simple_select_many_batch(
table="current_state_events",
column="room_id",
iterable=[room_id_no_info, room_id_with_info, space_room_id],
retcols=("event_id",),
keyvalues={},
desc="check current_state_events in test",
)
)
self.assertEqual(len(current_state_check_rows), 0)
# Clean-up the `sliding_sync_membership_snapshots` table as if the inserts did not
# happen during event creation.
self.get_success(
self.store.db_pool.simple_delete_many(
table="sliding_sync_membership_snapshots",
column="room_id",
iterable=(room_id_no_info, room_id_with_info, space_room_id),
keyvalues={},
desc="sliding_sync_membership_snapshots.test_membership_snapshots_background_update_historical_state",
)
)
# We shouldn't find anything in the table because we just deleted them in
# preparation for the test.
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
set(),
exact=True,
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
# The memberships for user1
(room_id_no_info, user1_id),
(room_id_with_info, user1_id),
(space_room_id, user1_id),
# The leave memberships for user2
(room_id_no_info, user2_id),
(room_id_with_info, user2_id),
(space_room_id, user2_id),
},
exact=True,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id_no_info, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_no_info,
user_id=user1_id,
# Because user2 kicked/banned user1 from the room
sender=user2_id,
membership_event_id=user1_membership_room_id_no_info_response[
"event_id"
],
membership=test_membership,
event_stream_ordering=self.get_success(
self.store.get_position_for_event(
user1_membership_room_id_no_info_response["event_id"]
)
).stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get(
(room_id_with_info, user1_id)
),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id_with_info,
user_id=user1_id,
# Because user2 kicked/banned user1 from the room
sender=user2_id,
membership_event_id=user1_membership_room_id_with_info_response[
"event_id"
],
membership=test_membership,
event_stream_ordering=self.get_success(
self.store.get_position_for_event(
user1_membership_room_id_with_info_response["event_id"]
)
).stream,
has_known_state=True,
room_type=None,
room_name="my super duper room",
is_encrypted=True,
tombstone_successor_room_id="another_room",
),
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((space_room_id, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=space_room_id,
user_id=user1_id,
# Because user2 kicked/banned user1 from the room
sender=user2_id,
membership_event_id=user1_membership_space_room_id_response["event_id"],
membership=test_membership,
event_stream_ordering=self.get_success(
self.store.get_position_for_event(
user1_membership_space_room_id_response["event_id"]
)
).stream,
has_known_state=True,
room_type=RoomTypes.SPACE,
room_name="my super duper space",
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_membership_snapshots_background_update_forgotten_missing(self) -> None:
"""
Test that a new row is inserted into `sliding_sync_membership_snapshots` when it
doesn't exist in the table yet.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 joins the room
self.helper.join(room_id, user1_id, tok=user1_tok)
# User1 leaves the room (we have to leave in order to forget the room)
self.helper.leave(room_id, user1_id, tok=user1_tok)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id)
)
# Forget the room
channel = self.make_request(
"POST",
f"/_matrix/client/r0/rooms/{room_id}/forget",
content={},
access_token=user1_tok,
)
self.assertEqual(channel.code, 200, channel.result)
# Clean-up the `sliding_sync_membership_snapshots` table as if the inserts did not
# happen during event creation.
self.get_success(
self.store.db_pool.simple_delete_many(
table="sliding_sync_membership_snapshots",
column="room_id",
iterable=(room_id,),
keyvalues={},
desc="sliding_sync_membership_snapshots.test_membership_snapshots_background_update_forgotten_missing",
)
)
# We shouldn't find anything in the table because we just deleted them in
# preparation for the test.
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
set(),
exact=True,
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id, user1_id),
(room_id, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.LEAVE,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
# Room is forgotten
forgotten=True,
),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
),
)
def test_membership_snapshots_background_update_forgotten_partial(self) -> None:
"""
Test an existing `sliding_sync_membership_snapshots` row is updated with the
latest `forgotten` status after the background update passes over it.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 joins the room
self.helper.join(room_id, user1_id, tok=user1_tok)
# User1 leaves the room (we have to leave in order to forget the room)
self.helper.leave(room_id, user1_id, tok=user1_tok)
state_map = self.get_success(
self.storage_controllers.state.get_current_state(room_id)
)
# Forget the room
channel = self.make_request(
"POST",
f"/_matrix/client/r0/rooms/{room_id}/forget",
content={},
access_token=user1_tok,
)
self.assertEqual(channel.code, 200, channel.result)
# Clean-up the `sliding_sync_joined_rooms` table as if the forgotten status
# never made it into the table.
self.get_success(
self.store.db_pool.simple_update(
table="sliding_sync_membership_snapshots",
keyvalues={"room_id": room_id},
updatevalues={"forgotten": 0},
desc="sliding_sync_membership_snapshots.test_membership_snapshots_background_update_forgotten_partial",
)
)
# We should see the partial row that we made in preparation for the test.
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id, user1_id),
(room_id, user2_id),
},
exact=True,
)
user1_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user1_id,
sender=user1_id,
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
membership=Membership.LEAVE,
event_stream_ordering=state_map[
(EventTypes.Member, user1_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
# Room is *not* forgotten because of our test preparation
forgotten=False,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
user1_snapshot,
)
user2_snapshot = _SlidingSyncMembershipSnapshotResult(
room_id=room_id,
user_id=user2_id,
sender=user2_id,
membership_event_id=state_map[(EventTypes.Member, user2_id)].event_id,
membership=Membership.JOIN,
event_stream_ordering=state_map[
(EventTypes.Member, user2_id)
].internal_metadata.stream_ordering,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
tombstone_successor_room_id=None,
)
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
user2_snapshot,
)
# Insert and run the background update.
self.get_success(
self.store.db_pool.simple_insert(
"background_updates",
{
"update_name": _BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
"progress_json": "{}",
},
)
)
self.store.db_pool.updates._all_done = False
self.wait_for_background_updates()
# Make sure the table is populated
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id, user1_id),
(room_id, user2_id),
},
exact=True,
)
# Forgotten status is now updated
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
attr.evolve(user1_snapshot, forgotten=True),
)
# Holds the info according to the current state when the user joined
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id, user2_id)),
user2_snapshot,
)