From d5accec2e5c857bf6ba71259ea052f5fe173b2eb Mon Sep 17 00:00:00 2001
From: Erik Johnston <erikj@element.io>
Date: Fri, 6 Sep 2024 11:12:29 +0100
Subject: [PATCH] Speed up sliding sync by avoiding copies (#17670)

We ended up spending ~10% CPU creating a new dictionary and
`_RoomMembershipForUser`, so let's avoid creating new dicts and copying
by returning `newly_joined`, `newly_left` and `is_dm` as sets directly.

---------

Co-authored-by: Eric Eastwood <eric.eastwood@beta.gouv.fr>
---
 changelog.d/17670.misc                      |   1 +
 synapse/handlers/sliding_sync/__init__.py   |  22 +-
 synapse/handlers/sliding_sync/room_lists.py | 175 ++++------
 synapse/storage/roommember.py               |  14 +
 tests/handlers/test_sliding_sync.py         | 346 +++++++++++---------
 5 files changed, 296 insertions(+), 262 deletions(-)
 create mode 100644 changelog.d/17670.misc

diff --git a/changelog.d/17670.misc b/changelog.d/17670.misc
new file mode 100644
index 0000000000..3550679247
--- /dev/null
+++ b/changelog.d/17670.misc
@@ -0,0 +1 @@
+Small performance improvement in speeding up sliding sync.
diff --git a/synapse/handlers/sliding_sync/__init__.py b/synapse/handlers/sliding_sync/__init__.py
index ac6dc79fdf..0f06ffaa11 100644
--- a/synapse/handlers/sliding_sync/__init__.py
+++ b/synapse/handlers/sliding_sync/__init__.py
@@ -25,8 +25,8 @@ from synapse.events.utils import strip_event
 from synapse.handlers.relations import BundledAggregations
 from synapse.handlers.sliding_sync.extensions import SlidingSyncExtensionHandler
 from synapse.handlers.sliding_sync.room_lists import (
+    RoomsForUserType,
     SlidingSyncRoomLists,
-    _RoomMembershipForUser,
 )
 from synapse.handlers.sliding_sync.store import SlidingSyncConnectionStore
 from synapse.logging.opentracing import (
@@ -39,7 +39,9 @@ from synapse.logging.opentracing import (
 )
 from synapse.storage.databases.main.roommember import extract_heroes_from_room_summary
 from synapse.storage.databases.main.stream import PaginateFunction
-from synapse.storage.roommember import MemberSummary
+from synapse.storage.roommember import (
+    MemberSummary,
+)
 from synapse.types import (
     JsonDict,
     PersistedEventPosition,
@@ -255,6 +257,8 @@ class SlidingSyncHandler:
                 ],
                 from_token=from_token,
                 to_token=to_token,
+                newly_joined=room_id in interested_rooms.newly_joined_rooms,
+                is_dm=room_id in interested_rooms.dm_room_ids,
             )
 
             # Filter out empty room results during incremental sync
@@ -352,7 +356,7 @@ class SlidingSyncHandler:
     async def get_current_state_ids_at(
         self,
         room_id: str,
-        room_membership_for_user_at_to_token: _RoomMembershipForUser,
+        room_membership_for_user_at_to_token: RoomsForUserType,
         state_filter: StateFilter,
         to_token: StreamToken,
     ) -> StateMap[str]:
@@ -417,7 +421,7 @@ class SlidingSyncHandler:
     async def get_current_state_at(
         self,
         room_id: str,
-        room_membership_for_user_at_to_token: _RoomMembershipForUser,
+        room_membership_for_user_at_to_token: RoomsForUserType,
         state_filter: StateFilter,
         to_token: StreamToken,
     ) -> StateMap[EventBase]:
@@ -457,9 +461,11 @@ class SlidingSyncHandler:
         new_connection_state: "MutablePerConnectionState",
         room_id: str,
         room_sync_config: RoomSyncConfig,
-        room_membership_for_user_at_to_token: _RoomMembershipForUser,
+        room_membership_for_user_at_to_token: RoomsForUserType,
         from_token: Optional[SlidingSyncStreamToken],
         to_token: StreamToken,
+        newly_joined: bool,
+        is_dm: bool,
     ) -> SlidingSyncResult.RoomResult:
         """
         Fetch room data for the sync response.
@@ -475,6 +481,8 @@ class SlidingSyncHandler:
                 in the room at the time of `to_token`.
             from_token: The point in the stream to sync from.
             to_token: The point in the stream to sync up to.
+            newly_joined: If the user has newly joined the room
+            is_dm: Whether the room is a DM room
         """
         user = sync_config.user
 
@@ -519,7 +527,7 @@ class SlidingSyncHandler:
         from_bound = None
         initial = True
         ignore_timeline_bound = False
-        if from_token and not room_membership_for_user_at_to_token.newly_joined:
+        if from_token and not newly_joined:
             room_status = previous_connection_state.rooms.have_sent_room(room_id)
             if room_status.status == HaveSentRoomFlag.LIVE:
                 from_bound = from_token.stream_token.room_key
@@ -1044,7 +1052,7 @@ class SlidingSyncHandler:
             name=room_name,
             avatar=room_avatar,
             heroes=heroes,
-            is_dm=room_membership_for_user_at_to_token.is_dm,
+            is_dm=is_dm,
             initial=initial,
             required_state=list(required_room_state.values()),
             timeline_events=timeline_events,
