From 9b83fb7c166579d8a700b5dfc141006ec0530954 Mon Sep 17 00:00:00 2001
From: Eric Eastwood <eric.eastwood@beta.gouv.fr>
Date: Thu, 12 Sep 2024 15:27:03 -0500
Subject: [PATCH] Sliding Sync: Move filters tests to rest layer (#17703)

Move filters tests to rest layer in order to test the new (with sliding
sync tables) and fallback paths that Sliding Sync can use.

Also found a bug in the new path because it's not being tested which is
also fixed in this PR. We now take into account `has_known_state` when
filtering.

Spawning from
https://github.com/element-hq/synapse/pull/17662#discussion_r1755574791.
This should have been done when we started using the new sliding sync
tables in https://github.com/element-hq/synapse/pull/17630
---
 changelog.d/17703.misc                        |    1 +
 synapse/handlers/sliding_sync/room_lists.py   |   17 +-
 synapse/rest/client/sync.py                   |    2 +-
 synapse/storage/databases/main/roommember.py  |    6 +-
 synapse/storage/roommember.py                 |    1 +
 tests/handlers/test_sliding_sync.py           | 1385 +-------------
 .../client/sliding_sync/test_lists_filters.py | 1681 +++++++++++++++++
 .../client/sliding_sync/test_rooms_meta.py    |   58 +
 .../client/sliding_sync/test_sliding_sync.py  |  572 ++----
 9 files changed, 1928 insertions(+), 1795 deletions(-)
 create mode 100644 changelog.d/17703.misc
 create mode 100644 tests/rest/client/sliding_sync/test_lists_filters.py

diff --git a/changelog.d/17703.misc b/changelog.d/17703.misc
new file mode 100644
index 0000000000..c5b0ea438a
--- /dev/null
+++ b/changelog.d/17703.misc
@@ -0,0 +1 @@
+Refactor sliding sync filter unit tests so the sliding sync API has better test coverage.
diff --git a/synapse/handlers/sliding_sync/room_lists.py b/synapse/handlers/sliding_sync/room_lists.py
index 652d05dbe9..50f0786374 100644
--- a/synapse/handlers/sliding_sync/room_lists.py
+++ b/synapse/handlers/sliding_sync/room_lists.py
@@ -246,6 +246,7 @@ class SlidingSyncRoomLists:
                         event_pos=change.event_pos,
                         room_version_id=change.room_version_id,
                         # We keep the current state of the room though
+                        has_known_state=existing_room.has_known_state,
                         room_type=existing_room.room_type,
                         is_encrypted=existing_room.is_encrypted,
                     )
@@ -270,6 +271,7 @@ class SlidingSyncRoomLists:
                         event_id=change.event_id,
                         event_pos=change.event_pos,
                         room_version_id=change.room_version_id,
+                        has_known_state=True,
                         room_type=room_type,
                         is_encrypted=is_encrypted,
                     )
@@ -305,6 +307,7 @@ class SlidingSyncRoomLists:
                     event_id=None,
                     event_pos=newly_left_room_map[room_id],
                     room_version_id=await self.store.get_room_version_id(room_id),
+                    has_known_state=True,
                     room_type=room_type,
                     is_encrypted=is_encrypted,
                 )
@@ -1630,12 +1633,14 @@ class SlidingSyncRoomLists:
                         and room_type not in filters.room_types
                     ):
                         filtered_room_id_set.remove(room_id)
+                        continue
 
                     if (
                         filters.not_room_types is not None
                         and room_type in filters.not_room_types
                     ):
                         filtered_room_id_set.remove(room_id)
+                        continue
 
         if filters.room_name_like is not None:
             with start_active_span("filters.room_name_like"):
@@ -1705,7 +1710,10 @@ class SlidingSyncRoomLists:
             filtered_room_id_set = {
                 room_id
                 for room_id in filtered_room_id_set
-                if sync_room_map[room_id].is_encrypted == filters.is_encrypted
+                # Remove rooms if we can't figure out what the encryption status is
+                if sync_room_map[room_id].has_known_state
+                # Or remove if it doesn't match the filter
+                and sync_room_map[room_id].is_encrypted == filters.is_encrypted
             }
 
         # Filter for rooms that the user has been invited to
@@ -1734,6 +1742,11 @@ class SlidingSyncRoomLists:
                 # Make a copy so we don't run into an error: `Set changed size during
                 # iteration`, when we filter out and remove items
                 for room_id in filtered_room_id_set.copy():
+                    # Remove rooms if we can't figure out what room type it is
+                    if not sync_room_map[room_id].has_known_state:
+                        filtered_room_id_set.remove(room_id)
+                        continue
+
                     room_type = sync_room_map[room_id].room_type
 
                     if (
@@ -1741,12 +1754,14 @@ class SlidingSyncRoomLists:
                         and room_type not in filters.room_types
                     ):
                         filtered_room_id_set.remove(room_id)
+                        continue
 
                     if (
                         filters.not_room_types is not None
                         and room_type in filters.not_room_types
                     ):
                         filtered_room_id_set.remove(room_id)
+                        continue
 
         if filters.room_name_like is not None:
             with start_active_span("filters.room_name_like"):
diff --git a/synapse/rest/client/sync.py b/synapse/rest/client/sync.py
index 9e2bf98189..364cf40153 100644
--- a/synapse/rest/client/sync.py
+++ b/synapse/rest/client/sync.py
@@ -1044,7 +1044,7 @@ class SlidingSyncRestServlet(RestServlet):
                 serialized_rooms[room_id]["heroes"] = serialized_heroes
 
             # We should only include the `initial` key if it's `True` to save bandwidth.
-            # The absense of this flag means `False`.
+            # The absence of this flag means `False`.
             if room_result.initial:
                 serialized_rooms[room_id]["initial"] = room_result.initial
 
diff --git a/synapse/storage/databases/main/roommember.py b/synapse/storage/databases/main/roommember.py
index 8bfa6254f3..e321a1add2 100644
--- a/synapse/storage/databases/main/roommember.py
+++ b/synapse/storage/databases/main/roommember.py
@@ -1420,6 +1420,7 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore):
                 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,
                     COALESCE(j.room_type, m.room_type),
                     COALESCE(j.is_encrypted, m.is_encrypted)
                 FROM sliding_sync_membership_snapshots AS m
@@ -1437,8 +1438,9 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore):
                     event_id=row[3],
                     room_version_id=row[4],
                     event_pos=PersistedEventPosition(row[5], row[6]),
-                    room_type=row[7],
-                    is_encrypted=bool(row[8]),
+                    has_known_state=bool(row[7]),
+                    room_type=row[8],
+                    is_encrypted=bool(row[9]),
                 )
                 for row in txn
             }
diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py
index af71c01c17..9dc6c395e8 100644
--- a/synapse/storage/roommember.py
+++ b/synapse/storage/roommember.py
@@ -48,6 +48,7 @@ class RoomsForUserSlidingSync:
     event_pos: PersistedEventPosition
     room_version_id: str
 
+    has_known_state: bool
     room_type: Optional[str]
     is_encrypted: bool
 
diff --git a/tests/handlers/test_sliding_sync.py b/tests/handlers/test_sliding_sync.py
index 7511a5b00a..e2c7a94ce2 100644
--- a/tests/handlers/test_sliding_sync.py
+++ b/tests/handlers/test_sliding_sync.py
@@ -18,7 +18,7 @@
 #
 #
 import logging
-from typing import AbstractSet, Dict, List, Optional, Tuple
+from typing import AbstractSet, Dict, Optional, Tuple
 from unittest.mock import patch
 
 from parameterized import parameterized
@@ -26,16 +26,11 @@ from parameterized import parameterized
 from twisted.test.proto_helpers import MemoryReactor
 
 from synapse.api.constants import (
-    AccountDataTypes,
-    EventContentFields,
     EventTypes,
     JoinRules,
     Membership,
-    RoomTypes,
 )
 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,
@@ -2984,1384 +2979,6 @@ class FilterRoomsRelevantForSyncTestCase(HomeserverTestCase):
         self.assertTrue(room_id1 in newly_left)
 
 
