summary refs log tree commit diff
path: root/packages/overlays/matrix-synapse/patches/0035-Convert-Sliding-Sync-tests-to-use-higher-level-compu.patch
diff options
context:
space:
mode:
Diffstat (limited to 'packages/overlays/matrix-synapse/patches/0035-Convert-Sliding-Sync-tests-to-use-higher-level-compu.patch')
-rw-r--r--packages/overlays/matrix-synapse/patches/0035-Convert-Sliding-Sync-tests-to-use-higher-level-compu.patch2816
1 files changed, 0 insertions, 2816 deletions
diff --git a/packages/overlays/matrix-synapse/patches/0035-Convert-Sliding-Sync-tests-to-use-higher-level-compu.patch b/packages/overlays/matrix-synapse/patches/0035-Convert-Sliding-Sync-tests-to-use-higher-level-compu.patch
deleted file mode 100644

index d34a1be..0000000 --- a/packages/overlays/matrix-synapse/patches/0035-Convert-Sliding-Sync-tests-to-use-higher-level-compu.patch +++ /dev/null
@@ -1,2816 +0,0 @@ -From ae877aa101796a0cd57c3637a875140ddb25ed51 Mon Sep 17 00:00:00 2001 -From: Devon Hudson <devon.dmytro@gmail.com> -Date: Wed, 7 May 2025 15:07:58 +0000 -Subject: [PATCH 35/74] Convert Sliding Sync tests to use higher-level - `compute_interested_rooms` (#18399) - -Spawning from -https://github.com/element-hq/synapse/pull/18375#discussion_r2071768635, - -This updates some sliding sync tests to use a higher level function in -order to move test coverage to cover both fallback & new tables. -Important when https://github.com/element-hq/synapse/pull/18375 is -merged. - -In other words, adjust tests to target `compute_interested_room(...)` -(relevant to both new and fallback path) instead of the lower level -`get_room_membership_for_user_at_to_token(...)` that only applies to the -fallback path. - -### Dev notes - -``` -SYNAPSE_TEST_LOG_LEVEL=INFO poetry run trial tests.handlers.test_sliding_sync.ComputeInterestedRoomsTestCase_new -``` - -``` -SYNAPSE_TEST_LOG_LEVEL=INFO poetry run trial tests.rest.client.sliding_sync -``` - -``` -SYNAPSE_POSTGRES=1 SYNAPSE_POSTGRES_USER=postgres SYNAPSE_TEST_LOG_LEVEL=INFO poetry run trial tests.handlers.test_sliding_sync.ComputeInterestedRoomsTestCase_new.test_display_name_changes_leave_after_token_range -``` - -### Pull Request Checklist - -<!-- Please read -https://element-hq.github.io/synapse/latest/development/contributing_guide.html -before submitting your pull request --> - -* [x] Pull request is based on the develop branch -* [x] Pull request includes a [changelog -file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog). -The entry should: -- Be a short description of your change which makes sense to users. -"Fixed a bug that prevented receiving messages from other servers." -instead of "Moved X method from `EventStore` to `EventWorkerStore`.". - - Use markdown where necessary, mostly for `code blocks`. - - End with either a period (.) or an exclamation mark (!). - - Start with a capital letter. -- Feel free to credit yourself, by adding a sentence "Contributed by -@github_username." or "Contributed by [Your Name]." to the end of the -entry. -* [x] [Code -style](https://element-hq.github.io/synapse/latest/code_style.html) is -correct -(run the -[linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters)) - ---------- - -Co-authored-by: Eric Eastwood <erice@element.io> ---- - changelog.d/18399.misc | 1 + - synapse/handlers/sliding_sync/room_lists.py | 122 +- - synapse/storage/_base.py | 10 +- - synapse/storage/databases/main/cache.py | 23 +- - synapse/storage/databases/main/roommember.py | 135 +- - synapse/storage/databases/main/stream.py | 2 + - tests/handlers/test_sliding_sync.py | 1382 +++++++++++++----- - 7 files changed, 1238 insertions(+), 437 deletions(-) - create mode 100644 changelog.d/18399.misc - -diff --git a/changelog.d/18399.misc b/changelog.d/18399.misc -new file mode 100644 -index 0000000000..847dc9a2b1 ---- /dev/null -+++ b/changelog.d/18399.misc -@@ -0,0 +1 @@ -+Refactor [MSC4186](https://github.com/matrix-org/matrix-spec-proposals/pull/4186) Simplified Sliding Sync room list tests to cover both new and fallback logic paths. -diff --git a/synapse/handlers/sliding_sync/room_lists.py b/synapse/handlers/sliding_sync/room_lists.py -index a1730b7e05..7e3cf539df 100644 ---- a/synapse/handlers/sliding_sync/room_lists.py -+++ b/synapse/handlers/sliding_sync/room_lists.py -@@ -244,14 +244,47 @@ class SlidingSyncRoomLists: - # Note: this won't include rooms the user has left themselves. We add back - # `newly_left` rooms below. This is more efficient than fetching all rooms and - # then filtering out the old left rooms. -- room_membership_for_user_map = await self.store.get_sliding_sync_rooms_for_user( -- user_id -+ room_membership_for_user_map = ( -+ await self.store.get_sliding_sync_rooms_for_user_from_membership_snapshots( -+ user_id -+ ) -+ ) -+ # To play nice with the rewind logic below, we need to go fetch the rooms the -+ # user has left themselves but only if it changed after the `to_token`. -+ # -+ # If a leave happens *after* the token range, we may have still been joined (or -+ # any non-self-leave which is relevant to sync) to the room before so we need to -+ # include it in the list of potentially relevant rooms and apply our rewind -+ # logic (outside of this function) to see if it's actually relevant. -+ # -+ # We do this separately from -+ # `get_sliding_sync_rooms_for_user_from_membership_snapshots` as those results -+ # are cached and the `to_token` isn't very cache friendly (people are constantly -+ # requesting with new tokens) so we separate it out here. -+ self_leave_room_membership_for_user_map = ( -+ await self.store.get_sliding_sync_self_leave_rooms_after_to_token( -+ user_id, to_token -+ ) - ) -+ if self_leave_room_membership_for_user_map: -+ # FIXME: It would be nice to avoid this copy but since -+ # `get_sliding_sync_rooms_for_user_from_membership_snapshots` is cached, it -+ # can't return a mutable value like a `dict`. We make the copy to get a -+ # mutable dict that we can change. We try to only make a copy when necessary -+ # (if we actually need to change something) as in most cases, the logic -+ # doesn't need to run. -+ room_membership_for_user_map = dict(room_membership_for_user_map) -+ room_membership_for_user_map.update(self_leave_room_membership_for_user_map) - - # Remove invites from ignored users - ignored_users = await self.store.ignored_users(user_id) - if ignored_users: -- # TODO: It would be nice to avoid these copies -+ # FIXME: It would be nice to avoid this copy but since -+ # `get_sliding_sync_rooms_for_user_from_membership_snapshots` is cached, it -+ # can't return a mutable value like a `dict`. We make the copy to get a -+ # mutable dict that we can change. We try to only make a copy when necessary -+ # (if we actually need to change something) as in most cases, the logic -+ # doesn't need to run. - room_membership_for_user_map = dict(room_membership_for_user_map) - # Make a copy so we don't run into an error: `dictionary changed size during - # iteration`, when we remove items -@@ -263,11 +296,23 @@ class SlidingSyncRoomLists: - ): - room_membership_for_user_map.pop(room_id, None) - -+ ( -+ newly_joined_room_ids, -+ newly_left_room_map, -+ ) = await self._get_newly_joined_and_left_rooms( -+ user_id, from_token=from_token, to_token=to_token -+ ) -+ - changes = await self._get_rewind_changes_to_current_membership_to_token( - sync_config.user, room_membership_for_user_map, to_token=to_token - ) - if changes: -- # TODO: It would be nice to avoid these copies -+ # FIXME: It would be nice to avoid this copy but since -+ # `get_sliding_sync_rooms_for_user_from_membership_snapshots` is cached, it -+ # can't return a mutable value like a `dict`. We make the copy to get a -+ # mutable dict that we can change. We try to only make a copy when necessary -+ # (if we actually need to change something) as in most cases, the logic -+ # doesn't need to run. - room_membership_for_user_map = dict(room_membership_for_user_map) - for room_id, change in changes.items(): - if change is None: -@@ -278,7 +323,7 @@ class SlidingSyncRoomLists: - existing_room = room_membership_for_user_map.get(room_id) - if existing_room is not None: - # Update room membership events to the point in time of the `to_token` -- room_membership_for_user_map[room_id] = RoomsForUserSlidingSync( -+ room_for_user = RoomsForUserSlidingSync( - room_id=room_id, - sender=change.sender, - membership=change.membership, -@@ -290,18 +335,18 @@ class SlidingSyncRoomLists: - room_type=existing_room.room_type, - is_encrypted=existing_room.is_encrypted, - ) -- -- ( -- newly_joined_room_ids, -- newly_left_room_map, -- ) = await self._get_newly_joined_and_left_rooms( -- user_id, from_token=from_token, to_token=to_token -- ) -- dm_room_ids = await self._get_dm_rooms_for_user(user_id) -+ if filter_membership_for_sync( -+ user_id=user_id, -+ room_membership_for_user=room_for_user, -+ newly_left=room_id in newly_left_room_map, -+ ): -+ room_membership_for_user_map[room_id] = room_for_user -+ else: -+ room_membership_for_user_map.pop(room_id, None) - - # Add back `newly_left` rooms (rooms left in the from -> to token range). - # -- # We do this because `get_sliding_sync_rooms_for_user(...)` doesn't include -+ # We do this because `get_sliding_sync_rooms_for_user_from_membership_snapshots(...)` doesn't include - # rooms that the user left themselves as it's more efficient to add them back - # here than to fetch all rooms and then filter out the old left rooms. The user - # only leaves a room once in a blue moon so this barely needs to run. -@@ -310,7 +355,12 @@ class SlidingSyncRoomLists: - newly_left_room_map.keys() - room_membership_for_user_map.keys() - ) - if missing_newly_left_rooms: -- # TODO: It would be nice to avoid these copies -+ # FIXME: It would be nice to avoid this copy but since -+ # `get_sliding_sync_rooms_for_user_from_membership_snapshots` is cached, it -+ # can't return a mutable value like a `dict`. We make the copy to get a -+ # mutable dict that we can change. We try to only make a copy when necessary -+ # (if we actually need to change something) as in most cases, the logic -+ # doesn't need to run. - room_membership_for_user_map = dict(room_membership_for_user_map) - for room_id in missing_newly_left_rooms: - newly_left_room_for_user = newly_left_room_map[room_id] -@@ -327,14 +377,21 @@ class SlidingSyncRoomLists: - # If the membership exists, it's just a normal user left the room on - # their own - if newly_left_room_for_user_sliding_sync is not None: -- room_membership_for_user_map[room_id] = ( -- newly_left_room_for_user_sliding_sync -- ) -+ if filter_membership_for_sync( -+ user_id=user_id, -+ room_membership_for_user=newly_left_room_for_user_sliding_sync, -+ newly_left=room_id in newly_left_room_map, -+ ): -+ room_membership_for_user_map[room_id] = ( -+ newly_left_room_for_user_sliding_sync -+ ) -+ else: -+ room_membership_for_user_map.pop(room_id, None) - - change = changes.get(room_id) - if change is not None: - # Update room membership events to the point in time of the `to_token` -- room_membership_for_user_map[room_id] = RoomsForUserSlidingSync( -+ room_for_user = RoomsForUserSlidingSync( - room_id=room_id, - sender=change.sender, - membership=change.membership, -@@ -346,6 +403,14 @@ class SlidingSyncRoomLists: - room_type=newly_left_room_for_user_sliding_sync.room_type, - is_encrypted=newly_left_room_for_user_sliding_sync.is_encrypted, - ) -+ if filter_membership_for_sync( -+ user_id=user_id, -+ room_membership_for_user=room_for_user, -+ newly_left=room_id in newly_left_room_map, -+ ): -+ room_membership_for_user_map[room_id] = room_for_user -+ else: -+ room_membership_for_user_map.pop(room_id, None) - - # If we are `newly_left` from the room but can't find any membership, - # then we have been "state reset" out of the room -@@ -367,7 +432,7 @@ class SlidingSyncRoomLists: - newly_left_room_for_user.event_pos.to_room_stream_token(), - ) - -- room_membership_for_user_map[room_id] = RoomsForUserSlidingSync( -+ room_for_user = RoomsForUserSlidingSync( - room_id=room_id, - sender=newly_left_room_for_user.sender, - membership=newly_left_room_for_user.membership, -@@ -378,6 +443,16 @@ class SlidingSyncRoomLists: - room_type=room_type, - is_encrypted=is_encrypted, - ) -+ if filter_membership_for_sync( -+ user_id=user_id, -+ room_membership_for_user=room_for_user, -+ newly_left=room_id in newly_left_room_map, -+ ): -+ room_membership_for_user_map[room_id] = room_for_user -+ else: -+ room_membership_for_user_map.pop(room_id, None) -+ -+ dm_room_ids = await self._get_dm_rooms_for_user(user_id) - - if sync_config.lists: - sync_room_map = room_membership_for_user_map -@@ -493,7 +568,12 @@ class SlidingSyncRoomLists: - - if sync_config.room_subscriptions: - with start_active_span("assemble_room_subscriptions"): -- # TODO: It would be nice to avoid these copies -+ # FIXME: It would be nice to avoid this copy but since -+ # `get_sliding_sync_rooms_for_user_from_membership_snapshots` is cached, it -+ # can't return a mutable value like a `dict`. We make the copy to get a -+ # mutable dict that we can change. We try to only make a copy when necessary -+ # (if we actually need to change something) as in most cases, the logic -+ # doesn't need to run. - room_membership_for_user_map = dict(room_membership_for_user_map) - - # Find which rooms are partially stated and may need to be filtered out -diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py -index 7251e72e3a..b5fe7dd858 100644 ---- a/synapse/storage/_base.py -+++ b/synapse/storage/_base.py -@@ -130,7 +130,7 @@ class SQLBaseStore(metaclass=ABCMeta): - "_get_rooms_for_local_user_where_membership_is_inner", (user_id,) - ) - self._attempt_to_invalidate_cache( -- "get_sliding_sync_rooms_for_user", (user_id,) -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", (user_id,) - ) - - # Purge other caches based on room state. -@@ -138,7 +138,9 @@ class SQLBaseStore(metaclass=ABCMeta): - self._attempt_to_invalidate_cache("get_partial_current_state_ids", (room_id,)) - self._attempt_to_invalidate_cache("get_room_type", (room_id,)) - self._attempt_to_invalidate_cache("get_room_encryption", (room_id,)) -- self._attempt_to_invalidate_cache("get_sliding_sync_rooms_for_user", None) -+ self._attempt_to_invalidate_cache( -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", None -+ ) - - def _invalidate_state_caches_all(self, room_id: str) -> None: - """Invalidates caches that are based on the current state, but does -@@ -168,7 +170,9 @@ class SQLBaseStore(metaclass=ABCMeta): - self._attempt_to_invalidate_cache("get_room_summary", (room_id,)) - self._attempt_to_invalidate_cache("get_room_type", (room_id,)) - self._attempt_to_invalidate_cache("get_room_encryption", (room_id,)) -- self._attempt_to_invalidate_cache("get_sliding_sync_rooms_for_user", None) -+ self._attempt_to_invalidate_cache( -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", None -+ ) - - def _attempt_to_invalidate_cache( - self, cache_name: str, key: Optional[Collection[Any]] -diff --git a/synapse/storage/databases/main/cache.py b/synapse/storage/databases/main/cache.py -index f364464c23..9418fb6dd7 100644 ---- a/synapse/storage/databases/main/cache.py -+++ b/synapse/storage/databases/main/cache.py -@@ -307,7 +307,7 @@ class CacheInvalidationWorkerStore(SQLBaseStore): - "get_rooms_for_user", (data.state_key,) - ) - self._attempt_to_invalidate_cache( -- "get_sliding_sync_rooms_for_user", None -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", None - ) - self._membership_stream_cache.entity_has_changed(data.state_key, token) # type: ignore[attr-defined] - elif data.type == EventTypes.RoomEncryption: -@@ -319,7 +319,7 @@ class CacheInvalidationWorkerStore(SQLBaseStore): - - if (data.type, data.state_key) in SLIDING_SYNC_RELEVANT_STATE_SET: - self._attempt_to_invalidate_cache( -- "get_sliding_sync_rooms_for_user", None -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", None - ) - elif row.type == EventsStreamAllStateRow.TypeId: - assert isinstance(data, EventsStreamAllStateRow) -@@ -330,7 +330,9 @@ class CacheInvalidationWorkerStore(SQLBaseStore): - self._attempt_to_invalidate_cache("get_rooms_for_user", None) - self._attempt_to_invalidate_cache("get_room_type", (data.room_id,)) - self._attempt_to_invalidate_cache("get_room_encryption", (data.room_id,)) -- self._attempt_to_invalidate_cache("get_sliding_sync_rooms_for_user", None) -+ self._attempt_to_invalidate_cache( -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", None -+ ) - else: - raise Exception("Unknown events stream row type %s" % (row.type,)) - -@@ -394,7 +396,8 @@ class CacheInvalidationWorkerStore(SQLBaseStore): - "_get_rooms_for_local_user_where_membership_is_inner", (state_key,) - ) - self._attempt_to_invalidate_cache( -- "get_sliding_sync_rooms_for_user", (state_key,) -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", -+ (state_key,), - ) - - self._attempt_to_invalidate_cache( -@@ -413,7 +416,9 @@ class CacheInvalidationWorkerStore(SQLBaseStore): - self._attempt_to_invalidate_cache("get_room_encryption", (room_id,)) - - if (etype, state_key) in SLIDING_SYNC_RELEVANT_STATE_SET: -- self._attempt_to_invalidate_cache("get_sliding_sync_rooms_for_user", None) -+ self._attempt_to_invalidate_cache( -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", None -+ ) - - if relates_to: - self._attempt_to_invalidate_cache( -@@ -470,7 +475,9 @@ class CacheInvalidationWorkerStore(SQLBaseStore): - self._attempt_to_invalidate_cache( - "_get_rooms_for_local_user_where_membership_is_inner", None - ) -- self._attempt_to_invalidate_cache("get_sliding_sync_rooms_for_user", None) -+ self._attempt_to_invalidate_cache( -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", None -+ ) - self._attempt_to_invalidate_cache("did_forget", None) - self._attempt_to_invalidate_cache("get_forgotten_rooms_for_user", None) - self._attempt_to_invalidate_cache("get_references_for_event", None) -@@ -529,7 +536,9 @@ class CacheInvalidationWorkerStore(SQLBaseStore): - self._attempt_to_invalidate_cache( - "get_current_hosts_in_room_ordered", (room_id,) - ) -- self._attempt_to_invalidate_cache("get_sliding_sync_rooms_for_user", None) -+ self._attempt_to_invalidate_cache( -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", None -+ ) - self._attempt_to_invalidate_cache("did_forget", None) - self._attempt_to_invalidate_cache("get_forgotten_rooms_for_user", None) - self._attempt_to_invalidate_cache("_get_membership_from_event_id", None) -diff --git a/synapse/storage/databases/main/roommember.py b/synapse/storage/databases/main/roommember.py -index b8c78baa6c..2084776543 100644 ---- a/synapse/storage/databases/main/roommember.py -+++ b/synapse/storage/databases/main/roommember.py -@@ -53,6 +53,7 @@ from synapse.storage.database import ( - ) - from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore - from synapse.storage.databases.main.events_worker import EventsWorkerStore -+from synapse.storage.databases.main.stream import _filter_results_by_stream - from synapse.storage.engines import Sqlite3Engine - from synapse.storage.roommember import ( - MemberSummary, -@@ -65,6 +66,7 @@ from synapse.types import ( - PersistedEventPosition, - StateMap, - StrCollection, -+ StreamToken, - get_domain_from_id, - ) - from synapse.util.caches.descriptors import _CacheContext, cached, cachedList -@@ -1389,7 +1391,9 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore): - txn, self.get_forgotten_rooms_for_user, (user_id,) - ) - self._invalidate_cache_and_stream( -- txn, self.get_sliding_sync_rooms_for_user, (user_id,) -+ txn, -+ self.get_sliding_sync_rooms_for_user_from_membership_snapshots, -+ (user_id,), - ) - - await self.db_pool.runInteraction("forget_membership", f) -@@ -1421,25 +1425,30 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore): - ) - - @cached(iterable=True, max_entries=10000) -- async def get_sliding_sync_rooms_for_user( -- self, -- user_id: str, -+ async def get_sliding_sync_rooms_for_user_from_membership_snapshots( -+ self, user_id: str - ) -> Mapping[str, RoomsForUserSlidingSync]: -- """Get all the rooms for a user to handle a sliding sync request. -+ """ -+ Get all the rooms for a user to handle a sliding sync request from the -+ `sliding_sync_membership_snapshots` table. These will be current memberships and -+ need to be rewound to the token range. - - Ignores forgotten rooms and rooms that the user has left themselves. - -+ Args: -+ user_id: The user ID to get the rooms for. -+ - Returns: - Map from room ID to membership info - """ - -- def get_sliding_sync_rooms_for_user_txn( -+ def _txn( - txn: LoggingTransaction, - ) -> Dict[str, RoomsForUserSlidingSync]: - # XXX: If you use any new columns that can change (like from - # `sliding_sync_joined_rooms` or `forgotten`), make sure to bust the -- # `get_sliding_sync_rooms_for_user` cache in the appropriate places (and add -- # tests). -+ # `get_sliding_sync_rooms_for_user_from_membership_snapshots` cache in the -+ # appropriate places (and add tests). - sql = """ - SELECT m.room_id, m.sender, m.membership, m.membership_event_id, - r.room_version, -@@ -1455,6 +1464,7 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore): - AND (m.membership != 'leave' OR m.user_id != m.sender) - """ - txn.execute(sql, (user_id,)) -+ - return { - row[0]: RoomsForUserSlidingSync( - room_id=row[0], -@@ -1475,8 +1485,113 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore): - } - - return await self.db_pool.runInteraction( -- "get_sliding_sync_rooms_for_user", -- get_sliding_sync_rooms_for_user_txn, -+ "get_sliding_sync_rooms_for_user_from_membership_snapshots", -+ _txn, -+ ) -+ -+ async def get_sliding_sync_self_leave_rooms_after_to_token( -+ self, -+ user_id: str, -+ to_token: StreamToken, -+ ) -> Dict[str, RoomsForUserSlidingSync]: -+ """ -+ Get all the self-leave rooms for a user after the `to_token` (outside the token -+ range) that are potentially relevant[1] and needed to handle a sliding sync -+ request. The results are from the `sliding_sync_membership_snapshots` table and -+ will be current memberships and need to be rewound to the token range. -+ -+ [1] If a leave happens after the token range, we may have still been joined (or -+ any non-self-leave which is relevant to sync) to the room before so we need to -+ include it in the list of potentially relevant rooms and apply -+ our rewind logic (outside of this function) to see if it's actually relevant. -+ -+ This is basically a sister-function to -+ `get_sliding_sync_rooms_for_user_from_membership_snapshots`. We could -+ alternatively incorporate this logic into -+ `get_sliding_sync_rooms_for_user_from_membership_snapshots` but those results -+ are cached and the `to_token` isn't very cache friendly (people are constantly -+ requesting with new tokens) so we separate it out here. -+ -+ Args: -+ user_id: The user ID to get the rooms for. -+ to_token: Any self-leave memberships after this position will be returned. -+ -+ Returns: -+ Map from room ID to membership info -+ """ -+ # TODO: Potential to check -+ # `self._membership_stream_cache.has_entity_changed(...)` as an early-return -+ # shortcut. -+ -+ def _txn( -+ txn: LoggingTransaction, -+ ) -> Dict[str, RoomsForUserSlidingSync]: -+ sql = """ -+ SELECT m.room_id, m.sender, m.membership, m.membership_event_id, -+ r.room_version, -+ m.event_instance_name, m.event_stream_ordering, -+ m.has_known_state, -+ m.room_type, -+ m.is_encrypted -+ FROM sliding_sync_membership_snapshots AS m -+ INNER JOIN rooms AS r USING (room_id) -+ WHERE user_id = ? -+ AND m.forgotten = 0 -+ AND m.membership = 'leave' -+ AND m.user_id = m.sender -+ AND (m.event_stream_ordering > ?) -+ """ -+ # If a leave happens after the token range, we may have still been joined -+ # (or any non-self-leave which is relevant to sync) to the room before so we -+ # need to include it in the list of potentially relevant rooms and apply our -+ # rewind logic (outside of this function). -+ # -+ # To handle tokens with a non-empty instance_map we fetch more -+ # results than necessary and then filter down -+ min_to_token_position = to_token.room_key.stream -+ txn.execute(sql, (user_id, min_to_token_position)) -+ -+ # Map from room_id to membership info -+ room_membership_for_user_map: Dict[str, RoomsForUserSlidingSync] = {} -+ for row in txn: -+ room_for_user = RoomsForUserSlidingSync( -+ room_id=row[0], -+ sender=row[1], -+ membership=row[2], -+ event_id=row[3], -+ room_version_id=row[4], -+ event_pos=PersistedEventPosition(row[5], row[6]), -+ has_known_state=bool(row[7]), -+ room_type=row[8], -+ is_encrypted=bool(row[9]), -+ ) -+ -+ # We filter out unknown room versions proactively. They shouldn't go -+ # down sync and their metadata may be in a broken state (causing -+ # errors). -+ if row[4] not in KNOWN_ROOM_VERSIONS: -+ continue -+ -+ # We only want to include the self-leave membership if it happened after -+ # the token range. -+ # -+ # Since the database pulls out more than necessary, we need to filter it -+ # down here. -+ if _filter_results_by_stream( -+ lower_token=None, -+ upper_token=to_token.room_key, -+ instance_name=room_for_user.event_pos.instance_name, -+ stream_ordering=room_for_user.event_pos.stream, -+ ): -+ continue -+ -+ room_membership_for_user_map[room_for_user.room_id] = room_for_user -+ -+ return room_membership_for_user_map -+ -+ return await self.db_pool.runInteraction( -+ "get_sliding_sync_self_leave_rooms_after_to_token", -+ _txn, - ) - - async def get_sliding_sync_room_for_user( -diff --git a/synapse/storage/databases/main/stream.py b/synapse/storage/databases/main/stream.py -index 00e5208674..c52389b8a9 100644 ---- a/synapse/storage/databases/main/stream.py -+++ b/synapse/storage/databases/main/stream.py -@@ -453,6 +453,8 @@ def _filter_results_by_stream( - stream_ordering falls between the two tokens (taking a None - token to mean unbounded). - -+ The token range is defined by > `lower_token` and <= `upper_token`. -+ - Used to filter results from fetching events in the DB against the given - tokens. This is necessary to handle the case where the tokens include - position maps, which we handle by fetching more than necessary from the DB -diff --git a/tests/handlers/test_sliding_sync.py b/tests/handlers/test_sliding_sync.py -index 5b7e2937f8..cbacf21ae7 100644 ---- a/tests/handlers/test_sliding_sync.py -+++ b/tests/handlers/test_sliding_sync.py -@@ -22,7 +22,7 @@ from typing import AbstractSet, Dict, Mapping, Optional, Set, Tuple - from unittest.mock import patch - - import attr --from parameterized import parameterized -+from parameterized import parameterized, parameterized_class - - from twisted.test.proto_helpers import MemoryReactor - -@@ -43,13 +43,15 @@ from synapse.rest import admin - from synapse.rest.client import knock, login, room - from synapse.server import HomeServer - from synapse.storage.util.id_generators import MultiWriterIdGenerator --from synapse.types import JsonDict, StateMap, StreamToken, UserID --from synapse.types.handlers.sliding_sync import SlidingSyncConfig -+from synapse.types import JsonDict, StateMap, StreamToken, UserID, create_requester -+from synapse.types.handlers.sliding_sync import PerConnectionState, SlidingSyncConfig - from synapse.types.state import StateFilter - from synapse.util import Clock - - from tests import unittest - from tests.replication._base import BaseMultiWorkerStreamTestCase -+from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase -+from tests.test_utils.event_injection import create_event - from tests.unittest import HomeserverTestCase, TestCase - - logger = logging.getLogger(__name__) -@@ -572,9 +574,23 @@ class RoomSyncConfigTestCase(TestCase): - self._assert_room_config_equal(combined_config, expected, "A into B") - - --class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): -+# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the -+# foreground update for -+# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by -+# https://github.com/element-hq/synapse/issues/17623) -+@parameterized_class( -+ ("use_new_tables",), -+ [ -+ (True,), -+ (False,), -+ ], -+ class_name_func=lambda cls, -+ num, -+ params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}", -+) -+class ComputeInterestedRoomsTestCase(SlidingSyncBase): - """ -- Tests Sliding Sync handler `get_room_membership_for_user_at_to_token()` to make sure it returns -+ Tests Sliding Sync handler `compute_interested_rooms()` to make sure it returns - the correct list of rooms IDs. - """ - -@@ -596,6 +612,11 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - self.store = self.hs.get_datastores().main - self.event_sources = hs.get_event_sources() - self.storage_controllers = hs.get_storage_controllers() -+ persistence = self.hs.get_storage_controllers().persistence -+ assert persistence is not None -+ self.persistence = persistence -+ -+ super().prepare(reactor, clock, hs) - - def test_no_rooms(self) -> None: - """ -@@ -606,15 +627,28 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - now_token = self.event_sources.get_current_token() - -- room_id_results, _, _ = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=now_token, - to_token=now_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) - -- self.assertEqual(room_id_results.keys(), set()) -+ self.assertIncludes(room_id_results, set(), exact=True) - - def test_get_newly_joined_room(self) -> None: - """ -@@ -633,22 +667,44 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - after_room_token = self.event_sources.get_current_token() - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room_token, - to_token=after_room_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - -- self.assertEqual(room_id_results.keys(), {room_id}) -+ self.assertIncludes( -+ room_id_results, -+ {room_id}, -+ exact=True, -+ ) - # It should be pointing to the join event (latest membership event in the - # from/to range) - self.assertEqual( -- room_id_results[room_id].event_id, -+ interested_rooms.room_membership_for_user_map[room_id].event_id, - join_response["event_id"], - ) -- self.assertEqual(room_id_results[room_id].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id].membership, -+ Membership.JOIN, -+ ) - # We should be considered `newly_joined` because we joined during the token - # range - self.assertTrue(room_id in newly_joined) -@@ -668,22 +724,40 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - after_room_token = self.event_sources.get_current_token() - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room_token, - to_token=after_room_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - -- self.assertEqual(room_id_results.keys(), {room_id}) -+ self.assertIncludes(room_id_results, {room_id}, exact=True) - # It should be pointing to the join event (latest membership event in the - # from/to range) - self.assertEqual( -- room_id_results[room_id].event_id, -+ interested_rooms.room_membership_for_user_map[room_id].event_id, - join_response["event_id"], - ) -- self.assertEqual(room_id_results[room_id].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id].membership, -+ Membership.JOIN, -+ ) - # We should *NOT* be `newly_joined` because we joined before the token range - self.assertTrue(room_id not in newly_joined) - self.assertTrue(room_id not in newly_left) -@@ -742,46 +816,71 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - after_room_token = self.event_sources.get_current_token() - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room_token, - to_token=after_room_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Ensure that the invited, ban, and knock rooms show up -- self.assertEqual( -- room_id_results.keys(), -+ self.assertIncludes( -+ room_id_results, - { - invited_room_id, - ban_room_id, - knock_room_id, - }, -+ exact=True, - ) - # It should be pointing to the the respective membership event (latest - # membership event in the from/to range) - self.assertEqual( -- room_id_results[invited_room_id].event_id, -+ interested_rooms.room_membership_for_user_map[invited_room_id].event_id, - invite_response["event_id"], - ) -- self.assertEqual(room_id_results[invited_room_id].membership, Membership.INVITE) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[invited_room_id].membership, -+ Membership.INVITE, -+ ) - self.assertTrue(invited_room_id not in newly_joined) - self.assertTrue(invited_room_id not in newly_left) - - self.assertEqual( -- room_id_results[ban_room_id].event_id, -+ interested_rooms.room_membership_for_user_map[ban_room_id].event_id, - ban_response["event_id"], - ) -- self.assertEqual(room_id_results[ban_room_id].membership, Membership.BAN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[ban_room_id].membership, -+ Membership.BAN, -+ ) - self.assertTrue(ban_room_id not in newly_joined) - self.assertTrue(ban_room_id not in newly_left) - - self.assertEqual( -- room_id_results[knock_room_id].event_id, -+ interested_rooms.room_membership_for_user_map[knock_room_id].event_id, - knock_room_membership_state_event.event_id, - ) -- self.assertEqual(room_id_results[knock_room_id].membership, Membership.KNOCK) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[knock_room_id].membership, -+ Membership.KNOCK, -+ ) - self.assertTrue(knock_room_id not in newly_joined) - self.assertTrue(knock_room_id not in newly_left) - -@@ -814,23 +913,43 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - after_kick_token = self.event_sources.get_current_token() - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_kick_token, - to_token=after_kick_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # The kicked room should show up -- self.assertEqual(room_id_results.keys(), {kick_room_id}) -+ self.assertIncludes(room_id_results, {kick_room_id}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[kick_room_id].event_id, -+ interested_rooms.room_membership_for_user_map[kick_room_id].event_id, - kick_response["event_id"], - ) -- self.assertEqual(room_id_results[kick_room_id].membership, Membership.LEAVE) -- self.assertNotEqual(room_id_results[kick_room_id].sender, user1_id) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[kick_room_id].membership, -+ Membership.LEAVE, -+ ) -+ self.assertNotEqual( -+ interested_rooms.room_membership_for_user_map[kick_room_id].sender, user1_id -+ ) - # We should *NOT* be `newly_joined` because we were not joined at the the time - # of the `to_token`. - self.assertTrue(kick_room_id not in newly_joined) -@@ -907,16 +1026,29 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - ) - self.assertEqual(channel.code, 200, channel.result) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room_forgets, - to_token=before_room_forgets, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) - - # We shouldn't see the room because it was forgotten -- self.assertEqual(room_id_results.keys(), set()) -+ self.assertIncludes(room_id_results, set(), exact=True) - - def test_newly_left_rooms(self) -> None: - """ -@@ -927,7 +1059,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - # Leave before we calculate the `from_token` - room_id1 = self.helper.create_room_as(user1_id, tok=user1_tok) -- leave_response1 = self.helper.leave(room_id1, user1_id, tok=user1_tok) -+ _leave_response1 = self.helper.leave(room_id1, user1_id, tok=user1_tok) - - after_room1_token = self.event_sources.get_current_token() - -@@ -937,31 +1069,52 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - after_room2_token = self.event_sources.get_current_token() - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_room2_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - -- self.assertEqual(room_id_results.keys(), {room_id1, room_id2}) -- -- self.assertEqual( -- room_id_results[room_id1].event_id, -- leave_response1["event_id"], -+ # `room_id1` should not show up because it was left before the token range. -+ # `room_id2` should show up because it is `newly_left` within the token range. -+ self.assertIncludes( -+ room_id_results, -+ {room_id2}, -+ exact=True, -+ message="Corresponding map to disambiguate the opaque room IDs: " -+ + str( -+ { -+ "room_id1": room_id1, -+ "room_id2": room_id2, -+ } -+ ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE) -- # We should *NOT* be `newly_joined` or `newly_left` because that happened before -- # the from/to range -- self.assertTrue(room_id1 not in newly_joined) -- self.assertTrue(room_id1 not in newly_left) - - self.assertEqual( -- room_id_results[room_id2].event_id, -+ interested_rooms.room_membership_for_user_map[room_id2].event_id, - leave_response2["event_id"], - ) -- self.assertEqual(room_id_results[room_id2].membership, Membership.LEAVE) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id2].membership, -+ Membership.LEAVE, -+ ) - # We should *NOT* be `newly_joined` because we are instead `newly_left` - self.assertTrue(room_id2 not in newly_joined) - self.assertTrue(room_id2 in newly_left) -@@ -987,21 +1140,39 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - room_id2 = self.helper.create_room_as(user2_id, tok=user2_tok) - self.helper.join(room_id2, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_response1["event_id"], - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should be `newly_joined` because we joined during the token range - self.assertTrue(room_id1 in newly_joined) - self.assertTrue(room_id1 not in newly_left) -@@ -1027,20 +1198,35 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - # Leave the room after we already have our tokens - leave_response = self.helper.leave(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # We should still see the room because we were joined during the - # from_token/to_token time period. -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_response["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1050,7 +1236,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should be `newly_joined` because we joined during the token range - self.assertTrue(room_id1 in newly_joined) - self.assertTrue(room_id1 not in newly_left) -@@ -1074,19 +1263,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - # Leave the room after we already have our tokens - leave_response = self.helper.leave(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # We should still see the room because we were joined before the `from_token` -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_response["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1096,7 +1300,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should *NOT* be `newly_joined` because we joined before the token range - self.assertTrue(room_id1 not in newly_joined) - self.assertTrue(room_id1 not in newly_left) -@@ -1138,19 +1345,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - join_response2 = self.helper.join(kick_room_id, user1_id, tok=user1_tok) - leave_response = self.helper.leave(kick_room_id, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_kick_token, - to_token=after_kick_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # We shouldn't see the room because it was forgotten -- self.assertEqual(room_id_results.keys(), {kick_room_id}) -+ self.assertIncludes(room_id_results, {kick_room_id}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[kick_room_id].event_id, -+ interested_rooms.room_membership_for_user_map[kick_room_id].event_id, - kick_response["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1162,8 +1384,13 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[kick_room_id].membership, Membership.LEAVE) -- self.assertNotEqual(room_id_results[kick_room_id].sender, user1_id) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[kick_room_id].membership, -+ Membership.LEAVE, -+ ) -+ self.assertNotEqual( -+ interested_rooms.room_membership_for_user_map[kick_room_id].sender, user1_id -+ ) - # We should *NOT* be `newly_joined` because we were kicked - self.assertTrue(kick_room_id not in newly_joined) - self.assertTrue(kick_room_id not in newly_left) -@@ -1194,19 +1421,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - join_response2 = self.helper.join(room_id1, user1_id, tok=user1_tok) - leave_response2 = self.helper.leave(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should still show up because it's newly_left during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - leave_response1["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1218,7 +1460,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.LEAVE, -+ ) - # We should *NOT* be `newly_joined` because we are actually `newly_left` during - # the token range - self.assertTrue(room_id1 not in newly_joined) -@@ -1249,19 +1494,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - # Join the room after we already have our tokens - join_response2 = self.helper.join(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should still show up because it's newly_left during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - leave_response1["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1272,7 +1532,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.LEAVE, -+ ) - # We should *NOT* be `newly_joined` because we are actually `newly_left` during - # the token range - self.assertTrue(room_id1 not in newly_joined) -@@ -1301,47 +1564,53 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - # Join and leave the room2 before the `to_token` - self.helper.join(room_id2, user1_id, tok=user1_tok) -- leave_response2 = self.helper.leave(room_id2, user1_id, tok=user1_tok) -+ _leave_response2 = self.helper.leave(room_id2, user1_id, tok=user1_tok) - - after_room1_token = self.event_sources.get_current_token() - - # Join the room2 after we already have our tokens - self.helper.join(room_id2, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=None, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Only rooms we were joined to before the `to_token` should show up -- self.assertEqual(room_id_results.keys(), {room_id1, room_id2}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - - # Room1 - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_response1["event_id"], - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -- # We should *NOT* be `newly_joined`/`newly_left` because there is no -- # `from_token` to define a "live" range to compare against -- self.assertTrue(room_id1 not in newly_joined) -- self.assertTrue(room_id1 not in newly_left) -- -- # Room2 -- # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id2].event_id, -- leave_response2["event_id"], -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, - ) -- self.assertEqual(room_id_results[room_id2].membership, Membership.LEAVE) - # We should *NOT* be `newly_joined`/`newly_left` because there is no - # `from_token` to define a "live" range to compare against -- self.assertTrue(room_id2 not in newly_joined) -- self.assertTrue(room_id2 not in newly_left) -+ self.assertTrue(room_id1 not in newly_joined) -+ self.assertTrue(room_id1 not in newly_left) - - def test_from_token_ahead_of_to_token(self) -> None: - """ -@@ -1365,7 +1634,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - # Join and leave the room2 before `to_token` - _join_room2_response1 = self.helper.join(room_id2, user1_id, tok=user1_tok) -- leave_room2_response1 = self.helper.leave(room_id2, user1_id, tok=user1_tok) -+ _leave_room2_response1 = self.helper.leave(room_id2, user1_id, tok=user1_tok) - - # Note: These are purposely swapped. The `from_token` should come after - # the `to_token` in this test -@@ -1390,55 +1659,70 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - # Join the room4 after we already have our tokens - self.helper.join(room_id4, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=from_token, - to_token=to_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # In the "current" state snapshot, we're joined to all of the rooms but in the - # from/to token range... - self.assertIncludes( -- room_id_results.keys(), -+ room_id_results, - { - # Included because we were joined before both tokens - room_id1, -- # Included because we had membership before the to_token -- room_id2, -+ # Excluded because we left before the `from_token` and `to_token` -+ # room_id2, - # Excluded because we joined after the `to_token` - # room_id3, - # Excluded because we joined after the `to_token` - # room_id4, - }, - exact=True, -+ message="Corresponding map to disambiguate the opaque room IDs: " -+ + str( -+ { -+ "room_id1": room_id1, -+ "room_id2": room_id2, -+ "room_id3": room_id3, -+ "room_id4": room_id4, -+ } -+ ), - ) - - # Room1 - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_room1_response1["event_id"], - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should *NOT* be `newly_joined`/`newly_left` because we joined `room1` - # before either of the tokens - self.assertTrue(room_id1 not in newly_joined) - self.assertTrue(room_id1 not in newly_left) - -- # Room2 -- # It should be pointing to the latest membership event in the from/to range -- self.assertEqual( -- room_id_results[room_id2].event_id, -- leave_room2_response1["event_id"], -- ) -- self.assertEqual(room_id_results[room_id2].membership, Membership.LEAVE) -- # We should *NOT* be `newly_joined`/`newly_left` because we joined and left -- # `room1` before either of the tokens -- self.assertTrue(room_id2 not in newly_joined) -- self.assertTrue(room_id2 not in newly_left) -- - def test_leave_before_range_and_join_leave_after_to_token(self) -> None: - """ - Test old left rooms. But we're also testing that joining and leaving after the -@@ -1455,7 +1739,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True) - # Join and leave the room before the from/to range - self.helper.join(room_id1, user1_id, tok=user1_tok) -- leave_response = self.helper.leave(room_id1, user1_id, tok=user1_tok) -+ self.helper.leave(room_id1, user1_id, tok=user1_tok) - - after_room1_token = self.event_sources.get_current_token() - -@@ -1463,25 +1747,28 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - self.helper.join(room_id1, user1_id, tok=user1_tok) - self.helper.leave(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) - -- self.assertEqual(room_id_results.keys(), {room_id1}) -- # It should be pointing to the latest membership event in the from/to range -- self.assertEqual( -- room_id_results[room_id1].event_id, -- leave_response["event_id"], -- ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE) -- # We should *NOT* be `newly_joined`/`newly_left` because we joined and left -- # `room1` before either of the tokens -- self.assertTrue(room_id1 not in newly_joined) -- self.assertTrue(room_id1 not in newly_left) -+ self.assertIncludes(room_id_results, set(), exact=True) - - def test_leave_before_range_and_join_after_to_token(self) -> None: - """ -@@ -1499,32 +1786,35 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True) - # Join and leave the room before the from/to range - self.helper.join(room_id1, user1_id, tok=user1_tok) -- leave_response = self.helper.leave(room_id1, user1_id, tok=user1_tok) -+ self.helper.leave(room_id1, user1_id, tok=user1_tok) - - after_room1_token = self.event_sources.get_current_token() - - # Join the room after we already have our tokens - self.helper.join(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) - -- self.assertEqual(room_id_results.keys(), {room_id1}) -- # It should be pointing to the latest membership event in the from/to range -- self.assertEqual( -- room_id_results[room_id1].event_id, -- leave_response["event_id"], -- ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE) -- # We should *NOT* be `newly_joined`/`newly_left` because we joined and left -- # `room1` before either of the tokens -- self.assertTrue(room_id1 not in newly_joined) -- self.assertTrue(room_id1 not in newly_left) -+ self.assertIncludes(room_id_results, set(), exact=True) - - def test_join_leave_multiple_times_during_range_and_after_to_token( - self, -@@ -1556,19 +1846,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - join_response3 = self.helper.join(room_id1, user1_id, tok=user1_tok) - leave_response3 = self.helper.leave(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because it was newly_left and joined during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_response2["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1582,7 +1887,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should be `newly_joined` because we joined during the token range - self.assertTrue(room_id1 in newly_joined) - # We should *NOT* be `newly_left` because we joined during the token range and -@@ -1618,19 +1926,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - join_response3 = self.helper.join(room_id1, user1_id, tok=user1_tok) - leave_response3 = self.helper.leave(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because we were joined before the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_response2["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1644,7 +1967,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should *NOT* be `newly_joined` because we joined before the token range - self.assertTrue(room_id1 not in newly_joined) - self.assertTrue(room_id1 not in newly_left) -@@ -1677,19 +2003,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - join_respsonse = self.helper.join(room_id1, user1_id, tok=user1_tok) - leave_response = self.helper.leave(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because we were invited before the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - invite_response["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1700,7 +2041,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.INVITE) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.INVITE, -+ ) - # We should *NOT* be `newly_joined` because we were only invited before the - # token range - self.assertTrue(room_id1 not in newly_joined) -@@ -1751,19 +2095,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - tok=user1_tok, - ) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because we were joined during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - displayname_change_during_token_range_response["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1778,7 +2137,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should be `newly_joined` because we joined during the token range - self.assertTrue(room_id1 in newly_joined) - self.assertTrue(room_id1 not in newly_left) -@@ -1816,19 +2178,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - after_change1_token = self.event_sources.get_current_token() - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_change1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because we were joined during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - displayname_change_during_token_range_response["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1840,7 +2217,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should *NOT* be `newly_joined` because we joined before the token range - self.assertTrue(room_id1 not in newly_joined) - self.assertTrue(room_id1 not in newly_left) -@@ -1888,19 +2268,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - tok=user1_tok, - ) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because we were joined before the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - displayname_change_before_token_range_response["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1915,18 +2310,22 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should *NOT* be `newly_joined` because we joined before the token range - self.assertTrue(room_id1 not in newly_joined) - self.assertTrue(room_id1 not in newly_left) - -- def test_display_name_changes_leave_after_token_range( -+ def test_newly_joined_display_name_changes_leave_after_token_range( - self, - ) -> None: - """ - Test that we point to the correct membership event within the from/to range even -- if there are multiple `join` membership events in a row indicating -- `displayname`/`avatar_url` updates and we leave after the `to_token`. -+ if we are `newly_joined` and there are multiple `join` membership events in a -+ row indicating `displayname`/`avatar_url` updates and we leave after the -+ `to_token`. - - See condition "1a)" comments in the `get_room_membership_for_user_at_to_token()` method. - """ -@@ -1941,6 +2340,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - # leave and can still re-join. - room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True) - join_response = self.helper.join(room_id1, user1_id, tok=user1_tok) -+ - # Update the displayname during the token range - displayname_change_during_token_range_response = self.helper.send_state( - room_id1, -@@ -1970,19 +2370,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - # Leave after the token - self.helper.leave(room_id1, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because we were joined during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - displayname_change_during_token_range_response["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -1997,11 +2412,118 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should be `newly_joined` because we joined during the token range - self.assertTrue(room_id1 in newly_joined) - self.assertTrue(room_id1 not in newly_left) - -+ def test_display_name_changes_leave_after_token_range( -+ self, -+ ) -> None: -+ """ -+ Test that we point to the correct membership event within the from/to range even -+ if there are multiple `join` membership events in a row indicating -+ `displayname`/`avatar_url` updates and we leave after the `to_token`. -+ -+ See condition "1a)" comments in the `get_room_membership_for_user_at_to_token()` method. -+ """ -+ 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") -+ -+ _before_room1_token = self.event_sources.get_current_token() -+ -+ # We create the room with user2 so the room isn't left with no members when we -+ # leave and can still re-join. -+ room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True) -+ join_response = self.helper.join(room_id1, user1_id, tok=user1_tok) -+ -+ after_join_token = self.event_sources.get_current_token() -+ -+ # Update the displayname during the token range -+ displayname_change_during_token_range_response = self.helper.send_state( -+ room_id1, -+ event_type=EventTypes.Member, -+ state_key=user1_id, -+ body={ -+ "membership": Membership.JOIN, -+ "displayname": "displayname during token range", -+ }, -+ tok=user1_tok, -+ ) -+ -+ after_display_name_change_token = self.event_sources.get_current_token() -+ -+ # Update the displayname after the token range -+ displayname_change_after_token_range_response = self.helper.send_state( -+ room_id1, -+ event_type=EventTypes.Member, -+ state_key=user1_id, -+ body={ -+ "membership": Membership.JOIN, -+ "displayname": "displayname after token range", -+ }, -+ tok=user1_tok, -+ ) -+ -+ # Leave after the token -+ self.helper.leave(room_id1, user1_id, tok=user1_tok) -+ -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), -+ from_token=after_join_token, -+ to_token=after_display_name_change_token, -+ ) -+ ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms -+ -+ # Room should show up because we were joined during the from/to range -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) -+ # It should be pointing to the latest membership event in the from/to range -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, -+ displayname_change_during_token_range_response["event_id"], -+ "Corresponding map to disambiguate the opaque event IDs: " -+ + str( -+ { -+ "join_response": join_response["event_id"], -+ "displayname_change_during_token_range_response": displayname_change_during_token_range_response[ -+ "event_id" -+ ], -+ "displayname_change_after_token_range_response": displayname_change_after_token_range_response[ -+ "event_id" -+ ], -+ } -+ ), -+ ) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) -+ # We only changed our display name during the token range so we shouldn't be -+ # considered `newly_joined` or `newly_left` -+ self.assertTrue(room_id1 not in newly_joined) -+ self.assertTrue(room_id1 not in newly_left) -+ - def test_display_name_changes_join_after_token_range( - self, - ) -> None: -@@ -2038,16 +2560,29 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - tok=user1_tok, - ) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) - - # Room shouldn't show up because we joined after the from/to range -- self.assertEqual(room_id_results.keys(), set()) -+ self.assertIncludes(room_id_results, set(), exact=True) - - def test_newly_joined_with_leave_join_in_token_range( - self, -@@ -2074,22 +2609,40 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - after_more_changes_token = self.event_sources.get_current_token() - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=after_room1_token, - to_token=after_more_changes_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because we were joined during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_response2["event_id"], - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should be considered `newly_joined` because there is some non-join event in - # between our latest join event. - self.assertTrue(room_id1 in newly_joined) -@@ -2139,19 +2692,34 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - after_room1_token = self.event_sources.get_current_token() - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room1_token, - to_token=after_room1_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room should show up because it was newly_left and joined during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1}) -+ self.assertIncludes(room_id_results, {room_id1}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - displayname_change_during_token_range_response2["event_id"], - "Corresponding map to disambiguate the opaque event IDs: " - + str( -@@ -2166,7 +2734,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - } - ), - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should be `newly_joined` because we first joined during the token range - self.assertTrue(room_id1 in newly_joined) - self.assertTrue(room_id1 not in newly_left) -@@ -2192,7 +2763,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - # Invited and left the room before the token - self.helper.invite(room_id1, src=user2_id, targ=user1_id, tok=user2_tok) -- leave_room1_response = self.helper.leave(room_id1, user1_id, tok=user1_tok) -+ _leave_room1_response = self.helper.leave(room_id1, user1_id, tok=user1_tok) - # Invited to room2 - invite_room2_response = self.helper.invite( - room_id2, src=user2_id, targ=user1_id, tok=user2_tok -@@ -2215,45 +2786,52 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - # Leave room3 - self.helper.leave(room_id3, user1_id, tok=user1_tok) - -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_room3_token, - to_token=after_room3_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - -- self.assertEqual( -- room_id_results.keys(), -+ self.assertIncludes( -+ room_id_results, - { -- # Left before the from/to range -- room_id1, -+ # Excluded because we left before the from/to range -+ # room_id1, - # Invited before the from/to range - room_id2, - # `newly_left` during the from/to range - room_id3, - }, -+ exact=True, - ) - -- # Room1 -- # It should be pointing to the latest membership event in the from/to range -- self.assertEqual( -- room_id_results[room_id1].event_id, -- leave_room1_response["event_id"], -- ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE) -- # We should *NOT* be `newly_joined`/`newly_left` because we were invited and left -- # before the token range -- self.assertTrue(room_id1 not in newly_joined) -- self.assertTrue(room_id1 not in newly_left) -- - # Room2 - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id2].event_id, -+ interested_rooms.room_membership_for_user_map[room_id2].event_id, - invite_room2_response["event_id"], - ) -- self.assertEqual(room_id_results[room_id2].membership, Membership.INVITE) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id2].membership, -+ Membership.INVITE, -+ ) - # We should *NOT* be `newly_joined`/`newly_left` because we were invited before - # the token range - self.assertTrue(room_id2 not in newly_joined) -@@ -2262,10 +2840,13 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - # Room3 - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id3].event_id, -+ interested_rooms.room_membership_for_user_map[room_id3].event_id, - leave_room3_response["event_id"], - ) -- self.assertEqual(room_id_results[room_id3].membership, Membership.LEAVE) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id3].membership, -+ Membership.LEAVE, -+ ) - # We should be `newly_left` because we were invited and left during - # the token range - self.assertTrue(room_id3 not in newly_joined) -@@ -2282,7 +2863,16 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - user2_tok = self.login(user2_id, "pass") - - # The room where the state reset will happen -- room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) -+ room_id1 = self.helper.create_room_as( -+ user2_id, -+ is_public=True, -+ tok=user2_tok, -+ ) -+ # Create a dummy event for us to point back to for the state reset -+ dummy_event_response = self.helper.send(room_id1, "test", tok=user2_tok) -+ dummy_event_id = dummy_event_response["event_id"] -+ -+ # Join after the dummy event - join_response1 = self.helper.join(room_id1, user1_id, tok=user1_tok) - - # Join another room so we don't hit the short-circuit and return early if they -@@ -2292,92 +2882,97 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase): - - before_reset_token = self.event_sources.get_current_token() - -- # Send another state event to make a position for the state reset to happen at -- dummy_state_response = self.helper.send_state( -- room_id1, -- event_type="foobarbaz", -- state_key="", -- body={"foo": "bar"}, -- tok=user2_tok, -- ) -- dummy_state_pos = self.get_success( -- self.store.get_position_for_event(dummy_state_response["event_id"]) -- ) -- -- # Mock a state reset removing the membership for user1 in the current state -- self.get_success( -- self.store.db_pool.simple_delete( -- table="current_state_events", -- keyvalues={ -- "room_id": room_id1, -- "type": EventTypes.Member, -- "state_key": user1_id, -- }, -- desc="state reset user in current_state_events", -+ # Trigger a state reset -+ join_rule_event, join_rule_context = self.get_success( -+ create_event( -+ self.hs, -+ prev_event_ids=[dummy_event_id], -+ type=EventTypes.JoinRules, -+ state_key="", -+ content={"join_rule": JoinRules.INVITE}, -+ sender=user2_id, -+ room_id=room_id1, -+ room_version=self.get_success(self.store.get_room_version_id(room_id1)), - ) - ) -- self.get_success( -- self.store.db_pool.simple_delete( -- table="local_current_membership", -- keyvalues={ -- "room_id": room_id1, -- "user_id": user1_id, -- }, -- desc="state reset user in local_current_membership", -- ) -- ) -- self.get_success( -- self.store.db_pool.simple_insert( -- table="current_state_delta_stream", -- values={ -- "stream_id": dummy_state_pos.stream, -- "room_id": room_id1, -- "type": EventTypes.Member, -- "state_key": user1_id, -- "event_id": None, -- "prev_event_id": join_response1["event_id"], -- "instance_name": dummy_state_pos.instance_name, -- }, -- desc="state reset user in current_state_delta_stream", -- ) -+ _, join_rule_event_pos, _ = self.get_success( -+ self.persistence.persist_event(join_rule_event, join_rule_context) - ) - -- # Manually bust the cache since we we're just manually messing with the database -- # and not causing an actual state reset. -- self.store._membership_stream_cache.entity_has_changed( -- user1_id, dummy_state_pos.stream -- ) -+ # Ensure that the state reset worked and only user2 is in the room now -+ users_in_room = self.get_success(self.store.get_users_in_room(room_id1)) -+ self.assertIncludes(set(users_in_room), {user2_id}, exact=True) - - after_reset_token = self.event_sources.get_current_token() - - # The function under test -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_reset_token, - to_token=after_reset_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - - # Room1 should show up because it was `newly_left` via state reset during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1, room_id2}) -+ self.assertIncludes(room_id_results, {room_id1, room_id2}, exact=True) - # It should be pointing to no event because we were removed from the room - # without a corresponding leave event - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - None, -+ "Corresponding map to disambiguate the opaque event IDs: " -+ + str( -+ { -+ "join_response1": join_response1["event_id"], -+ } -+ ), - ) - # State reset caused us to leave the room and there is no corresponding leave event -- self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.LEAVE, -+ ) - # We should *NOT* be `newly_joined` because we joined before the token range - self.assertTrue(room_id1 not in newly_joined) - # We should be `newly_left` because we were removed via state reset during the from/to range - self.assertTrue(room_id1 in newly_left) - - --class GetRoomMembershipForUserAtToTokenShardTestCase(BaseMultiWorkerStreamTestCase): -+# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the -+# foreground update for -+# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by -+# https://github.com/element-hq/synapse/issues/17623) -+@parameterized_class( -+ ("use_new_tables",), -+ [ -+ (True,), -+ (False,), -+ ], -+ class_name_func=lambda cls, -+ num, -+ params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}", -+) -+class ComputeInterestedRoomsShardTestCase( -+ BaseMultiWorkerStreamTestCase, SlidingSyncBase -+): - """ -- Tests Sliding Sync handler `get_room_membership_for_user_at_to_token()` to make sure it works with -+ Tests Sliding Sync handler `compute_interested_rooms()` to make sure it works with - sharded event stream_writers enabled - """ - -@@ -2475,7 +3070,7 @@ class GetRoomMembershipForUserAtToTokenShardTestCase(BaseMultiWorkerStreamTestCa - join_response1 = self.helper.join(room_id1, user1_id, tok=user1_tok) - join_response2 = self.helper.join(room_id2, user1_id, tok=user1_tok) - # Leave room2 -- leave_room2_response = self.helper.leave(room_id2, user1_id, tok=user1_tok) -+ _leave_room2_response = self.helper.leave(room_id2, user1_id, tok=user1_tok) - join_response3 = self.helper.join(room_id3, user1_id, tok=user1_tok) - # Leave room3 - self.helper.leave(room_id3, user1_id, tok=user1_tok) -@@ -2565,57 +3160,74 @@ class GetRoomMembershipForUserAtToTokenShardTestCase(BaseMultiWorkerStreamTestCa - self.get_success(actx.__aexit__(None, None, None)) - - # The function under test -- room_id_results, newly_joined, newly_left = self.get_success( -- self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token( -- UserID.from_string(user1_id), -+ interested_rooms = self.get_success( -+ self.sliding_sync_handler.room_lists.compute_interested_rooms( -+ SlidingSyncConfig( -+ user=UserID.from_string(user1_id), -+ requester=create_requester(user_id=user1_id), -+ lists={ -+ "foo-list": SlidingSyncConfig.SlidingSyncList( -+ ranges=[(0, 99)], -+ required_state=[], -+ timeline_limit=1, -+ ) -+ }, -+ conn_id=None, -+ ), -+ PerConnectionState(), - from_token=before_stuck_activity_token, - to_token=stuck_activity_token, - ) - ) -+ room_id_results = set(interested_rooms.lists["foo-list"].ops[0].room_ids) -+ newly_joined = interested_rooms.newly_joined_rooms -+ newly_left = interested_rooms.newly_left_rooms - -- self.assertEqual( -- room_id_results.keys(), -+ self.assertIncludes( -+ room_id_results, - { - room_id1, -- room_id2, -+ # Excluded because we left before the from/to range and the second join -+ # event happened while worker2 was stuck and technically occurs after -+ # the `stuck_activity_token`. -+ # room_id2, - room_id3, - }, -+ exact=True, -+ message="Corresponding map to disambiguate the opaque room IDs: " -+ + str( -+ { -+ "room_id1": room_id1, -+ "room_id2": room_id2, -+ "room_id3": room_id3, -+ } -+ ), - ) - - # Room1 - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id1].event_id, -+ interested_rooms.room_membership_for_user_map[room_id1].event_id, - join_room1_response["event_id"], - ) -- self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id1].membership, -+ Membership.JOIN, -+ ) - # We should be `newly_joined` because we joined during the token range - self.assertTrue(room_id1 in newly_joined) - self.assertTrue(room_id1 not in newly_left) - -- # Room2 -- # It should be pointing to the latest membership event in the from/to range -- self.assertEqual( -- room_id_results[room_id2].event_id, -- leave_room2_response["event_id"], -- ) -- self.assertEqual(room_id_results[room_id2].membership, Membership.LEAVE) -- # room_id2 should *NOT* be considered `newly_left` because we left before the -- # from/to range and the join event during the range happened while worker2 was -- # stuck. This means that from the perspective of the master, where the -- # `stuck_activity_token` is generated, the stream position for worker2 wasn't -- # advanced to the join yet. Looking at the `instance_map`, the join technically -- # comes after `stuck_activity_token`. -- self.assertTrue(room_id2 not in newly_joined) -- self.assertTrue(room_id2 not in newly_left) -- - # Room3 - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( -- room_id_results[room_id3].event_id, -+ interested_rooms.room_membership_for_user_map[room_id3].event_id, - join_on_worker3_response["event_id"], - ) -- self.assertEqual(room_id_results[room_id3].membership, Membership.JOIN) -+ self.assertEqual( -+ interested_rooms.room_membership_for_user_map[room_id3].membership, -+ Membership.JOIN, -+ ) - # We should be `newly_joined` because we joined during the token range - self.assertTrue(room_id3 in newly_joined) - self.assertTrue(room_id3 not in newly_left) -@@ -2645,6 +3257,9 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - self.store = self.hs.get_datastores().main - self.event_sources = hs.get_event_sources() - self.storage_controllers = hs.get_storage_controllers() -+ persistence = self.hs.get_storage_controllers().persistence -+ assert persistence is not None -+ self.persistence = persistence - - def _get_sync_room_ids_for_user( - self, -@@ -2687,7 +3302,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - to_token=now_token, - ) - -- self.assertEqual(room_id_results.keys(), set()) -+ self.assertIncludes(room_id_results.keys(), set(), exact=True) - - def test_basic_rooms(self) -> None: - """ -@@ -2753,7 +3368,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - ) - - # Ensure that the invited, ban, and knock rooms show up -- self.assertEqual( -+ self.assertIncludes( - room_id_results.keys(), - { - join_room_id, -@@ -2761,6 +3376,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - ban_room_id, - knock_room_id, - }, -+ exact=True, - ) - # It should be pointing to the the respective membership event (latest - # membership event in the from/to range) -@@ -2824,7 +3440,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - ) - - # Only the `newly_left` room should show up -- self.assertEqual(room_id_results.keys(), {room_id2}) -+ self.assertIncludes(room_id_results.keys(), {room_id2}, exact=True) - self.assertEqual( - room_id_results[room_id2].event_id, - _leave_response2["event_id"], -@@ -2869,7 +3485,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - ) - - # The kicked room should show up -- self.assertEqual(room_id_results.keys(), {kick_room_id}) -+ self.assertIncludes(room_id_results.keys(), {kick_room_id}, exact=True) - # It should be pointing to the latest membership event in the from/to range - self.assertEqual( - room_id_results[kick_room_id].event_id, -@@ -2893,8 +3509,17 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - user2_tok = self.login(user2_id, "pass") - - # The room where the state reset will happen -- room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) -- join_response1 = self.helper.join(room_id1, user1_id, tok=user1_tok) -+ room_id1 = self.helper.create_room_as( -+ user2_id, -+ is_public=True, -+ tok=user2_tok, -+ ) -+ # Create a dummy event for us to point back to for the state reset -+ dummy_event_response = self.helper.send(room_id1, "test", tok=user2_tok) -+ dummy_event_id = dummy_event_response["event_id"] -+ -+ # Join after the dummy event -+ self.helper.join(room_id1, user1_id, tok=user1_tok) - - # Join another room so we don't hit the short-circuit and return early if they - # have no room membership -@@ -2903,61 +3528,26 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - - before_reset_token = self.event_sources.get_current_token() - -- # Send another state event to make a position for the state reset to happen at -- dummy_state_response = self.helper.send_state( -- room_id1, -- event_type="foobarbaz", -- state_key="", -- body={"foo": "bar"}, -- tok=user2_tok, -- ) -- dummy_state_pos = self.get_success( -- self.store.get_position_for_event(dummy_state_response["event_id"]) -- ) -- -- # Mock a state reset removing the membership for user1 in the current state -- self.get_success( -- self.store.db_pool.simple_delete( -- table="current_state_events", -- keyvalues={ -- "room_id": room_id1, -- "type": EventTypes.Member, -- "state_key": user1_id, -- }, -- desc="state reset user in current_state_events", -- ) -- ) -- self.get_success( -- self.store.db_pool.simple_delete( -- table="local_current_membership", -- keyvalues={ -- "room_id": room_id1, -- "user_id": user1_id, -- }, -- desc="state reset user in local_current_membership", -+ # Trigger a state reset -+ join_rule_event, join_rule_context = self.get_success( -+ create_event( -+ self.hs, -+ prev_event_ids=[dummy_event_id], -+ type=EventTypes.JoinRules, -+ state_key="", -+ content={"join_rule": JoinRules.INVITE}, -+ sender=user2_id, -+ room_id=room_id1, -+ room_version=self.get_success(self.store.get_room_version_id(room_id1)), - ) - ) -- self.get_success( -- self.store.db_pool.simple_insert( -- table="current_state_delta_stream", -- values={ -- "stream_id": dummy_state_pos.stream, -- "room_id": room_id1, -- "type": EventTypes.Member, -- "state_key": user1_id, -- "event_id": None, -- "prev_event_id": join_response1["event_id"], -- "instance_name": dummy_state_pos.instance_name, -- }, -- desc="state reset user in current_state_delta_stream", -- ) -+ _, join_rule_event_pos, _ = self.get_success( -+ self.persistence.persist_event(join_rule_event, join_rule_context) - ) - -- # Manually bust the cache since we we're just manually messing with the database -- # and not causing an actual state reset. -- self.store._membership_stream_cache.entity_has_changed( -- user1_id, dummy_state_pos.stream -- ) -+ # Ensure that the state reset worked and only user2 is in the room now -+ users_in_room = self.get_success(self.store.get_users_in_room(room_id1)) -+ self.assertIncludes(set(users_in_room), {user2_id}, exact=True) - - after_reset_token = self.event_sources.get_current_token() - -@@ -2969,7 +3559,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase): - ) - - # Room1 should show up because it was `newly_left` via state reset during the from/to range -- self.assertEqual(room_id_results.keys(), {room_id1, room_id2}) -+ self.assertIncludes(room_id_results.keys(), {room_id1, room_id2}, exact=True) - # It should be pointing to no event because we were removed from the room - # without a corresponding leave event - self.assertEqual( --- -2.49.0 -