diff --git a/synapse/handlers/sliding_sync/room_lists.py b/synapse/handlers/sliding_sync/room_lists.py
index 1423d6ca53..a77b7ef2c3 100644
--- a/synapse/handlers/sliding_sync/room_lists.py
+++ b/synapse/handlers/sliding_sync/room_lists.py
@@ -19,7 +19,6 @@ from itertools import chain
 from typing import (
     TYPE_CHECKING,
     AbstractSet,
-    Any,
     Dict,
     List,
     Literal,
@@ -48,7 +47,11 @@ from synapse.storage.databases.main.state import (
     Sentinel as StateSentinel,
 )
 from synapse.storage.databases.main.stream import CurrentStateDeltaMembership
-from synapse.storage.roommember import RoomsForUser, RoomsForUserSlidingSync
+from synapse.storage.roommember import (
+    RoomsForUser,
+    RoomsForUserSlidingSync,
+    RoomsForUserStateReset,
+)
 from synapse.types import (
     MutableStateMap,
     PersistedEventPosition,
@@ -76,6 +79,11 @@ if TYPE_CHECKING:
 logger = logging.getLogger(__name__)
 
 
+# Helper definition for the types that we might return. We do this to avoid
+# copying data between types (which can be expensive for many rooms).
+RoomsForUserType = Union[RoomsForUserStateReset, RoomsForUser, RoomsForUserSlidingSync]
+
+
 @attr.s(auto_attribs=True, slots=True, frozen=True)
 class SlidingSyncInterestedRooms:
     """The set of rooms and metadata a client is interested in based on their
@@ -91,13 +99,22 @@ class SlidingSyncInterestedRooms:
             includes the rooms that *may* have relevant updates. Rooms not
             in this map will definitely not have room updates (though
             extensions may have updates in these rooms).
+        newly_joined_rooms: The set of rooms that were joined in the token range
+            and the user is still joined to at the end of this range.
+        newly_left_rooms: The set of rooms that we left in the token range
+            and are still "leave" at the end of this range.
+        dm_room_ids: The set of rooms the user consider as direct-message (DM) rooms
     """
 
     lists: Mapping[str, SlidingSyncResult.SlidingWindowList]
     relevant_room_map: Mapping[str, RoomSyncConfig]
     relevant_rooms_to_send_map: Mapping[str, RoomSyncConfig]
     all_rooms: Set[str]
-    room_membership_for_user_map: Mapping[str, "_RoomMembershipForUser"]
+    room_membership_for_user_map: Mapping[str, RoomsForUserType]
+
+    newly_joined_rooms: AbstractSet[str]
+    newly_left_rooms: AbstractSet[str]
+    dm_room_ids: AbstractSet[str]
 
 
 class Sentinel(enum.Enum):
@@ -106,47 +123,10 @@ class Sentinel(enum.Enum):
     UNSET_SENTINEL = object()
 
 
-@attr.s(slots=True, frozen=True, auto_attribs=True)
-class _RoomMembershipForUser:
-    """
-    Attributes:
-        room_id: The room ID of the membership event
-        event_id: The event ID of the membership event
-        event_pos: The stream position of the membership event
-        membership: The membership state of the user in the room
-        sender: The person who sent the membership event
-        newly_joined: Whether the user newly joined the room during the given token
-            range and is still joined to the room at the end of this range.
-        newly_left: Whether the user newly left (or kicked) the room during the given
-            token range and is still "leave" at the end of this range.
-        is_dm: Whether this user considers this room as a direct-message (DM) room
-    """
-
-    room_id: str
-    # Optional because state resets can affect room membership without a corresponding event.
-    event_id: Optional[str]
-    # Even during a state reset which removes the user from the room, we expect this to
-    # be set because `current_state_delta_stream` will note the position that the reset
-    # happened.
-    event_pos: PersistedEventPosition
-    # Even during a state reset which removes the user from the room, we expect this to
-    # be set to `LEAVE` because we can make that assumption based on the situaton (see
-    # `get_current_state_delta_membership_changes_for_user(...)`)
-    membership: str
-    # Optional because state resets can affect room membership without a corresponding event.
-    sender: Optional[str]
-    newly_joined: bool
-    newly_left: bool
-    is_dm: bool
-
-    def copy_and_replace(self, **kwds: Any) -> "_RoomMembershipForUser":
-        return attr.evolve(self, **kwds)
-
-
 def filter_membership_for_sync(
     *,
     user_id: str,
-    room_membership_for_user: Union[_RoomMembershipForUser, RoomsForUserSlidingSync],
+    room_membership_for_user: RoomsForUserType,
     newly_left: bool,
 ) -> bool:
     """
@@ -479,22 +459,10 @@ class SlidingSyncRoomLists:
             relevant_room_map=relevant_room_map,
             relevant_rooms_to_send_map=relevant_rooms_to_send_map,
             all_rooms=all_rooms,
-            room_membership_for_user_map={
-                # FIXME: Ideally we wouldn't have to create a new
-                # `_RoomMembershipForUser` here and instead just return
-                # `newly_joined_room_ids` directly, to save CPU time.
-                room_id: _RoomMembershipForUser(
-                    room_id=room_id,
-                    event_id=membership_info.event_id,
-                    event_pos=membership_info.event_pos,
-                    sender=membership_info.sender,
-                    membership=membership_info.membership,
-                    newly_joined=room_id in newly_joined_room_ids,
-                    newly_left=room_id in newly_left_room_map,
-                    is_dm=room_id in dm_room_ids,
-                )
-                for room_id, membership_info in room_membership_for_user_map.items()
-            },
+            room_membership_for_user_map=room_membership_for_user_map,
+            newly_joined_rooms=newly_joined_room_ids,
+            newly_left_rooms=set(newly_left_room_map),
+            dm_room_ids=dm_room_ids,
         )
 
     async def _compute_interested_rooms_fallback(
@@ -506,12 +474,16 @@ class SlidingSyncRoomLists:
     ) -> SlidingSyncInterestedRooms:
         """Fallback code when the database background updates haven't completed yet."""
 
-        room_membership_for_user_map = (
-            await self.get_room_membership_for_user_at_to_token(
-                sync_config.user, to_token, from_token
-            )
+        (
+            room_membership_for_user_map,
+            newly_joined_room_ids,
+            newly_left_room_ids,
+        ) = await self.get_room_membership_for_user_at_to_token(
+            sync_config.user, to_token, from_token
         )
 
+        dm_room_ids = await self._get_dm_rooms_for_user(sync_config.user.to_string())
+
         # Assemble sliding window lists
         lists: Dict[str, SlidingSyncResult.SlidingWindowList] = {}
         # Keep track of the rooms that we can display and need to fetch more info about
@@ -525,6 +497,7 @@ class SlidingSyncRoomLists:
                 sync_room_map = await self.filter_rooms_relevant_for_sync(
                     user=sync_config.user,
                     room_membership_for_user_map=room_membership_for_user_map,
+                    newly_left_room_ids=newly_left_room_ids,
                 )
 
                 for list_key, list_config in sync_config.lists.items():
@@ -536,6 +509,7 @@ class SlidingSyncRoomLists:
                             sync_room_map,
                             list_config.filters,
                             to_token,
+                            dm_room_ids,
                         )
 
                     # Find which rooms are partially stated and may need to be filtered out
@@ -679,6 +653,9 @@ class SlidingSyncRoomLists:
             relevant_rooms_to_send_map=relevant_rooms_to_send_map,
             all_rooms=all_rooms,
             room_membership_for_user_map=room_membership_for_user_map,
+            newly_joined_rooms=newly_joined_room_ids,
+            newly_left_rooms=newly_left_room_ids,
+            dm_room_ids=dm_room_ids,
         )
 
     async def _filter_relevant_room_to_send(
@@ -755,7 +732,7 @@ class SlidingSyncRoomLists:
     async def _get_rewind_changes_to_current_membership_to_token(
         self,
         user: UserID,
-        rooms_for_user: Mapping[str, Union[RoomsForUser, RoomsForUserSlidingSync]],
+        rooms_for_user: Mapping[str, RoomsForUserType],
         to_token: StreamToken,
     ) -> Mapping[str, Optional[RoomsForUser]]:
         """
@@ -907,7 +884,7 @@ class SlidingSyncRoomLists:
         user: UserID,
         to_token: StreamToken,
         from_token: Optional[StreamToken],
-    ) -> Dict[str, _RoomMembershipForUser]:
+    ) -> Tuple[Dict[str, RoomsForUserType], AbstractSet[str], AbstractSet[str]]:
         """
         Fetch room IDs that the user has had membership in (the full room list including
         long-lost left rooms that will be filtered, sorted, and sliced).
@@ -926,8 +903,11 @@ class SlidingSyncRoomLists:
             from_token: The point in the stream to sync from.
 
         Returns:
-            A dictionary of room IDs that the user has had membership in along with
-            membership information in that room at the time of `to_token`.
+            A 3-tuple of:
+              - A dictionary of room IDs that the user has had membership in along with
+                membership information in that room at the time of `to_token`.
+              - Set of newly joined rooms
+              - Set of newly left rooms
         """
         user_id = user.to_string()
 
@@ -944,12 +924,14 @@ class SlidingSyncRoomLists:
 
         # If the user has never joined any rooms before, we can just return an empty list
         if not room_for_user_list:
-            return {}
+            return {}, set(), set()
 
         # Since we fetched the users room list at some point in time after the
         # tokens, we need to revert/rewind some membership changes to match the point in
         # time of the `to_token`.
-        rooms_for_user = {room.room_id: room for room in room_for_user_list}
+        rooms_for_user: Dict[str, RoomsForUserType] = {
+            room.room_id: room for room in room_for_user_list
+        }
         changes = await self._get_rewind_changes_to_current_membership_to_token(
             user, rooms_for_user, to_token
         )
@@ -966,42 +948,23 @@ class SlidingSyncRoomLists:
             user_id, to_token=to_token, from_token=from_token
         )
 
-        dm_room_ids = await self._get_dm_rooms_for_user(user_id)
-
-        # Our working list of rooms that can show up in the sync response
-        sync_room_id_set = {
-            room_for_user.room_id: _RoomMembershipForUser(
-                room_id=room_for_user.room_id,
-                event_id=room_for_user.event_id,
-                event_pos=room_for_user.event_pos,
-                membership=room_for_user.membership,
-                sender=room_for_user.sender,
-                newly_joined=room_id in newly_joined_room_ids,
-                newly_left=room_id in newly_left_room_ids,
-                is_dm=room_id in dm_room_ids,
-            )
-            for room_id, room_for_user in rooms_for_user.items()
-        }
-
         # Ensure we have entries for rooms that the user has been "state reset"
         # out of. These are rooms appear in the `newly_left_rooms` map but
         # aren't in the `rooms_for_user` map.
         for room_id, left_event_pos in newly_left_room_ids.items():
-            if room_id in sync_room_id_set:
+            if room_id in rooms_for_user:
                 continue
 
-            sync_room_id_set[room_id] = _RoomMembershipForUser(
+            rooms_for_user[room_id] = RoomsForUserStateReset(
                 room_id=room_id,
                 event_id=None,
                 event_pos=left_event_pos,
                 membership=Membership.LEAVE,
                 sender=None,
-                newly_joined=False,
-                newly_left=True,
-                is_dm=room_id in dm_room_ids,
+                room_version_id=await self.store.get_room_version_id(room_id),
             )
 
-        return sync_room_id_set
+        return rooms_for_user, newly_joined_room_ids, set(newly_left_room_ids)
 
     @trace
     async def _get_newly_joined_and_left_rooms(
@@ -1009,7 +972,7 @@ class SlidingSyncRoomLists:
         user_id: str,
         to_token: StreamToken,
         from_token: Optional[StreamToken],
-    ) -> Tuple[StrCollection, Mapping[str, PersistedEventPosition]]:
+    ) -> Tuple[AbstractSet[str], Mapping[str, PersistedEventPosition]]:
         """Fetch the sets of rooms that the user newly joined or left in the
         given token range.
 
@@ -1162,8 +1125,9 @@ class SlidingSyncRoomLists:
     async def filter_rooms_relevant_for_sync(
         self,
         user: UserID,
-        room_membership_for_user_map: Dict[str, _RoomMembershipForUser],
-    ) -> Dict[str, _RoomMembershipForUser]:
+        room_membership_for_user_map: Dict[str, RoomsForUserType],
+        newly_left_room_ids: AbstractSet[str],
+    ) -> Dict[str, RoomsForUserType]:
         """
         Filter room IDs that should/can be listed for this user in the sync response (the
         full room list that will be further filtered, sorted, and sliced).
@@ -1184,6 +1148,7 @@ class SlidingSyncRoomLists:
         Args:
             user: User that is syncing
             room_membership_for_user_map: Room membership for the user
+            newly_left_room_ids: The set of room IDs we have newly left
 
         Returns:
             A dictionary of room IDs that should be listed in the sync response along
@@ -1198,7 +1163,7 @@ class SlidingSyncRoomLists:
             if filter_membership_for_sync(
                 user_id=user_id,
                 room_membership_for_user=room_membership_for_user,
-                newly_left=room_membership_for_user.newly_left,
+                newly_left=room_id in newly_left_room_ids,
             )
         }
 
@@ -1207,9 +1172,9 @@ class SlidingSyncRoomLists:
     async def check_room_subscription_allowed_for_user(
         self,
         room_id: str,
-        room_membership_for_user_map: Dict[str, _RoomMembershipForUser],
+        room_membership_for_user_map: Dict[str, RoomsForUserType],
         to_token: StreamToken,
-    ) -> Optional[_RoomMembershipForUser]:
+    ) -> Optional[RoomsForUserType]:
         """
         Check whether the user is allowed to see the room based on whether they have
         ever had membership in the room or if the room is `world_readable`.
@@ -1274,7 +1239,7 @@ class SlidingSyncRoomLists:
     async def _bulk_get_stripped_state_for_rooms_from_sync_room_map(
         self,
         room_ids: StrCollection,
-        sync_room_map: Dict[str, _RoomMembershipForUser],
+        sync_room_map: Dict[str, RoomsForUserType],
     ) -> Dict[str, Optional[StateMap[StrippedStateEvent]]]:
         """
         Fetch stripped state for a list of room IDs. Stripped state is only
@@ -1371,7 +1336,7 @@ class SlidingSyncRoomLists:
             "room_encryption",
         ],
         room_ids: Set[str],
-        sync_room_map: Dict[str, _RoomMembershipForUser],
+        sync_room_map: Dict[str, RoomsForUserType],
         to_token: StreamToken,
         room_id_to_stripped_state_map: Dict[
             str, Optional[StateMap[StrippedStateEvent]]
@@ -1535,10 +1500,11 @@ class SlidingSyncRoomLists:
     async def filter_rooms(
         self,
         user: UserID,
-        sync_room_map: Dict[str, _RoomMembershipForUser],
+        sync_room_map: Dict[str, RoomsForUserType],
         filters: SlidingSyncConfig.SlidingSyncList.Filters,
         to_token: StreamToken,
-    ) -> Dict[str, _RoomMembershipForUser]:
+        dm_room_ids: AbstractSet[str],
+    ) -> Dict[str, RoomsForUserType]:
         """
         Filter rooms based on the sync request.
 
@@ -1548,6 +1514,7 @@ class SlidingSyncRoomLists:
                 information in the room at the time of `to_token`.
             filters: Filters to apply
             to_token: We filter based on the state of the room at this token
+            dm_room_ids: Set of room IDs that are DMs for the user
 
         Returns:
             A filtered dictionary of room IDs along with membership information in the
@@ -1567,14 +1534,14 @@ class SlidingSyncRoomLists:
                     filtered_room_id_set = {
                         room_id
                         for room_id in filtered_room_id_set
-                        if sync_room_map[room_id].is_dm
+                        if room_id in dm_room_ids
                     }
                 else:
                     # Only non-DM rooms please
                     filtered_room_id_set = {
                         room_id
                         for room_id in filtered_room_id_set
-                        if not sync_room_map[room_id].is_dm
+                        if room_id not in dm_room_ids
                     }
 
         if filters.spaces is not None:
@@ -1862,9 +1829,9 @@ class SlidingSyncRoomLists:
     @trace
     async def sort_rooms(
         self,
-        sync_room_map: Dict[str, _RoomMembershipForUser],
+        sync_room_map: Dict[str, RoomsForUserType],
         to_token: StreamToken,
-    ) -> List[_RoomMembershipForUser]:
+    ) -> List[RoomsForUserType]:
         """
         Sort by `stream_ordering` of the last event that the user should see in the
         room. `stream_ordering` is unique so we get a stable sort.
diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py
index 09213627ec..af71c01c17 100644
--- a/synapse/storage/roommember.py
+++ b/synapse/storage/roommember.py
@@ -52,6 +52,20 @@ class RoomsForUserSlidingSync:
     is_encrypted: bool
 
 
+@attr.s(slots=True, frozen=True, weakref_slot=False, auto_attribs=True)
+class RoomsForUserStateReset:
+    """A version of `RoomsForUser` that supports optional sender and event ID
+    fields, to handle state resets. State resets can affect room membership
+    without a corresponding event so that information isn't always available."""
+
+    room_id: str
+    sender: Optional[str]
+    membership: str
+    event_id: Optional[str]
+    event_pos: PersistedEventPosition
+    room_version_id: str
+
+
 @attr.s(slots=True, frozen=True, weakref_slot=False, auto_attribs=True)
 class GetRoomsForUserWithStreamOrdering:
     room_id: str