-class FilterRoomsTestCase(HomeserverTestCase):
-    """
-    Tests Sliding Sync handler `filter_rooms()` to make sure it includes/excludes rooms
-    correctly.
-    """
-
-    servlets = [
-        admin.register_servlets,
-        knock.register_servlets,
-        login.register_servlets,
-        room.register_servlets,
-    ]
-
-    def default_config(self) -> JsonDict:
-        config = super().default_config()
-        # Enable sliding sync
-        config["experimental_features"] = {"msc3575_enabled": True}
-        return config
-
-    def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
-        self.sliding_sync_handler = self.hs.get_sliding_sync_handler()
-        self.store = self.hs.get_datastores().main
-        self.event_sources = hs.get_event_sources()
-
-    def _get_sync_room_ids_for_user(
-        self,
-        user: UserID,
-        to_token: StreamToken,
-        from_token: Optional[StreamToken],
-    ) -> Tuple[Dict[str, RoomsForUserType], AbstractSet[str], AbstractSet[str]]:
-        """
-        Get the rooms the user should be syncing with
-        """
-        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,
-                to_token=to_token,
-            )
-        )
-        filtered_sync_room_map = self.get_success(
-            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, newly_joined, newly_left
-
-    def _create_dm_room(
-        self,
-        inviter_user_id: str,
-        inviter_tok: str,
-        invitee_user_id: str,
-        invitee_tok: str,
-    ) -> str:
-        """
-        Helper to create a DM room as the "inviter" and invite the "invitee" user to the room. The
-        "invitee" user also will join the room. The `m.direct` account data will be set
-        for both users.
-        """
-
-        # Create a room and send an invite the other user
-        room_id = self.helper.create_room_as(
-            inviter_user_id,
-            is_public=False,
-            tok=inviter_tok,
-        )
-        self.helper.invite(
-            room_id,
-            src=inviter_user_id,
-            targ=invitee_user_id,
-            tok=inviter_tok,
-            extra_data={"is_direct": True},
-        )
-        # Person that was invited joins the room
-        self.helper.join(room_id, invitee_user_id, tok=invitee_tok)
-
-        # Mimic the client setting the room as a direct message in the global account
-        # data
-        self.get_success(
-            self.store.add_account_data_for_user(
-                invitee_user_id,
-                AccountDataTypes.DIRECT,
-                {inviter_user_id: [room_id]},
-            )
-        )
-        self.get_success(
-            self.store.add_account_data_for_user(
-                inviter_user_id,
-                AccountDataTypes.DIRECT,
-                {invitee_user_id: [room_id]},
-            )
-        )
-
-        return room_id
-
-    _remote_invite_count: int = 0
-
-    def _create_remote_invite_room_for_user(
-        self,
-        invitee_user_id: str,
-        unsigned_invite_room_state: Optional[List[StrippedStateEvent]],
-    ) -> str:
-        """
-        Create a fake invite for a remote room and persist it.
-
-        We don't have any state for these kind of rooms and can only rely on the
-        stripped state included in the unsigned portion of the invite event to identify
-        the room.
-
-        Args:
-            invitee_user_id: The person being invited
-            unsigned_invite_room_state: List of stripped state events to assist the
-                receiver in identifying the room.
-
-        Returns:
-            The room ID of the remote invite room
-        """
-        invite_room_id = f"!test_room{self._remote_invite_count}:remote_server"
-
-        invite_event_dict = {
-            "room_id": invite_room_id,
-            "sender": "@inviter:remote_server",
-            "state_key": invitee_user_id,
-            "depth": 1,
-            "origin_server_ts": 1,
-            "type": EventTypes.Member,
-            "content": {"membership": Membership.INVITE},
-            "auth_events": [],
-            "prev_events": [],
-        }
-        if unsigned_invite_room_state is not None:
-            serialized_stripped_state_events = []
-            for stripped_event in unsigned_invite_room_state:
-                serialized_stripped_state_events.append(
-                    {
-                        "type": stripped_event.type,
-                        "state_key": stripped_event.state_key,
-                        "sender": stripped_event.sender,
-                        "content": stripped_event.content,
-                    }
-                )
-
-            invite_event_dict["unsigned"] = {
-                "invite_room_state": serialized_stripped_state_events
-            }
-
-        invite_event = make_event_from_dict(
-            invite_event_dict,
-            room_version=RoomVersions.V10,
-        )
-        invite_event.internal_metadata.outlier = True
-        invite_event.internal_metadata.out_of_band_membership = True
-
-        self.get_success(
-            self.store.maybe_store_room_on_outlier_membership(
-                room_id=invite_room_id, room_version=invite_event.room_version
-            )
-        )
-        context = EventContext.for_outlier(self.hs.get_storage_controllers())
-        persist_controller = self.hs.get_storage_controllers().persistence
-        assert persist_controller is not None
-        self.get_success(persist_controller.persist_event(invite_event, context))
-
-        self._remote_invite_count += 1
-
-        return invite_room_id
-
-    def test_filter_dm_rooms(self) -> None:
-        """
-        Test `filter.is_dm` for DM rooms
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-        user2_id = self.register_user("user2", "pass")
-        user2_tok = self.login(user2_id, "pass")
-
-        # Create a normal room
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create a DM room
-        dm_room_id = self._create_dm_room(
-            inviter_user_id=user1_id,
-            inviter_tok=user1_tok,
-            invitee_user_id=user2_id,
-            invitee_tok=user2_tok,
-        )
-
-        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, newly_joined, newly_left = self._get_sync_room_ids_for_user(
-            UserID.from_string(user1_id),
-            from_token=None,
-            to_token=after_rooms_token,
-        )
-
-        # Try with `is_dm=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_dm=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=dm_room_ids,
-            )
-        )
-
-        self.assertEqual(truthy_filtered_room_map.keys(), {dm_room_id})
-
-        # Try with `is_dm=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_dm=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=dm_room_ids,
-            )
-        )
-
-        self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
-
-    def test_filter_encrypted_rooms(self) -> None:
-        """
-        Test `filter.is_encrypted` for encrypted rooms
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create an unencrypted room
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create an encrypted room
-        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        self.helper.send_state(
-            encrypted_room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user1_tok,
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try with `is_encrypted=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(truthy_filtered_room_map.keys(), {encrypted_room_id})
-
-        # Try with `is_encrypted=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
-
-    def test_filter_encrypted_server_left_room(self) -> None:
-        """
-        Test that we can apply a `filter.is_encrypted` against a room that everyone has left.
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        before_rooms_token = self.event_sources.get_current_token()
-
-        # Create an unencrypted room
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        # Leave the room
-        self.helper.leave(room_id, user1_id, tok=user1_tok)
-
-        # Create an encrypted room
-        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        self.helper.send_state(
-            encrypted_room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user1_tok,
-        )
-        # Leave the room
-        self.helper.leave(encrypted_room_id, user1_id, tok=user1_tok)
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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
-            from_token=before_rooms_token,
-            to_token=after_rooms_token,
-        )
-
-        # Try with `is_encrypted=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(truthy_filtered_room_map.keys(), {encrypted_room_id})
-
-        # Try with `is_encrypted=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
-
-    def test_filter_encrypted_server_left_room2(self) -> None:
-        """
-        Test that we can apply a `filter.is_encrypted` against a room that everyone has
-        left.
-
-        There is still someone local who is invited to the rooms but that doesn't affect
-        whether the server is participating in the room (users need to be joined).
-        """
-        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_rooms_token = self.event_sources.get_current_token()
-
-        # Create an unencrypted room
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        # Invite user2
-        self.helper.invite(room_id, targ=user2_id, tok=user1_tok)
-        # User1 leaves the room
-        self.helper.leave(room_id, user1_id, tok=user1_tok)
-
-        # Create an encrypted room
-        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        self.helper.send_state(
-            encrypted_room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user1_tok,
-        )
-        # Invite user2
-        self.helper.invite(encrypted_room_id, targ=user2_id, tok=user1_tok)
-        # User1 leaves the room
-        self.helper.leave(encrypted_room_id, user1_id, tok=user1_tok)
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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
-            from_token=before_rooms_token,
-            to_token=after_rooms_token,
-        )
-
-        # Try with `is_encrypted=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(truthy_filtered_room_map.keys(), {encrypted_room_id})
-
-        # Try with `is_encrypted=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
-
-    def test_filter_encrypted_after_we_left(self) -> None:
-        """
-        Test that we can apply a `filter.is_encrypted` against a room that was encrypted
-        after we left the room (make sure we don't just use the current state)
-        """
-        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_rooms_token = self.event_sources.get_current_token()
-
-        # Create an unencrypted room
-        room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
-        # Leave the room
-        self.helper.join(room_id, user1_id, tok=user1_tok)
-        self.helper.leave(room_id, user1_id, tok=user1_tok)
-
-        # Create a room that will be encrypted
-        encrypted_after_we_left_room_id = self.helper.create_room_as(
-            user2_id, tok=user2_tok
-        )
-        # Leave the room
-        self.helper.join(encrypted_after_we_left_room_id, user1_id, tok=user1_tok)
-        self.helper.leave(encrypted_after_we_left_room_id, user1_id, tok=user1_tok)
-
-        # Encrypt the room after we've left
-        self.helper.send_state(
-            encrypted_after_we_left_room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user2_tok,
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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
-            from_token=before_rooms_token,
-            to_token=after_rooms_token,
-        )
-
-        # Try with `is_encrypted=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # Even though we left the room before it was encrypted, we still see it because
-        # someone else on our server is still participating in the room and we "leak"
-        # the current state to the left user. But we consider the room encryption status
-        # to not be a secret given it's often set at the start of the room and it's one
-        # of the stripped state events that is normally handed out.
-        self.assertEqual(
-            truthy_filtered_room_map.keys(), {encrypted_after_we_left_room_id}
-        )
-
-        # Try with `is_encrypted=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # Even though we left the room before it was encrypted... (see comment above)
-        self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
-
-    def test_filter_encrypted_with_remote_invite_room_no_stripped_state(self) -> None:
-        """
-        Test that we can apply a `filter.is_encrypted` filter against a remote invite
-        room without any `unsigned.invite_room_state` (stripped state).
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create a remote invite room without any `unsigned.invite_room_state`
-        _remote_invite_room_id = self._create_remote_invite_room_for_user(
-            user1_id, None
-        )
-
-        # Create an unencrypted room
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create an encrypted room
-        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        self.helper.send_state(
-            encrypted_room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user1_tok,
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try with `is_encrypted=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should not appear because we can't figure out whether
-        # it is encrypted or not (no stripped state, `unsigned.invite_room_state`).
-        self.assertEqual(truthy_filtered_room_map.keys(), {encrypted_room_id})
-
-        # Try with `is_encrypted=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should not appear because we can't figure out whether
-        # it is encrypted or not (no stripped state, `unsigned.invite_room_state`).
-        self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
-
-    def test_filter_encrypted_with_remote_invite_encrypted_room(self) -> None:
-        """
-        Test that we can apply a `filter.is_encrypted` filter against a remote invite
-        encrypted room with some `unsigned.invite_room_state` (stripped state).
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create a remote invite room with some `unsigned.invite_room_state`
-        # indicating that the room is encrypted.
-        remote_invite_room_id = self._create_remote_invite_room_for_user(
-            user1_id,
-            [
-                StrippedStateEvent(
-                    type=EventTypes.Create,
-                    state_key="",
-                    sender="@inviter:remote_server",
-                    content={
-                        EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
-                        EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
-                    },
-                ),
-                StrippedStateEvent(
-                    type=EventTypes.RoomEncryption,
-                    state_key="",
-                    sender="@inviter:remote_server",
-                    content={
-                        EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2",
-                    },
-                ),
-            ],
-        )
-
-        # Create an unencrypted room
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create an encrypted room
-        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        self.helper.send_state(
-            encrypted_room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user1_tok,
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try with `is_encrypted=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should appear here because it is encrypted
-        # according to the stripped state
-        self.assertEqual(
-            truthy_filtered_room_map.keys(), {encrypted_room_id, remote_invite_room_id}
-        )
-
-        # Try with `is_encrypted=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should not appear here because it is encrypted
-        # according to the stripped state
-        self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
-
-    def test_filter_encrypted_with_remote_invite_unencrypted_room(self) -> None:
-        """
-        Test that we can apply a `filter.is_encrypted` filter against a remote invite
-        unencrypted room with some `unsigned.invite_room_state` (stripped state).
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create a remote invite room with some `unsigned.invite_room_state`
-        # but don't set any room encryption event.
-        remote_invite_room_id = self._create_remote_invite_room_for_user(
-            user1_id,
-            [
-                StrippedStateEvent(
-                    type=EventTypes.Create,
-                    state_key="",
-                    sender="@inviter:remote_server",
-                    content={
-                        EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
-                        EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
-                    },
-                ),
-                # No room encryption event
-            ],
-        )
-
-        # Create an unencrypted room
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create an encrypted room
-        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        self.helper.send_state(
-            encrypted_room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user1_tok,
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try with `is_encrypted=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should not appear here because it is unencrypted
-        # according to the stripped state
-        self.assertEqual(truthy_filtered_room_map.keys(), {encrypted_room_id})
-
-        # Try with `is_encrypted=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_encrypted=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should appear because it is unencrypted according to
-        # the stripped state
-        self.assertEqual(
-            falsy_filtered_room_map.keys(), {room_id, remote_invite_room_id}
-        )
-
-    def test_filter_invite_rooms(self) -> None:
-        """
-        Test `filter.is_invite` for rooms that the user has been invited to
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-        user2_id = self.register_user("user2", "pass")
-        user2_tok = self.login(user2_id, "pass")
-
-        # Create a normal room
-        room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
-        self.helper.join(room_id, user1_id, tok=user1_tok)
-
-        # Create a room that user1 is invited to
-        invite_room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
-        self.helper.invite(invite_room_id, src=user2_id, targ=user1_id, tok=user2_tok)
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try with `is_invite=True`
-        truthy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_invite=True,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(truthy_filtered_room_map.keys(), {invite_room_id})
-
-        # Try with `is_invite=False`
-        falsy_filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    is_invite=False,
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
-
-    def test_filter_room_types(self) -> None:
-        """
-        Test `filter.room_types` for different room types
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create a normal room (no room type)
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create a space room
-        space_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
-            },
-        )
-
-        # Create an arbitrarily typed room
-        foo_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {
-                    EventContentFields.ROOM_TYPE: "org.matrix.foobarbaz"
-                }
-            },
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try finding only normal rooms
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {room_id})
-
-        # Try finding only spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {space_room_id})
-
-        # Try finding normal rooms and spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    room_types=[None, RoomTypes.SPACE]
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {room_id, space_room_id})
-
-        # Try finding an arbitrary room type
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    room_types=["org.matrix.foobarbaz"]
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {foo_room_id})
-
-    def test_filter_not_room_types(self) -> None:
-        """
-        Test `filter.not_room_types` for different room types
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create a normal room (no room type)
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create a space room
-        space_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
-            },
-        )
-
-        # Create an arbitrarily typed room
-        foo_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {
-                    EventContentFields.ROOM_TYPE: "org.matrix.foobarbaz"
-                }
-            },
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try finding *NOT* normal rooms
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(not_room_types=[None]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {space_room_id, foo_room_id})
-
-        # Try finding *NOT* spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    not_room_types=[RoomTypes.SPACE]
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {room_id, foo_room_id})
-
-        # Try finding *NOT* normal rooms or spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    not_room_types=[None, RoomTypes.SPACE]
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {foo_room_id})
-
-        # Test how it behaves when we have both `room_types` and `not_room_types`.
-        # `not_room_types` should win.
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    room_types=[None], not_room_types=[None]
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # Nothing matches because nothing is both a normal room and not a normal room
-        self.assertEqual(filtered_room_map.keys(), set())
-
-        # Test how it behaves when we have both `room_types` and `not_room_types`.
-        # `not_room_types` should win.
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(
-                    room_types=[None, RoomTypes.SPACE], not_room_types=[None]
-                ),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {space_room_id})
-
-    def test_filter_room_types_server_left_room(self) -> None:
-        """
-        Test that we can apply a `filter.room_types` against a room that everyone has left.
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        before_rooms_token = self.event_sources.get_current_token()
-
-        # Create a normal room (no room type)
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        # Leave the room
-        self.helper.leave(room_id, user1_id, tok=user1_tok)
-
-        # Create a space room
-        space_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
-            },
-        )
-        # Leave the room
-        self.helper.leave(space_room_id, user1_id, tok=user1_tok)
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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
-            from_token=before_rooms_token,
-            to_token=after_rooms_token,
-        )
-
-        # Try finding only normal rooms
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {room_id})
-
-        # Try finding only spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {space_room_id})
-
-    def test_filter_room_types_server_left_room2(self) -> None:
-        """
-        Test that we can apply a `filter.room_types` against a room that everyone has left.
-
-        There is still someone local who is invited to the rooms but that doesn't affect
-        whether the server is participating in the room (users need to be joined).
-        """
-        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_rooms_token = self.event_sources.get_current_token()
-
-        # Create a normal room (no room type)
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-        # Invite user2
-        self.helper.invite(room_id, targ=user2_id, tok=user1_tok)
-        # User1 leaves the room
-        self.helper.leave(room_id, user1_id, tok=user1_tok)
-
-        # Create a space room
-        space_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
-            },
-        )
-        # Invite user2
-        self.helper.invite(space_room_id, targ=user2_id, tok=user1_tok)
-        # User1 leaves the room
-        self.helper.leave(space_room_id, user1_id, tok=user1_tok)
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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
-            from_token=before_rooms_token,
-            to_token=after_rooms_token,
-        )
-
-        # Try finding only normal rooms
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {room_id})
-
-        # Try finding only spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        self.assertEqual(filtered_room_map.keys(), {space_room_id})
-
-    def test_filter_room_types_with_remote_invite_room_no_stripped_state(self) -> None:
-        """
-        Test that we can apply a `filter.room_types` filter against a remote invite
-        room without any `unsigned.invite_room_state` (stripped state).
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create a remote invite room without any `unsigned.invite_room_state`
-        _remote_invite_room_id = self._create_remote_invite_room_for_user(
-            user1_id, None
-        )
-
-        # Create a normal room (no room type)
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create a space room
-        space_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
-            },
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try finding only normal rooms
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should not appear because we can't figure out what
-        # room type it is (no stripped state, `unsigned.invite_room_state`)
-        self.assertEqual(filtered_room_map.keys(), {room_id})
-
-        # Try finding only spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should not appear because we can't figure out what
-        # room type it is (no stripped state, `unsigned.invite_room_state`)
-        self.assertEqual(filtered_room_map.keys(), {space_room_id})
-
-    def test_filter_room_types_with_remote_invite_space(self) -> None:
-        """
-        Test that we can apply a `filter.room_types` filter against a remote invite
-        to a space room with some `unsigned.invite_room_state` (stripped state).
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create a remote invite room with some `unsigned.invite_room_state` indicating
-        # that it is a space room
-        remote_invite_room_id = self._create_remote_invite_room_for_user(
-            user1_id,
-            [
-                StrippedStateEvent(
-                    type=EventTypes.Create,
-                    state_key="",
-                    sender="@inviter:remote_server",
-                    content={
-                        EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
-                        EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
-                        # Specify that it is a space room
-                        EventContentFields.ROOM_TYPE: RoomTypes.SPACE,
-                    },
-                ),
-            ],
-        )
-
-        # Create a normal room (no room type)
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create a space room
-        space_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
-            },
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try finding only normal rooms
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should not appear here because it is a space room
-        # according to the stripped state
-        self.assertEqual(filtered_room_map.keys(), {room_id})
-
-        # Try finding only spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should appear here because it is a space room
-        # according to the stripped state
-        self.assertEqual(
-            filtered_room_map.keys(), {space_room_id, remote_invite_room_id}
-        )
-
-    def test_filter_room_types_with_remote_invite_normal_room(self) -> None:
-        """
-        Test that we can apply a `filter.room_types` filter against a remote invite
-        to a normal room with some `unsigned.invite_room_state` (stripped state).
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        # Create a remote invite room with some `unsigned.invite_room_state`
-        # but the create event does not specify a room type (normal room)
-        remote_invite_room_id = self._create_remote_invite_room_for_user(
-            user1_id,
-            [
-                StrippedStateEvent(
-                    type=EventTypes.Create,
-                    state_key="",
-                    sender="@inviter:remote_server",
-                    content={
-                        EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
-                        EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
-                        # No room type means this is a normal room
-                    },
-                ),
-            ],
-        )
-
-        # Create a normal room (no room type)
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        # Create a space room
-        space_room_id = self.helper.create_room_as(
-            user1_id,
-            tok=user1_tok,
-            extra_content={
-                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
-            },
-        )
-
-        after_rooms_token = self.event_sources.get_current_token()
-
-        # Get the rooms the user should be syncing with
-        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,
-        )
-
-        # Try finding only normal rooms
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[None]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should appear here because it is a normal room
-        # according to the stripped state (no room type)
-        self.assertEqual(filtered_room_map.keys(), {room_id, remote_invite_room_id})
-
-        # Try finding only spaces
-        filtered_room_map = self.get_success(
-            self.sliding_sync_handler.room_lists.filter_rooms(
-                UserID.from_string(user1_id),
-                sync_room_map,
-                SlidingSyncConfig.SlidingSyncList.Filters(room_types=[RoomTypes.SPACE]),
-                after_rooms_token,
-                dm_room_ids=set(),
-            )
-        )
-
-        # `remote_invite_room_id` should not appear here because it is a normal room
-        # according to the stripped state (no room type)
-        self.assertEqual(filtered_room_map.keys(), {space_room_id})
-
-
 class SortRoomsTestCase(HomeserverTestCase):
     """
     Tests Sliding Sync handler `sort_rooms()` to make sure it sorts/orders rooms
