# # 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: # . # import enum import logging from parameterized import parameterized, parameterized_class from twisted.internet.testing import MemoryReactor import synapse.rest.admin from synapse.api.constants import EventContentFields, EventTypes, JoinRules, Membership from synapse.handlers.sliding_sync import StateValues from synapse.rest.client import knock, login, room, sync from synapse.server import HomeServer from synapse.util.clock import Clock from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase from tests.test_utils.event_injection import mark_event_as_partial_state logger = logging.getLogger(__name__) # Inherit from `str` so that they show up in the test description when we # `@parameterized.expand(...)` the first parameter class MembershipAction(str, enum.Enum): INVITE = "invite" JOIN = "join" KNOCK = "knock" LEAVE = "leave" BAN = "ban" KICK = "kick" # 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 SlidingSyncRoomsRequiredStateTestCase(SlidingSyncBase): """ Test `rooms.required_state` in the Sliding Sync API. """ servlets = [ synapse.rest.admin.register_servlets, login.register_servlets, knock.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.storage_controllers = hs.get_storage_controllers() super().prepare(reactor, clock, hs) def test_rooms_no_required_state(self) -> None: """ Empty `rooms.required_state` should not return any state events in the room """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) # Make the Sliding Sync request sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], # Empty `required_state` "required_state": [], "timeline_limit": 0, } } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) # No `required_state` in response self.assertIsNone( response_body["rooms"][room_id1].get("required_state"), response_body["rooms"][room_id1], ) def test_rooms_required_state_initial_sync(self) -> None: """ Test `rooms.required_state` returns requested state events in the room during an initial sync. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) # Make the Sliding Sync request sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.RoomHistoryVisibility, ""], # This one doesn't exist in the room [EventTypes.Tombstone, ""], ], "timeline_limit": 0, } } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Create, "")], state_map[(EventTypes.RoomHistoryVisibility, "")], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_incremental_sync(self) -> None: """ Test `rooms.required_state` returns requested state events in the room during an incremental sync. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.RoomHistoryVisibility, ""], # This one doesn't exist in the room [EventTypes.Tombstone, ""], ], "timeline_limit": 1, } } } _, from_token = self.do_sync(sync_body, tok=user1_tok) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user1_tok) # Make the incremental Sliding Sync request response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok) # We only return updates but only if we've sent the room down the # connection before. self.assertIsNone(response_body["rooms"][room_id1].get("required_state")) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_incremental_sync_restart(self) -> None: """ Test that after a restart (and so the in memory caches are reset) that we correctly return an `M_UNKNOWN_POS` """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.RoomHistoryVisibility, ""], # This one doesn't exist in the room [EventTypes.Tombstone, ""], ], "timeline_limit": 1, } } } _, from_token = self.do_sync(sync_body, tok=user1_tok) # Reset the positions self.get_success( self.store.db_pool.simple_delete( table="sliding_sync_connections", keyvalues={"user_id": user1_id}, desc="clear_sliding_sync_connections_cache", ) ) # Make the Sliding Sync request channel = self.make_request( method="POST", path=self.sync_endpoint + f"?pos={from_token}", content=sync_body, access_token=user1_tok, ) self.assertEqual(channel.code, 400, channel.json_body) self.assertEqual( channel.json_body["errcode"], "M_UNKNOWN_POS", channel.json_body ) def test_rooms_required_state_wildcard(self) -> None: """ Test `rooms.required_state` returns all state events when using wildcard `["*", "*"]`. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key="", body={"foo": "bar"}, tok=user2_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key="namespaced", body={"foo": "bar"}, tok=user2_tok, ) # Make the Sliding Sync request with wildcards for the `event_type` and `state_key` sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [StateValues.WILDCARD, StateValues.WILDCARD], ], "timeline_limit": 0, } } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], # We should see all the state events in the room state_map.values(), exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_wildcard_event_type(self) -> None: """ Test `rooms.required_state` returns relevant state events when using wildcard in the event_type `["*", "foobarbaz"]`. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key="", body={"foo": "bar"}, tok=user2_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key=user2_id, body={"foo": "bar"}, tok=user2_tok, ) # Make the Sliding Sync request with wildcards for the `event_type` sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [StateValues.WILDCARD, user2_id], ], "timeline_limit": 0, } } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # We expect at-least any state event with the `user2_id` as the `state_key` self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Member, user2_id)], state_map[("org.matrix.foo_state", user2_id)], }, # Ideally, this would be exact but we're currently returning all state # events when the `event_type` is a wildcard. exact=False, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_wildcard_state_key(self) -> None: """ Test `rooms.required_state` returns relevant state events when using wildcard in the state_key `["foobarbaz","*"]`. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) # Make the Sliding Sync request with wildcards for the `state_key` sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Member, StateValues.WILDCARD], ], "timeline_limit": 0, } } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Member, user1_id)], state_map[(EventTypes.Member, user2_id)], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_lazy_loading_room_members_initial_sync(self) -> None: """ On initial sync, test `rooms.required_state` returns people relevant to the timeline when lazy-loading room members, `["m.room.member","$LAZY"]`. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") user3_id = self.register_user("user3", "pass") user3_tok = self.login(user3_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.join(room_id1, user3_id, tok=user3_tok) self.helper.send(room_id1, "1", tok=user2_tok) self.helper.send(room_id1, "2", tok=user3_tok) self.helper.send(room_id1, "3", tok=user2_tok) # Make the Sliding Sync request with lazy loading for the room members sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, StateValues.LAZY], ], "timeline_limit": 3, } } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # Only user2 and user3 sent events in the 3 events we see in the `timeline` self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Create, "")], state_map[(EventTypes.Member, user2_id)], state_map[(EventTypes.Member, user3_id)], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_lazy_loading_room_members_incremental_sync( self, ) -> None: """ On incremental sync, test `rooms.required_state` returns people relevant to the timeline when lazy-loading room members, `["m.room.member","$LAZY"]`. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") user3_id = self.register_user("user3", "pass") user3_tok = self.login(user3_id, "pass") user4_id = self.register_user("user4", "pass") user4_tok = self.login(user4_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.join(room_id1, user3_id, tok=user3_tok) self.helper.join(room_id1, user4_id, tok=user4_tok) self.helper.send(room_id1, "1", tok=user2_tok) self.helper.send(room_id1, "2", tok=user2_tok) self.helper.send(room_id1, "3", tok=user2_tok) # Make the Sliding Sync request with lazy loading for the room members sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, StateValues.LAZY], ], "timeline_limit": 3, } } } response_body, from_token = self.do_sync(sync_body, tok=user1_tok) # Send more timeline events into the room self.helper.send(room_id1, "4", tok=user2_tok) self.helper.send(room_id1, "5", tok=user4_tok) self.helper.send(room_id1, "6", tok=user4_tok) # Make an incremental Sliding Sync request response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # Only user2 and user4 sent events in the last 3 events we see in the `timeline` # but since we've seen user2 in the last sync (and their membership hasn't # changed), we should only see user4 here. self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Member, user4_id)], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) @parameterized.expand( [ (MembershipAction.LEAVE,), (MembershipAction.INVITE,), (MembershipAction.KNOCK,), (MembershipAction.JOIN,), (MembershipAction.BAN,), (MembershipAction.KICK,), ] ) def test_rooms_required_state_changed_membership_in_timeline_lazy_loading_room_members_incremental_sync( self, room_membership_action: str, ) -> None: """ On incremental sync, test `rooms.required_state` returns people relevant to the timeline when lazy-loading room members, `["m.room.member","$LAZY"]` **including changes to membership**. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") user3_id = self.register_user("user3", "pass") user3_tok = self.login(user3_id, "pass") user4_id = self.register_user("user4", "pass") user4_tok = self.login(user4_id, "pass") user5_id = self.register_user("user5", "pass") user5_tok = self.login(user5_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True) # If we're testing knocks, set the room to knock if room_membership_action == MembershipAction.KNOCK: self.helper.send_state( room_id1, EventTypes.JoinRules, {"join_rule": JoinRules.KNOCK}, tok=user2_tok, ) # Join the test users to the room self.helper.invite(room_id1, src=user2_id, targ=user1_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.invite(room_id1, src=user2_id, targ=user3_id, tok=user2_tok) self.helper.join(room_id1, user3_id, tok=user3_tok) self.helper.invite(room_id1, src=user2_id, targ=user4_id, tok=user2_tok) self.helper.join(room_id1, user4_id, tok=user4_tok) if room_membership_action in ( MembershipAction.LEAVE, MembershipAction.BAN, MembershipAction.JOIN, ): self.helper.invite(room_id1, src=user2_id, targ=user5_id, tok=user2_tok) self.helper.join(room_id1, user5_id, tok=user5_tok) # Send some messages to fill up the space self.helper.send(room_id1, "1", tok=user2_tok) self.helper.send(room_id1, "2", tok=user2_tok) self.helper.send(room_id1, "3", tok=user2_tok) # Make the Sliding Sync request with lazy loading for the room members sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, StateValues.LAZY], ], "timeline_limit": 3, } } } response_body, from_token = self.do_sync(sync_body, tok=user1_tok) # Send more timeline events into the room self.helper.send(room_id1, "4", tok=user2_tok) self.helper.send(room_id1, "5", tok=user4_tok) # The third event will be our membership event concerning user5 if room_membership_action == MembershipAction.LEAVE: # User 5 leaves self.helper.leave(room_id1, user5_id, tok=user5_tok) elif room_membership_action == MembershipAction.INVITE: # User 5 is invited self.helper.invite(room_id1, src=user2_id, targ=user5_id, tok=user2_tok) elif room_membership_action == MembershipAction.KNOCK: # User 5 knocks self.helper.knock(room_id1, user5_id, tok=user5_tok) # The admin of the room accepts the knock self.helper.invite(room_id1, src=user2_id, targ=user5_id, tok=user2_tok) elif room_membership_action == MembershipAction.JOIN: # Update the display name of user5 (causing a membership change) self.helper.send_state( room_id1, event_type=EventTypes.Member, state_key=user5_id, body={ EventContentFields.MEMBERSHIP: Membership.JOIN, EventContentFields.MEMBERSHIP_DISPLAYNAME: "quick changer", }, tok=user5_tok, ) elif room_membership_action == MembershipAction.BAN: self.helper.ban(room_id1, src=user2_id, targ=user5_id, tok=user2_tok) elif room_membership_action == MembershipAction.KICK: # Kick user5 from the room self.helper.change_membership( room=room_id1, src=user2_id, targ=user5_id, tok=user2_tok, membership=Membership.LEAVE, extra_data={ "reason": "Bad manners", }, ) else: raise AssertionError( f"Unknown room_membership_action: {room_membership_action}" ) # Make an incremental Sliding Sync request response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # Only user2, user4, and user5 sent events in the last 3 events we see in the # `timeline`. self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { # Appears because there is a message in the timeline from this user state_map[(EventTypes.Member, user4_id)], # Appears because there is a membership event in the timeline from this user state_map[(EventTypes.Member, user5_id)], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_expand_lazy_loading_room_members_incremental_sync( self, ) -> None: """ Test that when we expand the `required_state` to include lazy-loading room members, it returns people relevant to the timeline. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") user3_id = self.register_user("user3", "pass") user3_tok = self.login(user3_id, "pass") user4_id = self.register_user("user4", "pass") user4_tok = self.login(user4_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.join(room_id1, user3_id, tok=user3_tok) self.helper.join(room_id1, user4_id, tok=user4_tok) self.helper.send(room_id1, "1", tok=user2_tok) self.helper.send(room_id1, "2", tok=user2_tok) self.helper.send(room_id1, "3", tok=user2_tok) # Make the Sliding Sync request *without* lazy loading for the room members sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], ], "timeline_limit": 3, } } } response_body, from_token = self.do_sync(sync_body, tok=user1_tok) # Send more timeline events into the room self.helper.send(room_id1, "4", tok=user2_tok) self.helper.send(room_id1, "5", tok=user4_tok) self.helper.send(room_id1, "6", tok=user4_tok) # Expand `required_state` and make an incremental Sliding Sync request *with* # lazy-loading room members sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], [EventTypes.Member, StateValues.LAZY], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # Only user2 and user4 sent events in the last 3 events we see in the `timeline` # and we haven't seen any membership before this sync so we should see both # users. self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Member, user2_id)], state_map[(EventTypes.Member, user4_id)], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) # Send a message so the room comes down sync. self.helper.send(room_id1, "7", tok=user2_tok) self.helper.send(room_id1, "8", tok=user4_tok) self.helper.send(room_id1, "9", tok=user4_tok) # Make another incremental Sliding Sync request response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok) # Only user2 and user4 sent events in the last 3 events we see in the `timeline` # but since we've seen both memberships in the last sync, they shouldn't appear # again. self._assertRequiredStateIncludes( response_body["rooms"][room_id1].get("required_state", []), set(), exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_expand_retract_expand_lazy_loading_room_members_incremental_sync( self, ) -> None: """ Test that when we expand the `required_state` to include lazy-loading room members, it returns people relevant to the timeline. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") user3_id = self.register_user("user3", "pass") user3_tok = self.login(user3_id, "pass") user4_id = self.register_user("user4", "pass") user4_tok = self.login(user4_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.join(room_id1, user3_id, tok=user3_tok) self.helper.join(room_id1, user4_id, tok=user4_tok) self.helper.send(room_id1, "1", tok=user2_tok) self.helper.send(room_id1, "2", tok=user2_tok) self.helper.send(room_id1, "3", tok=user2_tok) # Make the Sliding Sync request *without* lazy loading for the room members sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], ], "timeline_limit": 3, } } } response_body, from_token = self.do_sync(sync_body, tok=user1_tok) # Send more timeline events into the room self.helper.send(room_id1, "4", tok=user2_tok) self.helper.send(room_id1, "5", tok=user4_tok) self.helper.send(room_id1, "6", tok=user4_tok) # Expand `required_state` and make an incremental Sliding Sync request *with* # lazy-loading room members sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], [EventTypes.Member, StateValues.LAZY], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # Only user2 and user4 sent events in the last 3 events we see in the `timeline` # and we haven't seen any membership before this sync so we should see both # users because we're lazy-loading the room members. self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Member, user2_id)], state_map[(EventTypes.Member, user4_id)], }, exact=True, ) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user4_tok) # Retract `required_state` and make an incremental Sliding Sync request # requesting a few memberships sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], [EventTypes.Member, StateValues.ME], [EventTypes.Member, user2_id], ] response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # We've seen user2's membership in the last sync so we shouldn't see it here # even though it's requested. We should only see user1's membership. self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Member, user1_id)], }, exact=True, ) def test_lazy_members_limited_sync(self) -> None: """Test that when using lazy loading for room members and a limited sync missing a membership change, we include the membership change next time said user says something. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) # Send a message from each user to the room so that both memberships are sent down. self.helper.send(room_id1, "1", tok=user1_tok) self.helper.send(room_id1, "2", tok=user2_tok) # Make a first sync to establish a position sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Member, StateValues.LAZY], ], "timeline_limit": 2, } } } response_body, from_token = self.do_sync(sync_body, tok=user1_tok) # We should see both membership events in required_state state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Member, user1_id)], state_map[(EventTypes.Member, user2_id)], }, exact=True, ) # User2 changes their display name (causing a membership change) self.helper.send_state( room_id1, event_type=EventTypes.Member, state_key=user2_id, body={ EventContentFields.MEMBERSHIP: Membership.JOIN, EventContentFields.MEMBERSHIP_DISPLAYNAME: "New Name", }, tok=user2_tok, ) # Send a couple of messages to the room to push out the membership change self.helper.send(room_id1, "3", tok=user1_tok) self.helper.send(room_id1, "4", tok=user1_tok) # Make an incremental Sliding Sync request response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # The membership change should *not* be included yet as user2 doesn't # have any events in the timeline. self._assertRequiredStateIncludes( response_body["rooms"][room_id1].get("required_state", []), set(), exact=True, ) # Now user2 sends a message to the room self.helper.send(room_id1, "5", tok=user2_tok) # Make another incremental Sliding Sync request response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # The membership change should now be included as user2 has an event # in the timeline. state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1].get("required_state", []), { state_map[(EventTypes.Member, user2_id)], }, exact=True, ) def test_rooms_required_state_me(self) -> None: """ Test `rooms.required_state` correctly handles $ME. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.send(room_id1, "1", tok=user2_tok) # Also send normal state events with state keys of the users, first # change the power levels to allow this. self.helper.send_state( room_id1, event_type=EventTypes.PowerLevels, body={"users": {user1_id: 50, user2_id: 100}}, tok=user2_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.foo", state_key=user1_id, body={}, tok=user1_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.foo", state_key=user2_id, body={}, tok=user2_tok, ) # Make the Sliding Sync request with a request for '$ME'. sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, StateValues.ME], ["org.matrix.foo", StateValues.ME], ], "timeline_limit": 3, } } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # Only user2 and user3 sent events in the 3 events we see in the `timeline` self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Create, "")], state_map[(EventTypes.Member, user1_id)], state_map[("org.matrix.foo", user1_id)], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) @parameterized.expand([(Membership.LEAVE,), (Membership.BAN,)]) def test_rooms_required_state_leave_ban_initial(self, stop_membership: str) -> None: """ Test `rooms.required_state` should not return state past a leave/ban event when it's the first "initial" time the room is being sent down the connection. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") user3_id = self.register_user("user3", "pass") user3_tok = self.login(user3_id, "pass") sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, "*"], ["org.matrix.foo_state", ""], ], "timeline_limit": 3, } } } _, from_token = self.do_sync(sync_body, tok=user1_tok) room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.join(room_id1, user3_id, tok=user3_tok) self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key="", body={"foo": "bar"}, tok=user2_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.bar_state", state_key="", body={"bar": "bar"}, tok=user2_tok, ) if stop_membership == Membership.LEAVE: # User 1 leaves self.helper.leave(room_id1, user1_id, tok=user1_tok) elif stop_membership == Membership.BAN: # User 1 is banned self.helper.ban(room_id1, src=user2_id, targ=user1_id, tok=user2_tok) # Get the state_map before we change the state as this is the final state we # expect User1 to be able to see state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # Change the state after user 1 leaves self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key="", body={"foo": "qux"}, tok=user2_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.bar_state", state_key="", body={"bar": "qux"}, tok=user2_tok, ) self.helper.leave(room_id1, user3_id, tok=user3_tok) # Make an incremental Sliding Sync request # # Also expand the required state to include the `org.matrix.bar_state` event. # This is just an extra complication of the test. sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, "*"], ["org.matrix.foo_state", ""], ["org.matrix.bar_state", ""], ], "timeline_limit": 3, } } } response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok) # We should only see the state up to the leave/ban event self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Create, "")], state_map[(EventTypes.Member, user1_id)], state_map[(EventTypes.Member, user2_id)], state_map[(EventTypes.Member, user3_id)], state_map[("org.matrix.foo_state", "")], state_map[("org.matrix.bar_state", "")], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) @parameterized.expand([(Membership.LEAVE,), (Membership.BAN,)]) def test_rooms_required_state_leave_ban_incremental( self, stop_membership: str ) -> None: """ Test `rooms.required_state` should not return state past a leave/ban event on incremental sync. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") user3_id = self.register_user("user3", "pass") user3_tok = self.login(user3_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.join(room_id1, user3_id, tok=user3_tok) self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key="", body={"foo": "bar"}, tok=user2_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.bar_state", state_key="", body={"bar": "bar"}, tok=user2_tok, ) sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, "*"], ["org.matrix.foo_state", ""], ], "timeline_limit": 3, } } } _, from_token = self.do_sync(sync_body, tok=user1_tok) if stop_membership == Membership.LEAVE: # User 1 leaves self.helper.leave(room_id1, user1_id, tok=user1_tok) elif stop_membership == Membership.BAN: # User 1 is banned self.helper.ban(room_id1, src=user2_id, targ=user1_id, tok=user2_tok) # Get the state_map before we change the state as this is the final state we # expect User1 to be able to see state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) # Change the state after user 1 leaves self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key="", body={"foo": "qux"}, tok=user2_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.bar_state", state_key="", body={"bar": "qux"}, tok=user2_tok, ) self.helper.leave(room_id1, user3_id, tok=user3_tok) # Make an incremental Sliding Sync request # # Also expand the required state to include the `org.matrix.bar_state` event. # This is just an extra complication of the test. sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, "*"], ["org.matrix.foo_state", ""], ["org.matrix.bar_state", ""], ], "timeline_limit": 3, } } } response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok) # User1 should only see the state up to the leave/ban event self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { # User1 should see their leave/ban membership state_map[(EventTypes.Member, user1_id)], state_map[("org.matrix.bar_state", "")], # The commented out state events were already returned in the initial # sync so we shouldn't see them again on the incremental sync. And we # shouldn't see the state events that changed after the leave/ban event. # # state_map[(EventTypes.Create, "")], # state_map[(EventTypes.Member, user2_id)], # state_map[(EventTypes.Member, user3_id)], # state_map[("org.matrix.foo_state", "")], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_combine_superset(self) -> None: """ Test `rooms.required_state` is combined across lists and room subscriptions. """ user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") user2_id = self.register_user("user2", "pass") user2_tok = self.login(user2_id, "pass") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) self.helper.join(room_id1, user1_id, tok=user1_tok) self.helper.send_state( room_id1, event_type="org.matrix.foo_state", state_key="", body={"foo": "bar"}, tok=user2_tok, ) self.helper.send_state( room_id1, event_type="org.matrix.bar_state", state_key="", body={"bar": "qux"}, tok=user2_tok, ) # Make the Sliding Sync request with wildcards for the `state_key` sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], [EventTypes.Member, user1_id], ], "timeline_limit": 0, }, "bar-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Member, StateValues.WILDCARD], ["org.matrix.foo_state", ""], ], "timeline_limit": 0, }, }, "room_subscriptions": { room_id1: { "required_state": [["org.matrix.bar_state", ""]], "timeline_limit": 0, } }, } response_body, _ = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Create, "")], state_map[(EventTypes.Member, user1_id)], state_map[(EventTypes.Member, user2_id)], state_map[("org.matrix.foo_state", "")], state_map[("org.matrix.bar_state", "")], }, exact=True, ) self.assertIsNone(response_body["rooms"][room_id1].get("invite_state")) def test_rooms_required_state_partial_state(self) -> None: """ Test partially-stated room are excluded if they require full 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") room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok) room_id2 = self.helper.create_room_as(user2_id, tok=user2_tok) _join_response1 = self.helper.join(room_id1, user1_id, tok=user1_tok) join_response2 = self.helper.join(room_id2, user1_id, tok=user1_tok) # Mark room2 as partial state self.get_success( mark_event_as_partial_state(self.hs, join_response2["event_id"], room_id2) ) # Make the Sliding Sync request with examples where `must_await_full_state()` is # `False` sync_body = { "lists": { "no-state-list": { "ranges": [[0, 1]], "required_state": [], "timeline_limit": 0, }, "other-state-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], ], "timeline_limit": 0, }, "lazy-load-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], # Lazy-load room members [EventTypes.Member, StateValues.LAZY], # Local member [EventTypes.Member, user2_id], ], "timeline_limit": 0, }, "local-members-only-list": { "ranges": [[0, 1]], "required_state": [ # Own user ID [EventTypes.Member, user1_id], # Local member [EventTypes.Member, user2_id], ], "timeline_limit": 0, }, "me-list": { "ranges": [[0, 1]], "required_state": [ # Own user ID [EventTypes.Member, StateValues.ME], # Local member [EventTypes.Member, user2_id], ], "timeline_limit": 0, }, "wildcard-type-local-state-key-list": { "ranges": [[0, 1]], "required_state": [ ["*", user1_id], # Not a user ID ["*", "foobarbaz"], # Not a user ID ["*", "foo.bar.baz"], # Not a user ID ["*", "@foo"], ], "timeline_limit": 0, }, } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) # The list should include both rooms now because we don't need full state for list_key in response_body["lists"].keys(): self.assertIncludes( set(response_body["lists"][list_key]["ops"][0]["room_ids"]), {room_id2, room_id1}, exact=True, message=f"Expected all rooms to show up for list_key={list_key}. Response " + str(response_body["lists"][list_key]), ) # Take each of the list variants and apply them to room subscriptions to make # sure the same rules apply for list_key in sync_body["lists"].keys(): sync_body_for_subscriptions = { "room_subscriptions": { room_id1: { "required_state": sync_body["lists"][list_key][ "required_state" ], "timeline_limit": 0, }, room_id2: { "required_state": sync_body["lists"][list_key][ "required_state" ], "timeline_limit": 0, }, } } response_body, _ = self.do_sync(sync_body_for_subscriptions, tok=user1_tok) self.assertIncludes( set(response_body["rooms"].keys()), {room_id2, room_id1}, exact=True, message=f"Expected all rooms to show up for test_key={list_key}.", ) # ===================================================================== # Make the Sliding Sync request with examples where `must_await_full_state()` is # `True` sync_body = { "lists": { "wildcard-list": { "ranges": [[0, 1]], "required_state": [ ["*", "*"], ], "timeline_limit": 0, }, "wildcard-type-remote-state-key-list": { "ranges": [[0, 1]], "required_state": [ ["*", "@some:remote"], # Not a user ID ["*", "foobarbaz"], # Not a user ID ["*", "foo.bar.baz"], # Not a user ID ["*", "@foo"], ], "timeline_limit": 0, }, "remote-member-list": { "ranges": [[0, 1]], "required_state": [ # Own user ID [EventTypes.Member, user1_id], # Remote member [EventTypes.Member, "@some:remote"], # Local member [EventTypes.Member, user2_id], ], "timeline_limit": 0, }, "lazy-but-remote-member-list": { "ranges": [[0, 1]], "required_state": [ # Lazy-load room members [EventTypes.Member, StateValues.LAZY], # Remote member [EventTypes.Member, "@some:remote"], ], "timeline_limit": 0, }, } } response_body, _ = self.do_sync(sync_body, tok=user1_tok) # Make sure the list includes room1 but room2 is excluded because it's still # partially-stated for list_key in response_body["lists"].keys(): self.assertIncludes( set(response_body["lists"][list_key]["ops"][0]["room_ids"]), {room_id1}, exact=True, message=f"Expected only fully-stated rooms to show up for list_key={list_key}. Response " + str(response_body["lists"][list_key]), ) # Take each of the list variants and apply them to room subscriptions to make # sure the same rules apply for list_key in sync_body["lists"].keys(): sync_body_for_subscriptions = { "room_subscriptions": { room_id1: { "required_state": sync_body["lists"][list_key][ "required_state" ], "timeline_limit": 0, }, room_id2: { "required_state": sync_body["lists"][list_key][ "required_state" ], "timeline_limit": 0, }, } } response_body, _ = self.do_sync(sync_body_for_subscriptions, tok=user1_tok) self.assertIncludes( set(response_body["rooms"].keys()), {room_id1}, exact=True, message=f"Expected only fully-stated rooms to show up for test_key={list_key}.", ) def test_rooms_required_state_expand(self) -> None: """Test that when we expand the required state argument we get the expanded state, and not just the changes to the new expanded.""" user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") # Create a room with a room name. room_id1 = self.helper.create_room_as( user1_id, tok=user1_tok, extra_content={"name": "Foo"} ) # Only request the state event to begin with sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], ], "timeline_limit": 1, } } } response_body, from_token = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Create, "")], }, exact=True, ) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user1_tok) # Update the sliding sync requests to include the room name sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], [EventTypes.Name, ""], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # We should see the room name, even though there haven't been any # changes. self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Name, "")], }, exact=True, ) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user1_tok) # We should not see any state changes. response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) self.assertIsNone(response_body["rooms"][room_id1].get("required_state")) def test_rooms_required_state_expand_retract_expand(self) -> None: """Test that when expanding, retracting and then expanding the required state, we get the changes that happened.""" user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") # Create a room with a room name. room_id1 = self.helper.create_room_as( user1_id, tok=user1_tok, extra_content={"name": "Foo"} ) # Only request the state event to begin with sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], ], "timeline_limit": 1, } } } response_body, from_token = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Create, "")], }, exact=True, ) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user1_tok) # Update the sliding sync requests to include the room name sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], [EventTypes.Name, ""], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # We should see the room name, even though there haven't been any # changes. self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Name, "")], }, exact=True, ) # Update the room name self.helper.send_state( room_id1, EventTypes.Name, {"name": "Bar"}, state_key="", tok=user1_tok ) # Update the sliding sync requests to exclude the room name again sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # We should not see the updated room name in state (though it will be in # the timeline). self.assertIsNone(response_body["rooms"][room_id1].get("required_state")) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user1_tok) # Update the sliding sync requests to include the room name again sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], [EventTypes.Name, ""], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # We should see the *new* room name, even though there haven't been any # changes. state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Name, "")], }, exact=True, ) def test_rooms_required_state_expand_deduplicate(self) -> None: """Test that when expanding, retracting and then expanding the required state, we don't get the state down again if it hasn't changed""" user1_id = self.register_user("user1", "pass") user1_tok = self.login(user1_id, "pass") # Create a room with a room name. room_id1 = self.helper.create_room_as( user1_id, tok=user1_tok, extra_content={"name": "Foo"} ) # Only request the state event to begin with sync_body = { "lists": { "foo-list": { "ranges": [[0, 1]], "required_state": [ [EventTypes.Create, ""], ], "timeline_limit": 1, } } } response_body, from_token = self.do_sync(sync_body, tok=user1_tok) state_map = self.get_success( self.storage_controllers.state.get_current_state(room_id1) ) self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Create, "")], }, exact=True, ) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user1_tok) # Update the sliding sync requests to include the room name sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], [EventTypes.Name, ""], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # We should see the room name, even though there haven't been any # changes. self._assertRequiredStateIncludes( response_body["rooms"][room_id1]["required_state"], { state_map[(EventTypes.Name, "")], }, exact=True, ) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user1_tok) # Update the sliding sync requests to exclude the room name again sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # We should not see any state updates self.assertIsNone(response_body["rooms"][room_id1].get("required_state")) # Send a message so the room comes down sync. self.helper.send(room_id1, "msg", tok=user1_tok) # Update the sliding sync requests to include the room name again sync_body["lists"]["foo-list"]["required_state"] = [ [EventTypes.Create, ""], [EventTypes.Name, ""], ] response_body, from_token = self.do_sync( sync_body, since=from_token, tok=user1_tok ) # We should not see the room name again, as we have already sent that # down. self.assertIsNone(response_body["rooms"][room_id1].get("required_state"))