diff --git a/tests/handlers/test_sliding_sync.py b/tests/handlers/test_sliding_sync.py
index 2ef9f665f9..7511a5b00a 100644
--- a/tests/handlers/test_sliding_sync.py
+++ b/tests/handlers/test_sliding_sync.py
@@ -18,7 +18,7 @@
 #
 #
 import logging
-from typing import Dict, List, Optional
+from typing import AbstractSet, Dict, List, Optional, Tuple
 from unittest.mock import patch
 
 from parameterized import parameterized
@@ -37,9 +37,9 @@ from synapse.api.room_versions import RoomVersions
 from synapse.events import StrippedStateEvent, make_event_from_dict
 from synapse.events.snapshot import EventContext
 from synapse.handlers.sliding_sync import (
+    RoomsForUserType,
     RoomSyncConfig,
     StateValues,
-    _RoomMembershipForUser,
 )
 from synapse.rest import admin
 from synapse.rest.client import knock, login, room
@@ -606,7 +606,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         now_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=now_token,
@@ -633,7 +633,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         after_room_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=before_room_token,
@@ -651,8 +651,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertEqual(room_id_results[room_id].membership, Membership.JOIN)
         # We should be considered `newly_joined` because we joined during the token
         # range
-        self.assertEqual(room_id_results[room_id].newly_joined, True)
-        self.assertEqual(room_id_results[room_id].newly_left, False)
+        self.assertTrue(room_id in newly_joined)
+        self.assertTrue(room_id not in newly_left)
 
     def test_get_already_joined_room(self) -> None:
         """
@@ -668,7 +668,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         after_room_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=after_room_token,
@@ -685,8 +685,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id].membership, Membership.JOIN)
         # We should *NOT* be `newly_joined` because we joined before the token range
-        self.assertEqual(room_id_results[room_id].newly_joined, False)
-        self.assertEqual(room_id_results[room_id].newly_left, False)
+        self.assertTrue(room_id not in newly_joined)
+        self.assertTrue(room_id not in newly_left)
 
     def test_get_invited_banned_knocked_room(self) -> None:
         """