diff --git a/tests/rest/client/sliding_sync/test_lists_filters.py b/tests/rest/client/sliding_sync/test_lists_filters.py
new file mode 100644
index 0000000000..8df35f5f65
--- /dev/null
+++ b/tests/rest/client/sliding_sync/test_lists_filters.py
@@ -0,0 +1,1681 @@
+#
+# This file is licensed under the Affero General Public License (AGPL) version 3.
+#
+# Copyright (C) 2024 New Vector, Ltd
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# See the GNU Affero General Public License for more details:
+# <https://www.gnu.org/licenses/agpl-3.0.html>.
+#
+import logging
+
+from parameterized import parameterized_class
+
+from twisted.test.proto_helpers import MemoryReactor
+
+import synapse.rest.admin
+from synapse.api.constants import (
+    EventContentFields,
+    EventTypes,
+    RoomTypes,
+)
+from synapse.api.room_versions import RoomVersions
+from synapse.events import StrippedStateEvent
+from synapse.rest.client import login, room, sync
+from synapse.server import HomeServer
+from synapse.types import JsonDict
+from synapse.util import Clock
+
+from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase
+
+logger = logging.getLogger(__name__)
+
+
+# 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 SlidingSyncFiltersTestCase(SlidingSyncBase):
+    """
+    Test `filters` in the Sliding Sync API to make sure it includes/excludes rooms
+    correctly.
+    """
+
+    servlets = [
+        synapse.rest.admin.register_servlets,
+        login.register_servlets,
+        room.register_servlets,
+        sync.register_servlets,
+    ]
+
+    def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
+        self.store = hs.get_datastores().main
+        self.event_sources = hs.get_event_sources()
+        self.storage_controllers = hs.get_storage_controllers()
+        self.account_data_handler = hs.get_account_data_handler()
+
+        super().prepare(reactor, clock, hs)
+
+    def test_multiple_filters_and_multiple_lists(self) -> None:
+        """
+        Test that filters apply to `lists` in various scenarios.
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+        user2_id = self.register_user("user2", "pass")
+        user2_tok = self.login(user2_id, "pass")
+
+        # Create a DM room
+        joined_dm_room_id = self._create_dm_room(
+            inviter_user_id=user1_id,
+            inviter_tok=user1_tok,
+            invitee_user_id=user2_id,
+            invitee_tok=user2_tok,
+            should_join_room=True,
+        )
+        invited_dm_room_id = self._create_dm_room(
+            inviter_user_id=user1_id,
+            inviter_tok=user1_tok,
+            invitee_user_id=user2_id,
+            invitee_tok=user2_tok,
+            should_join_room=False,
+        )
+
+        # Create a normal room
+        room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
+        self.helper.join(room_id, user1_id, tok=user1_tok)
+
+        # Create a room that user1 is invited to
+        invite_room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
+        self.helper.invite(invite_room_id, src=user2_id, targ=user1_id, tok=user2_tok)
+
+        # Make the Sliding Sync request
+        sync_body = {
+            "lists": {
+                # Absence of filters does not imply "False" values
+                "all": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 1,
+                    "filters": {},
+                },
+                # Test single truthy filter
+                "dms": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 1,
+                    "filters": {"is_dm": True},
+                },
+                # Test single falsy filter
+                "non-dms": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 1,
+                    "filters": {"is_dm": False},
+                },
+                # Test how multiple filters should stack (AND'd together)
+                "room-invites": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 1,
+                    "filters": {"is_dm": False, "is_invite": True},
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+
+        # Make sure it has the lists we requested
+        self.assertIncludes(
+            response_body["lists"].keys(),
+            {"all", "dms", "non-dms", "room-invites"},
+            exact=True,
+        )
+
+        # Make sure the lists have the correct rooms
+        self.assertIncludes(
+            set(response_body["lists"]["all"]["ops"][0]["room_ids"]),
+            {
+                invite_room_id,
+                room_id,
+                invited_dm_room_id,
+                joined_dm_room_id,
+            },
+            exact=True,
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["dms"]["ops"][0]["room_ids"]),
+            {invited_dm_room_id, joined_dm_room_id},
+            exact=True,
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["non-dms"]["ops"][0]["room_ids"]),
+            {invite_room_id, room_id},
+            exact=True,
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["room-invites"]["ops"][0]["room_ids"]),
+            {invite_room_id},
+            exact=True,
+        )
+
+    def test_filters_regardless_of_membership_server_left_room(self) -> None:
+        """
+        Test that filters apply to rooms regardless of membership. We're also
+        compounding the problem by having all of the local users leave the room causing
+        our server to leave the room.
+
+        We want to make sure that if someone is filtering rooms, and leaves, you still
+        get that final update down sync that you left.
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+        user2_id = self.register_user("user2", "pass")
+        user2_tok = self.login(user2_id, "pass")
+
+        # Create a normal room
+        room_id = self.helper.create_room_as(user1_id, tok=user2_tok)
+        self.helper.join(room_id, user1_id, tok=user1_tok)
+
+        # Create an encrypted space room
+        space_room_id = self.helper.create_room_as(
+            user2_id,
+            tok=user2_tok,
+            extra_content={
+                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
+            },
+        )
+        self.helper.send_state(
+            space_room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user2_tok,
+        )
+        self.helper.join(space_room_id, user1_id, tok=user1_tok)
+
+        # Make an initial Sliding Sync request
+        sync_body = {
+            "lists": {
+                "all-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {},
+                },
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 1,
+                    "filters": {
+                        "is_encrypted": True,
+                        "room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
+
+        # Make sure the response has the lists we requested
+        self.assertListEqual(
+            list(response_body["lists"].keys()),
+            ["all-list", "foo-list"],
+            response_body["lists"].keys(),
+        )
+
+        # Make sure the lists have the correct rooms
+        self.assertListEqual(
+            list(response_body["lists"]["all-list"]["ops"]),
+            [
+                {
+                    "op": "SYNC",
+                    "range": [0, 99],
+                    "room_ids": [space_room_id, room_id],
+                }
+            ],
+        )
+        self.assertListEqual(
+            list(response_body["lists"]["foo-list"]["ops"]),
+            [
+                {
+                    "op": "SYNC",
+                    "range": [0, 99],
+                    "room_ids": [space_room_id],
+                }
+            ],
+        )
+
+        # Everyone leaves the encrypted space room
+        self.helper.leave(space_room_id, user1_id, tok=user1_tok)
+        self.helper.leave(space_room_id, user2_id, tok=user2_tok)
+
+        # Make an incremental Sliding Sync request
+        sync_body = {
+            "lists": {
+                "all-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {},
+                },
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 1,
+                    "filters": {
+                        "is_encrypted": True,
+                        "room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok)
+
+        # Make sure the lists have the correct rooms even though we `newly_left`
+        self.assertListEqual(
+            list(response_body["lists"]["all-list"]["ops"]),
+            [
+                {
+                    "op": "SYNC",
+                    "range": [0, 99],
+                    "room_ids": [space_room_id, room_id],
+                }
+            ],
+        )
+        self.assertListEqual(
+            list(response_body["lists"]["foo-list"]["ops"]),
+            [
+                {
+                    "op": "SYNC",
+                    "range": [0, 99],
+                    "room_ids": [space_room_id],
+                }
+            ],
+        )
+
+    def test_filters_is_dm(self) -> None:
+        """
+        Test `filter.is_dm` for DM rooms
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+        user2_id = self.register_user("user2", "pass")
+        user2_tok = self.login(user2_id, "pass")
+
+        # Create a normal room
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create a DM room
+        dm_room_id = self._create_dm_room(
+            inviter_user_id=user1_id,
+            inviter_tok=user1_tok,
+            invitee_user_id=user2_id,
+            invitee_tok=user2_tok,
+        )
+
+        # Try with `is_dm=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_dm": True,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {dm_room_id},
+            exact=True,
+        )
+
+        # Try with `is_dm=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_dm": False,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+    def test_filters_is_encrypted(self) -> None:
+        """
+        Test `filters.is_encrypted` for encrypted rooms
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create an unencrypted room
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create an encrypted room
+        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        self.helper.send_state(
+            encrypted_room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user1_tok,
+        )
+
+        # Try with `is_encrypted=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": True,
+                    },
+                },
+            }
+        }
+        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
+        # No rooms are encrypted yet
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {encrypted_room_id},
+            exact=True,
+        )
+
+        # Try with `is_encrypted=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": False,
+                    },
+                },
+            }
+        }
+        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
+        # No rooms are encrypted yet
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+    def test_filters_is_encrypted_server_left_room(self) -> None:
+        """
+        Test that we can apply a `filters.is_encrypted` against a room that everyone has left.
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Get a token before we create any rooms
+        sync_body: JsonDict = {
+            "lists": {},
+        }
+        response_body, before_rooms_token = self.do_sync(sync_body, tok=user1_tok)
+
+        # Create an unencrypted room
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        # Leave the room
+        self.helper.leave(room_id, user1_id, tok=user1_tok)
+
+        # Create an encrypted room
+        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        self.helper.send_state(
+            encrypted_room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user1_tok,
+        )
+        # Leave the room
+        self.helper.leave(encrypted_room_id, user1_id, tok=user1_tok)
+
+        # Try with `is_encrypted=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": True,
+                    },
+                },
+            }
+        }
+        # Use an incremental sync so that the room is considered `newly_left` and shows
+        # up down sync
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {encrypted_room_id},
+            exact=True,
+        )
+
+        # Try with `is_encrypted=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": False,
+                    },
+                },
+            }
+        }
+        # Use an incremental sync so that the room is considered `newly_left` and shows
+        # up down sync
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+    def test_filters_is_encrypted_server_left_room2(self) -> None:
+        """
+        Test that we can apply a `filters.is_encrypted` against a room that everyone has
+        left.
+
+        There is still someone local who is invited to the rooms but that doesn't affect
+        whether the server is participating in the room (users need to be joined).
+        """
+        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")
+
+        # Get a token before we create any rooms
+        sync_body: JsonDict = {
+            "lists": {},
+        }
+        response_body, before_rooms_token = self.do_sync(sync_body, tok=user1_tok)
+
+        # Create an unencrypted room
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        # Invite user2
+        self.helper.invite(room_id, targ=user2_id, tok=user1_tok)
+        # User1 leaves the room
+        self.helper.leave(room_id, user1_id, tok=user1_tok)
+
+        # Create an encrypted room
+        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        self.helper.send_state(
+            encrypted_room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user1_tok,
+        )
+        # Invite user2
+        self.helper.invite(encrypted_room_id, targ=user2_id, tok=user1_tok)
+        # User1 leaves the room
+        self.helper.leave(encrypted_room_id, user1_id, tok=user1_tok)
+
+        # Try with `is_encrypted=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": True,
+                    },
+                },
+            }
+        }
+        # Use an incremental sync so that the room is considered `newly_left` and shows
+        # up down sync
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {encrypted_room_id},
+            exact=True,
+        )
+
+        # Try with `is_encrypted=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": False,
+                    },
+                },
+            }
+        }
+        # Use an incremental sync so that the room is considered `newly_left` and shows
+        # up down sync
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+    def test_filters_is_encrypted_after_we_left(self) -> None:
+        """
+        Test that we can apply a `filters.is_encrypted` against a room that was encrypted
+        after we left the room (make sure we don't just use the current state)
+        """
+        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")
+
+        # Get a token before we create any rooms
+        sync_body: JsonDict = {
+            "lists": {},
+        }
+        response_body, before_rooms_token = self.do_sync(sync_body, tok=user1_tok)
+
+        # Create an unencrypted room
+        room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
+        # Leave the room
+        self.helper.join(room_id, user1_id, tok=user1_tok)
+        self.helper.leave(room_id, user1_id, tok=user1_tok)
+
+        # Create a room that will be encrypted
+        encrypted_after_we_left_room_id = self.helper.create_room_as(
+            user2_id, tok=user2_tok
+        )
+        # Leave the room
+        self.helper.join(encrypted_after_we_left_room_id, user1_id, tok=user1_tok)
+        self.helper.leave(encrypted_after_we_left_room_id, user1_id, tok=user1_tok)
+
+        # Encrypt the room after we've left
+        self.helper.send_state(
+            encrypted_after_we_left_room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user2_tok,
+        )
+
+        # Try with `is_encrypted=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": True,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        if self.use_new_tables:
+            self.assertIncludes(
+                set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+                set(),
+                exact=True,
+            )
+        else:
+            # Even though we left the room before it was encrypted, we still see it because
+            # someone else on our server is still participating in the room and we "leak"
+            # the current state to the left user. But we consider the room encryption status
+            # to not be a secret given it's often set at the start of the room and it's one
+            # of the stripped state events that is normally handed out.
+            self.assertIncludes(
+                set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+                {encrypted_after_we_left_room_id},
+                exact=True,
+            )
+
+        # Try with `is_encrypted=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": False,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        if self.use_new_tables:
+            self.assertIncludes(
+                set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+                {room_id, encrypted_after_we_left_room_id},
+                exact=True,
+            )
+        else:
+            # Even though we left the room before it was encrypted... (see comment above)
+            self.assertIncludes(
+                set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+                {room_id},
+                exact=True,
+            )
+
+    def test_filters_is_encrypted_with_remote_invite_room_no_stripped_state(
+        self,
+    ) -> None:
+        """
+        Test that we can apply a `filters.is_encrypted` filter against a remote invite
+        room without any `unsigned.invite_room_state` (stripped state).
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create a remote invite room without any `unsigned.invite_room_state`
+        _remote_invite_room_id = self._create_remote_invite_room_for_user(
+            user1_id, None
+        )
+
+        # Create an unencrypted room
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create an encrypted room
+        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        self.helper.send_state(
+            encrypted_room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user1_tok,
+        )
+
+        # Try with `is_encrypted=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": True,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should not appear because we can't figure out whether
+        # it is encrypted or not (no stripped state, `unsigned.invite_room_state`).
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {encrypted_room_id},
+            exact=True,
+        )
+
+        # Try with `is_encrypted=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": False,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should not appear because we can't figure out whether
+        # it is encrypted or not (no stripped state, `unsigned.invite_room_state`).
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+    def test_filters_is_encrypted_with_remote_invite_encrypted_room(self) -> None:
+        """
+        Test that we can apply a `filters.is_encrypted` filter against a remote invite
+        encrypted room with some `unsigned.invite_room_state` (stripped state).
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create a remote invite room with some `unsigned.invite_room_state`
+        # indicating that the room is encrypted.
+        remote_invite_room_id = self._create_remote_invite_room_for_user(
+            user1_id,
+            [
+                StrippedStateEvent(
+                    type=EventTypes.Create,
+                    state_key="",
+                    sender="@inviter:remote_server",
+                    content={
+                        EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
+                        EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
+                    },
+                ),
+                StrippedStateEvent(
+                    type=EventTypes.RoomEncryption,
+                    state_key="",
+                    sender="@inviter:remote_server",
+                    content={
+                        EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2",
+                    },
+                ),
+            ],
+        )
+
+        # Create an unencrypted room
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create an encrypted room
+        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        self.helper.send_state(
+            encrypted_room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user1_tok,
+        )
+
+        # Try with `is_encrypted=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": True,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should appear here because it is encrypted
+        # according to the stripped state
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {encrypted_room_id, remote_invite_room_id},
+            exact=True,
+        )
+
+        # Try with `is_encrypted=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": False,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should not appear here because it is encrypted
+        # according to the stripped state
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+    def test_filters_is_encrypted_with_remote_invite_unencrypted_room(self) -> None:
+        """
+        Test that we can apply a `filters.is_encrypted` filter against a remote invite
+        unencrypted room with some `unsigned.invite_room_state` (stripped state).
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create a remote invite room with some `unsigned.invite_room_state`
+        # but don't set any room encryption event.
+        remote_invite_room_id = self._create_remote_invite_room_for_user(
+            user1_id,
+            [
+                StrippedStateEvent(
+                    type=EventTypes.Create,
+                    state_key="",
+                    sender="@inviter:remote_server",
+                    content={
+                        EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
+                        EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
+                    },
+                ),
+                # No room encryption event
+            ],
+        )
+
+        # Create an unencrypted room
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create an encrypted room
+        encrypted_room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        self.helper.send_state(
+            encrypted_room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user1_tok,
+        )
+
+        # Try with `is_encrypted=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": True,
+                    },
+                },
+            }
+        }
+        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should not appear here because it is unencrypted
+        # according to the stripped state
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {encrypted_room_id},
+            exact=True,
+        )
+
+        # Try with `is_encrypted=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": False,
+                    },
+                },
+            }
+        }
+        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should appear because it is unencrypted according to
+        # the stripped state
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id, remote_invite_room_id},
+            exact=True,
+        )
+
+    def test_filters_is_encrypted_updated(self) -> None:
+        """
+        Make sure we get rooms if the encrypted room status is updated for a joined room
+        (`filters.is_encrypted`)
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_encrypted": True,
+                    },
+                },
+            }
+        }
+        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
+
+        # No rooms are encrypted yet
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            set(),
+            exact=True,
+        )
+
+        # Update the encryption status
+        self.helper.send_state(
+            room_id,
+            EventTypes.RoomEncryption,
+            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
+            tok=user1_tok,
+        )
+
+        # We should see the room now because it's encrypted
+        response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+    def test_filters_is_invite_rooms(self) -> None:
+        """
+        Test `filters.is_invite` for rooms that the user has been invited to
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+        user2_id = self.register_user("user2", "pass")
+        user2_tok = self.login(user2_id, "pass")
+
+        # Create a normal room
+        room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
+        self.helper.join(room_id, user1_id, tok=user1_tok)
+
+        # Create a room that user1 is invited to
+        invite_room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
+        self.helper.invite(invite_room_id, src=user2_id, targ=user1_id, tok=user2_tok)
+
+        # Try with `is_invite=True`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_invite": True,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {invite_room_id},
+            exact=True,
+        )
+
+        # Try with `is_invite=False`
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "is_invite": False,
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+    def test_filters_room_types(self) -> None:
+        """
+        Test `filters.room_types` for different room types
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create a normal room (no room type)
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create a space room
+        space_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
+            },
+        )
+
+        # Create an arbitrarily typed room
+        foo_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {
+                    EventContentFields.ROOM_TYPE: "org.matrix.foobarbaz"
+                }
+            },
+        )
+
+        # Try finding only normal rooms
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+        # Try finding only spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {space_room_id},
+            exact=True,
+        )
+
+        # Try finding normal rooms and spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None, RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id, space_room_id},
+            exact=True,
+        )
+
+        # Try finding an arbitrary room type
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": ["org.matrix.foobarbaz"],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {foo_room_id},
+            exact=True,
+        )
+
+    def test_filters_not_room_types(self) -> None:
+        """
+        Test `filters.not_room_types` for different room types
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create a normal room (no room type)
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create a space room
+        space_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
+            },
+        )
+
+        # Create an arbitrarily typed room
+        foo_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {
+                    EventContentFields.ROOM_TYPE: "org.matrix.foobarbaz"
+                }
+            },
+        )
+
+        # Try finding *NOT* normal rooms
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "not_room_types": [None],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {space_room_id, foo_room_id},
+            exact=True,
+        )
+
+        # Try finding *NOT* spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "not_room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id, foo_room_id},
+            exact=True,
+        )
+
+        # Try finding *NOT* normal rooms or spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "not_room_types": [None, RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {foo_room_id},
+            exact=True,
+        )
+
+        # Test how it behaves when we have both `room_types` and `not_room_types`.
+        # `not_room_types` should win.
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None],
+                        "not_room_types": [None],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # Nothing matches because nothing is both a normal room and not a normal room
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            set(),
+            exact=True,
+        )
+
+        # Test how it behaves when we have both `room_types` and `not_room_types`.
+        # `not_room_types` should win.
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None, RoomTypes.SPACE],
+                        "not_room_types": [None],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {space_room_id},
+            exact=True,
+        )
+
+    def test_filters_room_types_server_left_room(self) -> None:
+        """
+        Test that we can apply a `filters.room_types` against a room that everyone has left.
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Get a token before we create any rooms
+        sync_body: JsonDict = {
+            "lists": {},
+        }
+        response_body, before_rooms_token = self.do_sync(sync_body, tok=user1_tok)
+
+        # Create a normal room (no room type)
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        # Leave the room
+        self.helper.leave(room_id, user1_id, tok=user1_tok)
+
+        # Create a space room
+        space_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
+            },
+        )
+        # Leave the room
+        self.helper.leave(space_room_id, user1_id, tok=user1_tok)
+
+        # Try finding only normal rooms
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None],
+                    },
+                },
+            }
+        }
+        # Use an incremental sync so that the room is considered `newly_left` and shows
+        # up down sync
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+        # Try finding only spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        # Use an incremental sync so that the room is considered `newly_left` and shows
+        # up down sync
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {space_room_id},
+            exact=True,
+        )
+
+    def test_filter_room_types_server_left_room2(self) -> None:
+        """
+        Test that we can apply a `filter.room_types` against a room that everyone has left.
+
+        There is still someone local who is invited to the rooms but that doesn't affect
+        whether the server is participating in the room (users need to be joined).
+        """
+        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")
+
+        # Get a token before we create any rooms
+        sync_body: JsonDict = {
+            "lists": {},
+        }
+        response_body, before_rooms_token = self.do_sync(sync_body, tok=user1_tok)
+
+        # Create a normal room (no room type)
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+        # Invite user2
+        self.helper.invite(room_id, targ=user2_id, tok=user1_tok)
+        # User1 leaves the room
+        self.helper.leave(room_id, user1_id, tok=user1_tok)
+
+        # Create a space room
+        space_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
+            },
+        )
+        # Invite user2
+        self.helper.invite(space_room_id, targ=user2_id, tok=user1_tok)
+        # User1 leaves the room
+        self.helper.leave(space_room_id, user1_id, tok=user1_tok)
+
+        # Try finding only normal rooms
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None],
+                    },
+                },
+            }
+        }
+        # Use an incremental sync so that the room is considered `newly_left` and shows
+        # up down sync
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+        # Try finding only spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        # Use an incremental sync so that the room is considered `newly_left` and shows
+        # up down sync
+        response_body, _ = self.do_sync(
+            sync_body, since=before_rooms_token, tok=user1_tok
+        )
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {space_room_id},
+            exact=True,
+        )
+
+    def test_filters_room_types_with_remote_invite_room_no_stripped_state(self) -> None:
+        """
+        Test that we can apply a `filters.room_types` filter against a remote invite
+        room without any `unsigned.invite_room_state` (stripped state).
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create a remote invite room without any `unsigned.invite_room_state`
+        _remote_invite_room_id = self._create_remote_invite_room_for_user(
+            user1_id, None
+        )
+
+        # Create a normal room (no room type)
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create a space room
+        space_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
+            },
+        )
+
+        # Try finding only normal rooms
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None],
+                    },
+                },
+            }
+        }
+        # `remote_invite_room_id` should not appear because we can't figure out what
+        # room type it is (no stripped state, `unsigned.invite_room_state`)
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+        # Try finding only spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        # `remote_invite_room_id` should not appear because we can't figure out what
+        # room type it is (no stripped state, `unsigned.invite_room_state`)
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {space_room_id},
+            exact=True,
+        )
+
+    def test_filters_room_types_with_remote_invite_space(self) -> None:
+        """
+        Test that we can apply a `filters.room_types` filter against a remote invite
+        to a space room with some `unsigned.invite_room_state` (stripped state).
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create a remote invite room with some `unsigned.invite_room_state` indicating
+        # that it is a space room
+        remote_invite_room_id = self._create_remote_invite_room_for_user(
+            user1_id,
+            [
+                StrippedStateEvent(
+                    type=EventTypes.Create,
+                    state_key="",
+                    sender="@inviter:remote_server",
+                    content={
+                        EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
+                        EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
+                        # Specify that it is a space room
+                        EventContentFields.ROOM_TYPE: RoomTypes.SPACE,
+                    },
+                ),
+            ],
+        )
+
+        # Create a normal room (no room type)
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create a space room
+        space_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
+            },
+        )
+
+        # Try finding only normal rooms
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should not appear here because it is a space room
+        # according to the stripped state
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id},
+            exact=True,
+        )
+
+        # Try finding only spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should appear here because it is a space room
+        # according to the stripped state
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {space_room_id, remote_invite_room_id},
+            exact=True,
+        )
+
+    def test_filters_room_types_with_remote_invite_normal_room(self) -> None:
+        """
+        Test that we can apply a `filters.room_types` filter against a remote invite
+        to a normal room with some `unsigned.invite_room_state` (stripped state).
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        # Create a remote invite room with some `unsigned.invite_room_state`
+        # but the create event does not specify a room type (normal room)
+        remote_invite_room_id = self._create_remote_invite_room_for_user(
+            user1_id,
+            [
+                StrippedStateEvent(
+                    type=EventTypes.Create,
+                    state_key="",
+                    sender="@inviter:remote_server",
+                    content={
+                        EventContentFields.ROOM_CREATOR: "@inviter:remote_server",
+                        EventContentFields.ROOM_VERSION: RoomVersions.V10.identifier,
+                        # No room type means this is a normal room
+                    },
+                ),
+            ],
+        )
+
+        # Create a normal room (no room type)
+        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        # Create a space room
+        space_room_id = self.helper.create_room_as(
+            user1_id,
+            tok=user1_tok,
+            extra_content={
+                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
+            },
+        )
+
+        # Try finding only normal rooms
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [None],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should appear here because it is a normal room
+        # according to the stripped state (no room type)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {room_id, remote_invite_room_id},
+            exact=True,
+        )
+
+        # Try finding only spaces
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                    "filters": {
+                        "room_types": [RoomTypes.SPACE],
+                    },
+                },
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+        # `remote_invite_room_id` should not appear here because it is a normal room
+        # according to the stripped state (no room type)
+        self.assertIncludes(
+            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
+            {space_room_id},
+            exact=True,
+        )
diff --git a/tests/rest/client/sliding_sync/test_rooms_meta.py b/tests/rest/client/sliding_sync/test_rooms_meta.py
index 6dbce7126f..40743d17eb 100644
--- a/tests/rest/client/sliding_sync/test_rooms_meta.py
+++ b/tests/rest/client/sliding_sync/test_rooms_meta.py
@@ -1139,3 +1139,61 @@ class SlidingSyncRoomsMetaTestCase(SlidingSyncBase):
         self.assertEqual(
             response_body["rooms"][room_id]["bump_stamp"], invite_pos.stream
         )