@@ -742,7 +742,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         after_room_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=before_room_token,
@@ -766,24 +766,24 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
             invite_response["event_id"],
         )
         self.assertEqual(room_id_results[invited_room_id].membership, Membership.INVITE)
-        self.assertEqual(room_id_results[invited_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[invited_room_id].newly_left, False)
+        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,
             ban_response["event_id"],
         )
         self.assertEqual(room_id_results[ban_room_id].membership, Membership.BAN)
-        self.assertEqual(room_id_results[ban_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[ban_room_id].newly_left, False)
+        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,
             knock_room_membership_state_event.event_id,
         )
         self.assertEqual(room_id_results[knock_room_id].membership, Membership.KNOCK)
-        self.assertEqual(room_id_results[knock_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[knock_room_id].newly_left, False)
+        self.assertTrue(knock_room_id not in newly_joined)
+        self.assertTrue(knock_room_id not in newly_left)
 
     def test_get_kicked_room(self) -> None:
         """
@@ -814,7 +814,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         after_kick_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=after_kick_token,
@@ -833,8 +833,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertNotEqual(room_id_results[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.assertEqual(room_id_results[kick_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[kick_room_id].newly_left, False)
+        self.assertTrue(kick_room_id not in newly_joined)
+        self.assertTrue(kick_room_id not in newly_left)
 
     def test_forgotten_rooms(self) -> None:
         """
@@ -907,7 +907,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(channel.code, 200, channel.result)
 
-        room_id_results = self.get_success(
+        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),
                 from_token=before_room_forgets,
@@ -937,7 +937,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         after_room2_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -954,8 +954,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         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.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        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,
@@ -963,8 +963,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id2].membership, Membership.LEAVE)
         # We should *NOT* be `newly_joined` because we are instead `newly_left`
-        self.assertEqual(room_id_results[room_id2].newly_joined, False)
-        self.assertEqual(room_id_results[room_id2].newly_left, True)
+        self.assertTrue(room_id2 not in newly_joined)
+        self.assertTrue(room_id2 in newly_left)
 
     def test_no_joins_after_to_token(self) -> None:
         """
@@ -987,7 +987,7 @@ 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 = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -1003,8 +1003,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should be `newly_joined` because we joined during the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, True)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_join_during_range_and_left_room_after_to_token(self) -> None:
         """
@@ -1027,7 +1027,7 @@ 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 = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -1052,8 +1052,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should be `newly_joined` because we joined during the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, True)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_join_before_range_and_left_room_after_to_token(self) -> None:
         """
@@ -1074,7 +1074,7 @@ 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 = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -1098,8 +1098,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should *NOT* be `newly_joined` because we joined before the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 not in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_kicked_before_range_and_left_after_to_token(self) -> None:
         """
@@ -1138,7 +1138,7 @@ 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 = self.get_success(
+        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),
                 from_token=after_kick_token,
@@ -1165,8 +1165,8 @@ 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)
         # We should *NOT* be `newly_joined` because we were kicked
-        self.assertEqual(room_id_results[kick_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[kick_room_id].newly_left, False)
+        self.assertTrue(kick_room_id not in newly_joined)
+        self.assertTrue(kick_room_id not in newly_left)
 
     def test_newly_left_during_range_and_join_leave_after_to_token(self) -> None:
         """
@@ -1194,7 +1194,7 @@ 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 = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -1221,8 +1221,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE)
         # We should *NOT* be `newly_joined` because we are actually `newly_left` during
         # the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, True)
+        self.assertTrue(room_id1 not in newly_joined)
+        self.assertTrue(room_id1 in newly_left)
 
     def test_newly_left_during_range_and_join_after_to_token(self) -> None:
         """
@@ -1249,7 +1249,7 @@ 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 = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -1275,8 +1275,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertEqual(room_id_results[room_id1].membership, Membership.LEAVE)
         # We should *NOT* be `newly_joined` because we are actually `newly_left` during
         # the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, True)
+        self.assertTrue(room_id1 not in newly_joined)
+        self.assertTrue(room_id1 in newly_left)
 
     def test_no_from_token(self) -> None:
         """
@@ -1308,7 +1308,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         # Join the room2 after we already have our tokens
         self.helper.join(room_id2, user1_id, tok=user1_tok)
 
-        room_id_results = self.get_success(
+        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),
                 from_token=None,
@@ -1328,8 +1328,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         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.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        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
@@ -1340,8 +1340,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         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.assertEqual(room_id_results[room_id2].newly_joined, False)
-        self.assertEqual(room_id_results[room_id2].newly_left, False)
+        self.assertTrue(room_id2 not in newly_joined)
+        self.assertTrue(room_id2 not in newly_left)
 
     def test_from_token_ahead_of_to_token(self) -> None:
         """
@@ -1390,7 +1390,7 @@ 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 = self.get_success(
+        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),
                 from_token=from_token,
@@ -1424,8 +1424,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should *NOT* be `newly_joined`/`newly_left` because we joined `room1`
         # before either of the tokens
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        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
@@ -1436,8 +1436,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         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.assertEqual(room_id_results[room_id2].newly_joined, False)
-        self.assertEqual(room_id_results[room_id2].newly_left, False)
+        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:
         """
@@ -1463,7 +1463,7 @@ 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 = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -1480,8 +1480,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         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.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 not in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_leave_before_range_and_join_after_to_token(self) -> None:
         """
@@ -1506,7 +1506,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         # Join the room after we already have our tokens
         self.helper.join(room_id1, user1_id, tok=user1_tok)
 
-        room_id_results = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -1523,8 +1523,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         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.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 not in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_join_leave_multiple_times_during_range_and_after_to_token(
         self,
@@ -1556,7 +1556,7 @@ 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 = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -1584,10 +1584,10 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should be `newly_joined` because we joined during the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, True)
+        self.assertTrue(room_id1 in newly_joined)
         # We should *NOT* be `newly_left` because we joined during the token range and
         # was still joined at the end of the range
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_join_leave_multiple_times_before_range_and_after_to_token(
         self,
@@ -1618,7 +1618,7 @@ 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 = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -1646,8 +1646,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should *NOT* be `newly_joined` because we joined before the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 not in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_invite_before_range_and_join_leave_after_to_token(
         self,
@@ -1677,7 +1677,7 @@ 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 = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -1703,8 +1703,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertEqual(room_id_results[room_id1].membership, Membership.INVITE)
         # We should *NOT* be `newly_joined` because we were only invited before the
         # token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 not in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_join_and_display_name_changes_in_token_range(
         self,
@@ -1751,7 +1751,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
             tok=user1_tok,
         )
 
-        room_id_results = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -1780,8 +1780,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should be `newly_joined` because we joined during the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, True)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_display_name_changes_in_token_range(
         self,
@@ -1816,7 +1816,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         after_change1_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -1842,8 +1842,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should *NOT* be `newly_joined` because we joined before the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 not in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_display_name_changes_before_and_after_token_range(
         self,
@@ -1888,7 +1888,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
             tok=user1_tok,
         )
 
-        room_id_results = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -1917,8 +1917,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should *NOT* be `newly_joined` because we joined before the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        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(
         self,
@@ -1970,7 +1970,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         # Leave after the token
         self.helper.leave(room_id1, user1_id, tok=user1_tok)
 
-        room_id_results = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -1999,8 +1999,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should be `newly_joined` because we joined during the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, True)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_display_name_changes_join_after_token_range(
         self,
@@ -2038,7 +2038,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
             tok=user1_tok,
         )
 
-        room_id_results = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -2074,7 +2074,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         after_more_changes_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=after_room1_token,
@@ -2092,8 +2092,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertEqual(room_id_results[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.assertEqual(room_id_results[room_id1].newly_joined, True)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_newly_joined_only_joins_during_token_range(
         self,
@@ -2139,7 +2139,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
 
         after_room1_token = self.event_sources.get_current_token()
 
-        room_id_results = self.get_success(
+        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),
                 from_token=before_room1_token,
@@ -2168,8 +2168,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should be `newly_joined` because we first joined during the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, True)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        self.assertTrue(room_id1 in newly_joined)
+        self.assertTrue(room_id1 not in newly_left)
 
     def test_multiple_rooms_are_not_confused(
         self,
@@ -2215,7 +2215,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         # Leave room3
         self.helper.leave(room_id3, user1_id, tok=user1_tok)
 
-        room_id_results = self.get_success(
+        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),
                 from_token=before_room3_token,
@@ -2244,8 +2244,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         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.assertEqual(room_id_results[room_id1].newly_joined, False)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        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
@@ -2256,8 +2256,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertEqual(room_id_results[room_id2].membership, Membership.INVITE)
         # We should *NOT* be `newly_joined`/`newly_left` because we were invited before
         # the token range
-        self.assertEqual(room_id_results[room_id2].newly_joined, False)
-        self.assertEqual(room_id_results[room_id2].newly_left, False)
+        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
@@ -2268,8 +2268,8 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         self.assertEqual(room_id_results[room_id3].membership, Membership.LEAVE)
         # We should be `newly_left` because we were invited and left during
         # the token range
-        self.assertEqual(room_id_results[room_id3].newly_joined, False)
-        self.assertEqual(room_id_results[room_id3].newly_left, True)
+        self.assertTrue(room_id3 not in newly_joined)
+        self.assertTrue(room_id3 in newly_left)
 
     def test_state_reset(self) -> None:
         """
@@ -2351,7 +2351,7 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         after_reset_token = self.event_sources.get_current_token()
 
         # The function under test
-        room_id_results = self.get_success(
+        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),
                 from_token=before_reset_token,
@@ -2370,9 +2370,9 @@ class GetRoomMembershipForUserAtToTokenTestCase(HomeserverTestCase):
         # 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)
         # We should *NOT* be `newly_joined` because we joined before the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
+        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.assertEqual(room_id_results[room_id1].newly_left, True)
+        self.assertTrue(room_id1 in newly_left)
 
 
 class GetRoomMembershipForUserAtToTokenShardTestCase(BaseMultiWorkerStreamTestCase):
@@ -2565,7 +2565,7 @@ class GetRoomMembershipForUserAtToTokenShardTestCase(BaseMultiWorkerStreamTestCa
         self.get_success(actx.__aexit__(None, None, None))
 
         # The function under test
-        room_id_results = self.get_success(
+        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),
                 from_token=before_stuck_activity_token,
@@ -2590,8 +2590,8 @@ class GetRoomMembershipForUserAtToTokenShardTestCase(BaseMultiWorkerStreamTestCa
         )
         self.assertEqual(room_id_results[room_id1].membership, Membership.JOIN)
         # We should be `newly_joined` because we joined during the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, True)
-        self.assertEqual(room_id_results[room_id1].newly_left, False)
+        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
@@ -2606,8 +2606,8 @@ class GetRoomMembershipForUserAtToTokenShardTestCase(BaseMultiWorkerStreamTestCa
         # `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.assertEqual(room_id_results[room_id2].newly_joined, False)
-        self.assertEqual(room_id_results[room_id2].newly_left, False)
+        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
@@ -2617,8 +2617,8 @@ class GetRoomMembershipForUserAtToTokenShardTestCase(BaseMultiWorkerStreamTestCa
         )
         self.assertEqual(room_id_results[room_id3].membership, Membership.JOIN)
         # We should be `newly_joined` because we joined during the token range
-        self.assertEqual(room_id_results[room_id3].newly_joined, True)
-        self.assertEqual(room_id_results[room_id3].newly_left, False)
+        self.assertTrue(room_id3 in newly_joined)
+        self.assertTrue(room_id3 not in newly_left)
 
 
 class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
@@ -2651,11 +2651,11 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
         user: UserID,
         to_token: StreamToken,
         from_token: Optional[StreamToken],
-    ) -> Dict[str, _RoomMembershipForUser]:
+    ) -> Tuple[Dict[str, RoomsForUserType], AbstractSet[str], AbstractSet[str]]:
         """
         Get the rooms the user should be syncing with
         """
-        room_membership_for_user_map = self.get_success(
+        room_membership_for_user_map, newly_joined, newly_left = self.get_success(
             self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token(
                 user=user,
                 from_token=from_token,
@@ -2666,10 +2666,11 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
             self.sliding_sync_handler.room_lists.filter_rooms_relevant_for_sync(
                 user=user,
                 room_membership_for_user_map=room_membership_for_user_map,
+                newly_left_room_ids=newly_left,
             )
         )
 
-        return filtered_sync_room_map
+        return filtered_sync_room_map, newly_joined, newly_left
 
     def test_no_rooms(self) -> None:
         """
@@ -2680,7 +2681,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
 
         now_token = self.event_sources.get_current_token()
 
-        room_id_results = self._get_sync_room_ids_for_user(
+        room_id_results, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=now_token,
             to_token=now_token,
@@ -2745,7 +2746,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
 
         after_room_token = self.event_sources.get_current_token()
 
-        room_id_results = self._get_sync_room_ids_for_user(
+        room_id_results, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=before_room_token,
             to_token=after_room_token,
@@ -2768,32 +2769,32 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
             join_response["event_id"],
         )
         self.assertEqual(room_id_results[join_room_id].membership, Membership.JOIN)
-        self.assertEqual(room_id_results[join_room_id].newly_joined, True)
-        self.assertEqual(room_id_results[join_room_id].newly_left, False)
+        self.assertTrue(join_room_id in newly_joined)
+        self.assertTrue(join_room_id not in newly_left)
 
         self.assertEqual(
             room_id_results[invited_room_id].event_id,
             invite_response["event_id"],
         )
         self.assertEqual(room_id_results[invited_room_id].membership, Membership.INVITE)
-        self.assertEqual(room_id_results[invited_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[invited_room_id].newly_left, False)
+        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,
             ban_response["event_id"],
         )
         self.assertEqual(room_id_results[ban_room_id].membership, Membership.BAN)
-        self.assertEqual(room_id_results[ban_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[ban_room_id].newly_left, False)
+        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,
             knock_room_membership_state_event.event_id,
         )
         self.assertEqual(room_id_results[knock_room_id].membership, Membership.KNOCK)
-        self.assertEqual(room_id_results[knock_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[knock_room_id].newly_left, False)
+        self.assertTrue(knock_room_id not in newly_joined)
+        self.assertTrue(knock_room_id not in newly_left)
 
     def test_only_newly_left_rooms_show_up(self) -> None:
         """
@@ -2816,7 +2817,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
 
         after_room2_token = self.event_sources.get_current_token()
 
-        room_id_results = self._get_sync_room_ids_for_user(
+        room_id_results, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=after_room1_token,
             to_token=after_room2_token,
@@ -2829,8 +2830,8 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
             _leave_response2["event_id"],
         )
         # We should *NOT* be `newly_joined` because we are instead `newly_left`
-        self.assertEqual(room_id_results[room_id2].newly_joined, False)
-        self.assertEqual(room_id_results[room_id2].newly_left, True)
+        self.assertTrue(room_id2 not in newly_joined)
+        self.assertTrue(room_id2 in newly_left)
 
     def test_get_kicked_room(self) -> None:
         """
@@ -2861,7 +2862,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
 
         after_kick_token = self.event_sources.get_current_token()
 
-        room_id_results = self._get_sync_room_ids_for_user(
+        room_id_results, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=after_kick_token,
             to_token=after_kick_token,
@@ -2878,8 +2879,8 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
         self.assertNotEqual(room_id_results[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.assertEqual(room_id_results[kick_room_id].newly_joined, False)
-        self.assertEqual(room_id_results[kick_room_id].newly_left, False)
+        self.assertTrue(kick_room_id not in newly_joined)
+        self.assertTrue(kick_room_id not in newly_left)
 
     def test_state_reset(self) -> None:
         """
@@ -2961,7 +2962,7 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
         after_reset_token = self.event_sources.get_current_token()
 
         # The function under test
-        room_id_results = self._get_sync_room_ids_for_user(
+        room_id_results, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=before_reset_token,
             to_token=after_reset_token,
@@ -2978,9 +2979,9 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
         # 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)
         # We should *NOT* be `newly_joined` because we joined before the token range
-        self.assertEqual(room_id_results[room_id1].newly_joined, False)
+        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.assertEqual(room_id_results[room_id1].newly_left, True)
+        self.assertTrue(room_id1 in newly_left)
 
 
 class FilterRoomsTestCase(HomeserverTestCase):
@@ -3012,11 +3013,11 @@ class FilterRoomsTestCase(HomeserverTestCase):
         user: UserID,
         to_token: StreamToken,
         from_token: Optional[StreamToken],
-    ) -> Dict[str, _RoomMembershipForUser]:
+    ) -> Tuple[Dict[str, RoomsForUserType], AbstractSet[str], AbstractSet[str]]:
         """
         Get the rooms the user should be syncing with
         """
-        room_membership_for_user_map = self.get_success(
+        room_membership_for_user_map, newly_joined, newly_left = self.get_success(
             self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token(
                 user=user,
                 from_token=from_token,
@@ -3027,10 +3028,11 @@ class FilterRoomsTestCase(HomeserverTestCase):
             self.sliding_sync_handler.room_lists.filter_rooms_relevant_for_sync(
                 user=user,
                 room_membership_for_user_map=room_membership_for_user_map,
+                newly_left_room_ids=newly_left,
             )
         )
 
-        return filtered_sync_room_map
+        return filtered_sync_room_map, newly_joined, newly_left
 
     def _create_dm_room(
         self,
@@ -3174,8 +3176,12 @@ class FilterRoomsTestCase(HomeserverTestCase):
 
         after_rooms_token = self.event_sources.get_current_token()
 
+        dm_room_ids = self.get_success(
+            self.sliding_sync_handler.room_lists._get_dm_rooms_for_user(user1_id)
+        )
+
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -3190,6 +3196,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_dm=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=dm_room_ids,
             )
         )
 
@@ -3204,6 +3211,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_dm=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=dm_room_ids,
             )
         )
 
@@ -3231,7 +3239,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -3246,6 +3254,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3260,6 +3269,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3293,7 +3303,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             # We're using a `from_token` so that the room is considered `newly_left` and
             # appears in our list of relevant sync rooms
@@ -3310,6 +3320,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3324,6 +3335,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3367,7 +3379,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             # We're using a `from_token` so that the room is considered `newly_left` and
             # appears in our list of relevant sync rooms
@@ -3384,6 +3396,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3398,6 +3411,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3440,7 +3454,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             # We're using a `from_token` so that the room is considered `newly_left` and
             # appears in our list of relevant sync rooms
@@ -3457,6 +3471,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3478,6 +3493,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3512,7 +3528,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -3527,6 +3543,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3543,6 +3560,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3598,7 +3616,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -3613,6 +3631,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3631,6 +3650,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3679,7 +3699,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -3694,6 +3714,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3710,6 +3731,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_encrypted=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3739,7 +3761,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -3754,6 +3776,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_invite=True,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3768,6 +3791,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     is_invite=False,
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3806,7 +3830,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -3819,6 +3843,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3831,6 +3856,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3845,6 +3871,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     room_types=[None, RoomTypes.SPACE]
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3859,6 +3886,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     room_types=["org.matrix.foobarbaz"]
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3897,7 +3925,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -3910,6 +3938,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(not_room_types=[None]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3924,6 +3953,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     not_room_types=[RoomTypes.SPACE]
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3938,6 +3968,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     not_room_types=[None, RoomTypes.SPACE]
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3953,6 +3984,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     room_types=[None], not_room_types=[None]
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -3969,6 +4001,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                     room_types=[None, RoomTypes.SPACE], not_room_types=[None]
                 ),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4002,7 +4035,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             # We're using a `from_token` so that the room is considered `newly_left` and
             # appears in our list of relevant sync rooms
@@ -4017,6 +4050,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4029,6 +4063,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4071,7 +4106,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             # We're using a `from_token` so that the room is considered `newly_left` and
             # appears in our list of relevant sync rooms
@@ -4086,6 +4121,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4098,6 +4134,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4131,7 +4168,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -4144,6 +4181,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4158,6 +4196,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4207,7 +4246,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -4220,6 +4259,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4234,6 +4274,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4284,7 +4325,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -4297,6 +4338,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4311,6 +4353,7 @@ class FilterRoomsTestCase(HomeserverTestCase):
                 sync_room_map,
                 SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
                 after_rooms_token,
+                dm_room_ids=set(),
             )
         )
 
@@ -4348,11 +4391,11 @@ class SortRoomsTestCase(HomeserverTestCase):
         user: UserID,
         to_token: StreamToken,
         from_token: Optional[StreamToken],
-    ) -> Dict[str, _RoomMembershipForUser]:
+    ) -> Tuple[Dict[str, RoomsForUserType], AbstractSet[str], AbstractSet[str]]:
         """
         Get the rooms the user should be syncing with
         """
-        room_membership_for_user_map = self.get_success(
+        room_membership_for_user_map, newly_joined, newly_left = self.get_success(
             self.sliding_sync_handler.room_lists.get_room_membership_for_user_at_to_token(
                 user=user,
                 from_token=from_token,
@@ -4363,10 +4406,11 @@ class SortRoomsTestCase(HomeserverTestCase):
             self.sliding_sync_handler.room_lists.filter_rooms_relevant_for_sync(
                 user=user,
                 room_membership_for_user_map=room_membership_for_user_map,
+                newly_left_room_ids=newly_left,
             )
         )
 
-        return filtered_sync_room_map
+        return filtered_sync_room_map, newly_joined, newly_left
 
     def test_sort_activity_basic(self) -> None:
         """
@@ -4387,7 +4431,7 @@ class SortRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
@@ -4468,7 +4512,7 @@ class SortRoomsTestCase(HomeserverTestCase):
         self.helper.send(room_id3, "activity in room3", tok=user2_tok)
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=before_rooms_token,
             to_token=after_rooms_token,
@@ -4532,7 +4576,7 @@ class SortRoomsTestCase(HomeserverTestCase):
         after_rooms_token = self.event_sources.get_current_token()
 
         # Get the rooms the user should be syncing with
-        sync_room_map = self._get_sync_room_ids_for_user(
+        sync_room_map, newly_joined, newly_left = self._get_sync_room_ids_for_user(
             UserID.from_string(user1_id),
             from_token=None,
             to_token=after_rooms_token,
-- 
GitLab