+
+    def test_rooms_meta_is_dm(self) -> None:
+        """
+        Test `rooms` `is_dm` is correctly set for DM rooms.
+        """
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+        user2_id = self.register_user("user2", "pass")
+        user2_tok = self.login(user2_id, "pass")
+
+        # Create a DM room
+        joined_dm_room_id = self._create_dm_room(
+            inviter_user_id=user1_id,
+            inviter_tok=user1_tok,
+            invitee_user_id=user2_id,
+            invitee_tok=user2_tok,
+            should_join_room=True,
+        )
+        invited_dm_room_id = self._create_dm_room(
+            inviter_user_id=user1_id,
+            inviter_tok=user1_tok,
+            invitee_user_id=user2_id,
+            invitee_tok=user2_tok,
+            should_join_room=False,
+        )
+
+        # Create a normal room
+        room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
+        self.helper.join(room_id, user1_id, tok=user1_tok)
+
+        # Create a room that user1 is invited to
+        invite_room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
+        self.helper.invite(invite_room_id, src=user2_id, targ=user1_id, tok=user2_tok)
+
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 99]],
+                    "required_state": [],
+                    "timeline_limit": 0,
+                }
+            }
+        }
+        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
+
+        # Ensure DM's are correctly marked
+        self.assertDictEqual(
+            {
+                room_id: room.get("is_dm")
+                for room_id, room in response_body["rooms"].items()
+            },
+            {
+                invite_room_id: None,
+                room_id: None,
+                invited_dm_room_id: True,
+                joined_dm_room_id: True,
+            },
+        )
diff --git a/tests/rest/client/sliding_sync/test_sliding_sync.py b/tests/rest/client/sliding_sync/test_sliding_sync.py
index 9e23dbe522..fe35cbb532 100644
--- a/tests/rest/client/sliding_sync/test_sliding_sync.py
+++ b/tests/rest/client/sliding_sync/test_sliding_sync.py
@@ -23,11 +23,12 @@ from twisted.test.proto_helpers import MemoryReactor
 import synapse.rest.admin
 from synapse.api.constants import (
     AccountDataTypes,
-    EventContentFields,
     EventTypes,
-    RoomTypes,
+    Membership,
 )
-from synapse.events import EventBase
+from synapse.api.room_versions import RoomVersions
+from synapse.events import EventBase, StrippedStateEvent, make_event_from_dict
+from synapse.events.snapshot import EventContext
 from synapse.rest.client import devices, login, receipts, room, sync
 from synapse.server import HomeServer
 from synapse.types import (
@@ -141,6 +142,167 @@ class SlidingSyncBase(unittest.HomeserverTestCase):
             message=str(actual_required_state),
         )
 
+    def _add_new_dm_to_global_account_data(
+        self, source_user_id: str, target_user_id: str, target_room_id: str
+    ) -> None:
+        """
+        Helper to handle inserting a new DM for the source user into global account data
+        (handles all of the list merging).
+
+        Args:
+            source_user_id: The user ID of the DM mapping we're going to update
+            target_user_id: User ID of the person the DM is with
+            target_room_id: Room ID of the DM
+        """
+        store = self.hs.get_datastores().main
+
+        # Get the current DM map
+        existing_dm_map = self.get_success(
+            store.get_global_account_data_by_type_for_user(
+                source_user_id, AccountDataTypes.DIRECT
+            )
+        )
+        # Scrutinize the account data since it has no concrete type. We're just copying
+        # everything into a known type. It should be a mapping from user ID to a list of
+        # room IDs. Ignore anything else.
+        new_dm_map: Dict[str, List[str]] = {}
+        if isinstance(existing_dm_map, dict):
+            for user_id, room_ids in existing_dm_map.items():
+                if isinstance(user_id, str) and isinstance(room_ids, list):
+                    for room_id in room_ids:
+                        if isinstance(room_id, str):
+                            new_dm_map[user_id] = new_dm_map.get(user_id, []) + [
+                                room_id
+                            ]
+
+        # Add the new DM to the map
+        new_dm_map[target_user_id] = new_dm_map.get(target_user_id, []) + [
+            target_room_id
+        ]
+        # Save the DM map to global account data
+        self.get_success(
+            store.add_account_data_for_user(
+                source_user_id,
+                AccountDataTypes.DIRECT,
+                new_dm_map,
+            )
+        )
+
+    def _create_dm_room(
+        self,
+        inviter_user_id: str,
+        inviter_tok: str,
+        invitee_user_id: str,
+        invitee_tok: str,
+        should_join_room: bool = True,
+    ) -> str:
+        """
+        Helper to create a DM room as the "inviter" and invite the "invitee" user to the
+        room. The "invitee" user also will join the room. The `m.direct` account data
+        will be set for both users.
+        """
+        # Create a room and send an invite the other user
+        room_id = self.helper.create_room_as(
+            inviter_user_id,
+            is_public=False,
+            tok=inviter_tok,
+        )
+        self.helper.invite(
+            room_id,
+            src=inviter_user_id,
+            targ=invitee_user_id,
+            tok=inviter_tok,
+            extra_data={"is_direct": True},
+        )
+        if should_join_room:
+            # Person that was invited joins the room
+            self.helper.join(room_id, invitee_user_id, tok=invitee_tok)
+
+        # Mimic the client setting the room as a direct message in the global account
+        # data for both users.
+        self._add_new_dm_to_global_account_data(
+            invitee_user_id, inviter_user_id, room_id
+        )
+        self._add_new_dm_to_global_account_data(
+            inviter_user_id, invitee_user_id, room_id
+        )
+
+        return room_id
+
+    _remote_invite_count: int = 0
+
+    def _create_remote_invite_room_for_user(
+        self,
+        invitee_user_id: str,
+        unsigned_invite_room_state: Optional[List[StrippedStateEvent]],
+    ) -> str:
+        """
+        Create a fake invite for a remote room and persist it.
+
+        We don't have any state for these kind of rooms and can only rely on the
+        stripped state included in the unsigned portion of the invite event to identify
+        the room.
+
+        Args:
+            invitee_user_id: The person being invited
+            unsigned_invite_room_state: List of stripped state events to assist the
+                receiver in identifying the room.
+
+        Returns:
+            The room ID of the remote invite room
+        """
+        store = self.hs.get_datastores().main
+
+        invite_room_id = f"!test_room{self._remote_invite_count}:remote_server"
+
+        invite_event_dict = {
+            "room_id": invite_room_id,
+            "sender": "@inviter:remote_server",
+            "state_key": invitee_user_id,
+            "depth": 1,
+            "origin_server_ts": 1,
+            "type": EventTypes.Member,
+            "content": {"membership": Membership.INVITE},
+            "auth_events": [],
+            "prev_events": [],
+        }
+        if unsigned_invite_room_state is not None:
+            serialized_stripped_state_events = []
+            for stripped_event in unsigned_invite_room_state:
+                serialized_stripped_state_events.append(
+                    {
+                        "type": stripped_event.type,
+                        "state_key": stripped_event.state_key,
+                        "sender": stripped_event.sender,
+                        "content": stripped_event.content,
+                    }
+                )
+
+            invite_event_dict["unsigned"] = {
+                "invite_room_state": serialized_stripped_state_events
+            }
+
+        invite_event = make_event_from_dict(
+            invite_event_dict,
+            room_version=RoomVersions.V10,
+        )
+        invite_event.internal_metadata.outlier = True
+        invite_event.internal_metadata.out_of_band_membership = True
+
+        self.get_success(
+            store.maybe_store_room_on_outlier_membership(
+                room_id=invite_room_id, room_version=invite_event.room_version
+            )
+        )
+        context = EventContext.for_outlier(self.hs.get_storage_controllers())
+        persist_controller = self.hs.get_storage_controllers().persistence
+        assert persist_controller is not None
+        self.get_success(persist_controller.persist_event(invite_event, context))
+
+        self._remote_invite_count += 1
+
+        return invite_room_id
+
     def _bump_notifier_wait_for_events(
         self,
         user_id: str,
@@ -261,93 +423,6 @@ class SlidingSyncTestCase(SlidingSyncBase):
 
         super().prepare(reactor, clock, hs)
 
-    def _add_new_dm_to_global_account_data(
-        self, source_user_id: str, target_user_id: str, target_room_id: str
-    ) -> None:
-        """
-        Helper to handle inserting a new DM for the source user into global account data
-        (handles all of the list merging).
-
-        Args:
-            source_user_id: The user ID of the DM mapping we're going to update
-            target_user_id: User ID of the person the DM is with
-            target_room_id: Room ID of the DM
-        """
-
-        # Get the current DM map
-        existing_dm_map = self.get_success(
-            self.store.get_global_account_data_by_type_for_user(
-                source_user_id, AccountDataTypes.DIRECT
-            )
-        )
-        # Scrutinize the account data since it has no concrete type. We're just copying
-        # everything into a known type. It should be a mapping from user ID to a list of
-        # room IDs. Ignore anything else.
-        new_dm_map: Dict[str, List[str]] = {}
-        if isinstance(existing_dm_map, dict):
-            for user_id, room_ids in existing_dm_map.items():
-                if isinstance(user_id, str) and isinstance(room_ids, list):
-                    for room_id in room_ids:
-                        if isinstance(room_id, str):
-                            new_dm_map[user_id] = new_dm_map.get(user_id, []) + [
-                                room_id
-                            ]
-
-        # Add the new DM to the map
-        new_dm_map[target_user_id] = new_dm_map.get(target_user_id, []) + [
-            target_room_id
-        ]
-        # Save the DM map to global account data
-        self.get_success(
-            self.store.add_account_data_for_user(
-                source_user_id,
-                AccountDataTypes.DIRECT,
-                new_dm_map,
-            )
-        )
-
-    def _create_dm_room(
-        self,
-        inviter_user_id: str,
-        inviter_tok: str,
-        invitee_user_id: str,
-        invitee_tok: str,
-        should_join_room: bool = True,
-    ) -> str:
-        """
-        Helper to create a DM room as the "inviter" and invite the "invitee" user to the
-        room. The "invitee" user also will join the room. The `m.direct` account data
-        will be set for both users.
-        """
-
-        # Create a room and send an invite the other user
-        room_id = self.helper.create_room_as(
-            inviter_user_id,
-            is_public=False,
-            tok=inviter_tok,
-        )
-        self.helper.invite(
-            room_id,
-            src=inviter_user_id,
-            targ=invitee_user_id,
-            tok=inviter_tok,
-            extra_data={"is_direct": True},
-        )
-        if should_join_room:
-            # Person that was invited joins the room
-            self.helper.join(room_id, invitee_user_id, tok=invitee_tok)
-
-        # Mimic the client setting the room as a direct message in the global account
-        # data for both users.
-        self._add_new_dm_to_global_account_data(
-            invitee_user_id, inviter_user_id, room_id
-        )
-        self._add_new_dm_to_global_account_data(
-            inviter_user_id, invitee_user_id, room_id
-        )
-
-        return room_id
-
     def test_sync_list(self) -> None:
         """
         Test that room IDs show up in the Sliding Sync `lists`
@@ -547,323 +622,6 @@ class SlidingSyncTestCase(SlidingSyncBase):
         # There should be no room sent down.
         self.assertFalse(channel.json_body["rooms"])
 
-    def test_filter_list(self) -> None:
-        """
-        Test that filters apply to `lists`
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-        user2_id = self.register_user("user2", "pass")
-        user2_tok = self.login(user2_id, "pass")
-
-        # Create a DM room
-        joined_dm_room_id = self._create_dm_room(
-            inviter_user_id=user1_id,
-            inviter_tok=user1_tok,
-            invitee_user_id=user2_id,
-            invitee_tok=user2_tok,
-            should_join_room=True,
-        )
-        invited_dm_room_id = self._create_dm_room(
-            inviter_user_id=user1_id,
-            inviter_tok=user1_tok,
-            invitee_user_id=user2_id,
-            invitee_tok=user2_tok,
-            should_join_room=False,
-        )
-
-        # Create a normal room
-        room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
-        self.helper.join(room_id, user1_id, tok=user1_tok)
-
-        # Create a room that user1 is invited to
-        invite_room_id = self.helper.create_room_as(user2_id, tok=user2_tok)
-        self.helper.invite(invite_room_id, src=user2_id, targ=user1_id, tok=user2_tok)
-
-        # Make the Sliding Sync request
-        sync_body = {
-            "lists": {
-                # Absense of filters does not imply "False" values
-                "all": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 1,
-                    "filters": {},
-                },
-                # Test single truthy filter
-                "dms": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 1,
-                    "filters": {"is_dm": True},
-                },
-                # Test single falsy filter
-                "non-dms": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 1,
-                    "filters": {"is_dm": False},
-                },
-                # Test how multiple filters should stack (AND'd together)
-                "room-invites": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 1,
-                    "filters": {"is_dm": False, "is_invite": True},
-                },
-            }
-        }
-        response_body, _ = self.do_sync(sync_body, tok=user1_tok)
-
-        # Make sure it has the foo-list we requested
-        self.assertListEqual(
-            list(response_body["lists"].keys()),
-            ["all", "dms", "non-dms", "room-invites"],
-            response_body["lists"].keys(),
-        )
-
-        # Make sure the lists have the correct rooms
-        self.assertListEqual(
-            list(response_body["lists"]["all"]["ops"]),
-            [
-                {
-                    "op": "SYNC",
-                    "range": [0, 99],
-                    "room_ids": [
-                        invite_room_id,
-                        room_id,
-                        invited_dm_room_id,
-                        joined_dm_room_id,
-                    ],
-                }
-            ],
-            list(response_body["lists"]["all"]),
-        )
-        self.assertListEqual(
-            list(response_body["lists"]["dms"]["ops"]),
-            [
-                {
-                    "op": "SYNC",
-                    "range": [0, 99],
-                    "room_ids": [invited_dm_room_id, joined_dm_room_id],
-                }
-            ],
-            list(response_body["lists"]["dms"]),
-        )
-        self.assertListEqual(
-            list(response_body["lists"]["non-dms"]["ops"]),
-            [
-                {
-                    "op": "SYNC",
-                    "range": [0, 99],
-                    "room_ids": [invite_room_id, room_id],
-                }
-            ],
-            list(response_body["lists"]["non-dms"]),
-        )
-        self.assertListEqual(
-            list(response_body["lists"]["room-invites"]["ops"]),
-            [
-                {
-                    "op": "SYNC",
-                    "range": [0, 99],
-                    "room_ids": [invite_room_id],
-                }
-            ],
-            list(response_body["lists"]["room-invites"]),
-        )
-
-        # Ensure DM's are correctly marked
-        self.assertDictEqual(
-            {
-                room_id: room.get("is_dm")
-                for room_id, room in response_body["rooms"].items()
-            },
-            {
-                invite_room_id: None,
-                room_id: None,
-                invited_dm_room_id: True,
-                joined_dm_room_id: True,
-            },
-        )
-
-    def test_filter_regardless_of_membership_server_left_room(self) -> None:
-        """
-        Test that filters apply to rooms regardless of membership. We're also
-        compounding the problem by having all of the local users leave the room causing
-        our server to leave the room.
-
-        We want to make sure that if someone is filtering rooms, and leaves, you still
-        get that final update down sync that you left.
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-        user2_id = self.register_user("user2", "pass")
-        user2_tok = self.login(user2_id, "pass")
-
-        # Create a normal room
-        room_id = self.helper.create_room_as(user1_id, tok=user2_tok)
-        self.helper.join(room_id, user1_id, tok=user1_tok)
-
-        # Create an encrypted space room
-        space_room_id = self.helper.create_room_as(
-            user2_id,
-            tok=user2_tok,
-            extra_content={
-                "creation_content": {EventContentFields.ROOM_TYPE: RoomTypes.SPACE}
-            },
-        )
-        self.helper.send_state(
-            space_room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user2_tok,
-        )
-        self.helper.join(space_room_id, user1_id, tok=user1_tok)
-
-        # Make an initial Sliding Sync request
-        sync_body = {
-            "lists": {
-                "all-list": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 0,
-                    "filters": {},
-                },
-                "foo-list": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 1,
-                    "filters": {
-                        "is_encrypted": True,
-                        "room_types": [RoomTypes.SPACE],
-                    },
-                },
-            }
-        }
-        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
-
-        # Make sure the response has the lists we requested
-        self.assertListEqual(
-            list(response_body["lists"].keys()),
-            ["all-list", "foo-list"],
-            response_body["lists"].keys(),
-        )
-
-        # Make sure the lists have the correct rooms
-        self.assertListEqual(
-            list(response_body["lists"]["all-list"]["ops"]),
-            [
-                {
-                    "op": "SYNC",
-                    "range": [0, 99],
-                    "room_ids": [space_room_id, room_id],
-                }
-            ],
-        )
-        self.assertListEqual(
-            list(response_body["lists"]["foo-list"]["ops"]),
-            [
-                {
-                    "op": "SYNC",
-                    "range": [0, 99],
-                    "room_ids": [space_room_id],
-                }
-            ],
-        )
-
-        # Everyone leaves the encrypted space room
-        self.helper.leave(space_room_id, user1_id, tok=user1_tok)
-        self.helper.leave(space_room_id, user2_id, tok=user2_tok)
-
-        # Make an incremental Sliding Sync request
-        sync_body = {
-            "lists": {
-                "all-list": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 0,
-                    "filters": {},
-                },
-                "foo-list": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 1,
-                    "filters": {
-                        "is_encrypted": True,
-                        "room_types": [RoomTypes.SPACE],
-                    },
-                },
-            }
-        }
-        response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok)
-
-        # Make sure the lists have the correct rooms even though we `newly_left`
-        self.assertListEqual(
-            list(response_body["lists"]["all-list"]["ops"]),
-            [
-                {
-                    "op": "SYNC",
-                    "range": [0, 99],
-                    "room_ids": [space_room_id, room_id],
-                }
-            ],
-        )
-        self.assertListEqual(
-            list(response_body["lists"]["foo-list"]["ops"]),
-            [
-                {
-                    "op": "SYNC",
-                    "range": [0, 99],
-                    "room_ids": [space_room_id],
-                }
-            ],
-        )
-
-    def test_filter_is_encrypted_up_to_date(self) -> None:
-        """
-        Make sure we get up-to-date `is_encrypted` status for a joined room
-        """
-        user1_id = self.register_user("user1", "pass")
-        user1_tok = self.login(user1_id, "pass")
-
-        room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
-
-        sync_body = {
-            "lists": {
-                "foo-list": {
-                    "ranges": [[0, 99]],
-                    "required_state": [],
-                    "timeline_limit": 0,
-                    "filters": {
-                        "is_encrypted": True,
-                    },
-                },
-            }
-        }
-        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
-        self.assertIncludes(
-            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
-            set(),
-            exact=True,
-        )
-
-        # Update the encryption status
-        self.helper.send_state(
-            room_id,
-            EventTypes.RoomEncryption,
-            {EventContentFields.ENCRYPTION_ALGORITHM: "m.megolm.v1.aes-sha2"},
-            tok=user1_tok,
-        )
-
-        # We should see the room now because it's encrypted
-        response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok)
-        self.assertIncludes(
-            set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
-            {room_id},
-            exact=True,
-        )
-
     def test_forgotten_up_to_date(self) -> None:
         """
         Make sure we get up-to-date `forgotten` status for rooms
-- 
GitLab