Experimental support for MSC3266 Room Summary API. (#10394)

This commit is contained in:
Michael Telatynski 2021-08-16 15:49:12 +01:00 committed by GitHub
parent 87b62f8bb2
commit 0ace38b7b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 289 additions and 115 deletions

View file

@ -0,0 +1 @@
Initial local support for [MSC3266](https://github.com/matrix-org/synapse/pull/10394), Room Summary over the unstable `/rooms/{roomIdOrAlias}/summary` API.

View file

@ -86,7 +86,7 @@ files =
tests/test_event_auth.py,
tests/test_utils,
tests/handlers/test_password_providers.py,
tests/handlers/test_space_summary.py,
tests/handlers/test_room_summary.py,
tests/rest/client/v1/test_login.py,
tests/rest/client/v2_alpha/test_auth.py,
tests/util/test_itertools.py,

View file

@ -38,3 +38,6 @@ class ExperimentalConfig(Config):
# MSC3244 (room version capabilities)
self.msc3244_enabled: bool = experimental.get("msc3244_enabled", False)
# MSC3266 (room summary api)
self.msc3266_enabled: bool = experimental.get("msc3266_enabled", False)

View file

@ -547,7 +547,7 @@ class FederationSpaceSummaryServlet(BaseFederationServlet):
server_name: str,
):
super().__init__(hs, authenticator, ratelimiter, server_name)
self.handler = hs.get_space_summary_handler()
self.handler = hs.get_room_summary_handler()
async def on_GET(
self,
@ -608,7 +608,7 @@ class FederationRoomHierarchyServlet(BaseFederationServlet):
server_name: str,
):
super().__init__(hs, authenticator, ratelimiter, server_name)
self.handler = hs.get_space_summary_handler()
self.handler = hs.get_room_summary_handler()
async def on_GET(
self,

View file

@ -28,7 +28,7 @@ from synapse.api.constants import (
Membership,
RoomTypes,
)
from synapse.api.errors import AuthError, Codes, SynapseError
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
from synapse.events import EventBase
from synapse.events.utils import format_event_for_client_v2
from synapse.types import JsonDict
@ -75,7 +75,7 @@ class _PaginationSession:
processed_rooms: Set[str]
class SpaceSummaryHandler:
class RoomSummaryHandler:
# The time a pagination session remains valid for.
_PAGINATION_SESSION_VALIDITY_PERIOD_MS = 5 * 60 * 1000
@ -412,7 +412,7 @@ class SpaceSummaryHandler:
room_entry,
children_room_entries,
inaccessible_children,
) = await self._summarize_remote_room_hiearchy(
) = await self._summarize_remote_room_hierarchy(
queue_entry,
suggested_only,
)
@ -724,7 +724,7 @@ class SpaceSummaryHandler:
return results
async def _summarize_remote_room_hiearchy(
async def _summarize_remote_room_hierarchy(
self, room: "_RoomQueueEntry", suggested_only: bool
) -> Tuple[Optional["_RoomEntry"], Dict[str, JsonDict], Set[str]]:
"""
@ -781,25 +781,25 @@ class SpaceSummaryHandler:
self, room_id: str, requester: Optional[str], origin: Optional[str] = None
) -> bool:
"""
Calculate whether the room should be shown in the spaces summary.
Calculate whether the room should be shown to the requester.
It should be included if:
It should return true if:
* The requester is joined or can join the room (per MSC3173).
* The origin server has any user that is joined or can join the room.
* The history visibility is set to world readable.
Args:
room_id: The room ID to summarize.
room_id: The room ID to check accessibility of.
requester:
The user requesting the summary, if it is a local request. None
if this is a federation request.
The user making the request, if it is a local request.
None if this is a federation request.
origin:
The server requesting the summary, if it is a federation request.
The server making the request, if it is a federation request.
None if this is a local request.
Returns:
True if the room should be included in the spaces summary.
True if the room is accessible to the requesting user or server.
"""
state_ids = await self._store.get_current_state_ids(room_id)
@ -893,9 +893,9 @@ class SpaceSummaryHandler:
self, requester: str, room_id: str, room: JsonDict
) -> bool:
"""
Calculate whether the room received over federation should be shown in the spaces summary.
Calculate whether the room received over federation should be shown to the requester.
It should be included if:
It should return true if:
* The requester is joined or can join the room (per MSC3173).
* The history visibility is set to world readable.
@ -907,10 +907,10 @@ class SpaceSummaryHandler:
Args:
requester: The user requesting the summary.
room_id: The room ID returned over federation.
room: The summary of the child room returned over federation.
room: The summary of the room returned over federation.
Returns:
True if the room should be included in the spaces summary.
True if the room is accessible to the requesting user.
"""
# The API doesn't return the room version so assume that a
# join rule of knock is valid.
@ -936,7 +936,7 @@ class SpaceSummaryHandler:
async def _build_room_entry(self, room_id: str, for_federation: bool) -> JsonDict:
"""
Generate en entry suitable for the 'rooms' list in the summary response.
Generate en entry summarising a single room.
Args:
room_id: The room ID to summarize.
@ -1024,6 +1024,61 @@ class SpaceSummaryHandler:
# and order to ensure we return stable results.
return sorted(filter(_has_valid_via, events), key=_child_events_comparison_key)
async def get_room_summary(
self,
requester: Optional[str],
room_id: str,
remote_room_hosts: Optional[List[str]] = None,
) -> JsonDict:
"""
Implementation of the room summary C-S API from MSC3266
Args:
requester: user id of the user making this request, will be None
for unauthenticated requests
room_id: room id to summarise.
remote_room_hosts: a list of homeservers to try fetching data through
if we don't know it ourselves
Returns:
summary dict to return
"""
is_in_room = await self._store.is_host_joined(room_id, self._server_name)
if is_in_room:
room_entry = await self._summarize_local_room(
requester,
None,
room_id,
# Suggested-only doesn't matter since no children are requested.
suggested_only=False,
max_children=0,
)
if not room_entry:
raise NotFoundError("Room not found or is not accessible")
room_summary = room_entry.room
# If there was a requester, add their membership.
if requester:
(
membership,
_,
) = await self._store.get_local_current_membership_for_user_in_room(
requester, room_id
)
room_summary["membership"] = membership or "leave"
else:
# TODO federation API, descoped from initial unstable implementation
# as MSC needs more maturing on that side.
raise SynapseError(400, "Federation is not currently supported.")
return room_summary
@attr.s(frozen=True, slots=True, auto_attribs=True)
class _RoomQueueEntry:

View file

@ -14,16 +14,28 @@
""" This module contains base REST classes for constructing REST servlets. """
import logging
from typing import Iterable, List, Mapping, Optional, Sequence, overload
from typing import (
TYPE_CHECKING,
Iterable,
List,
Mapping,
Optional,
Sequence,
Tuple,
overload,
)
from typing_extensions import Literal
from twisted.web.server import Request
from synapse.api.errors import Codes, SynapseError
from synapse.types import JsonDict
from synapse.types import JsonDict, RoomAlias, RoomID
from synapse.util import json_decoder
if TYPE_CHECKING:
from synapse.server import HomeServer
logger = logging.getLogger(__name__)
@ -663,3 +675,45 @@ class RestServlet:
else:
raise NotImplementedError("RestServlet must register something.")
class ResolveRoomIdMixin:
def __init__(self, hs: "HomeServer"):
self.room_member_handler = hs.get_room_member_handler()
async def resolve_room_id(
self, room_identifier: str, remote_room_hosts: Optional[List[str]] = None
) -> Tuple[str, Optional[List[str]]]:
"""
Resolve a room identifier to a room ID, if necessary.
This also performanes checks to ensure the room ID is of the proper form.
Args:
room_identifier: The room ID or alias.
remote_room_hosts: The potential remote room hosts to use.
Returns:
The resolved room ID.
Raises:
SynapseError if the room ID is of the wrong form.
"""
if RoomID.is_valid(room_identifier):
resolved_room_id = room_identifier
elif RoomAlias.is_valid(room_identifier):
room_alias = RoomAlias.from_string(room_identifier)
(
room_id,
remote_room_hosts,
) = await self.room_member_handler.lookup_room_alias(room_alias)
resolved_room_id = room_id.to_string()
else:
raise SynapseError(
400, "%s was not legal room ID or room alias" % (room_identifier,)
)
if not resolved_room_id:
raise SynapseError(
400, "Unknown room ID or room alias %s" % room_identifier
)
return resolved_room_id, remote_room_hosts

View file

@ -20,6 +20,7 @@ from synapse.api.constants import EventTypes, JoinRules, Membership
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
from synapse.api.filtering import Filter
from synapse.http.servlet import (
ResolveRoomIdMixin,
RestServlet,
assert_params_in_dict,
parse_integer,
@ -33,7 +34,7 @@ from synapse.rest.admin._base import (
assert_user_is_admin,
)
from synapse.storage.databases.main.room import RoomSortOrder
from synapse.types import JsonDict, RoomAlias, RoomID, UserID, create_requester
from synapse.types import JsonDict, UserID, create_requester
from synapse.util import json_decoder
if TYPE_CHECKING:
@ -45,48 +46,6 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
class ResolveRoomIdMixin:
def __init__(self, hs: "HomeServer"):
self.room_member_handler = hs.get_room_member_handler()
async def resolve_room_id(
self, room_identifier: str, remote_room_hosts: Optional[List[str]] = None
) -> Tuple[str, Optional[List[str]]]:
"""
Resolve a room identifier to a room ID, if necessary.
This also performanes checks to ensure the room ID is of the proper form.
Args:
room_identifier: The room ID or alias.
remote_room_hosts: The potential remote room hosts to use.
Returns:
The resolved room ID.
Raises:
SynapseError if the room ID is of the wrong form.
"""
if RoomID.is_valid(room_identifier):
resolved_room_id = room_identifier
elif RoomAlias.is_valid(room_identifier):
room_alias = RoomAlias.from_string(room_identifier)
(
room_id,
remote_room_hosts,
) = await self.room_member_handler.lookup_room_alias(room_alias)
resolved_room_id = room_id.to_string()
else:
raise SynapseError(
400, "%s was not legal room ID or room alias" % (room_identifier,)
)
if not resolved_room_id:
raise SynapseError(
400, "Unknown room ID or room alias %s" % room_identifier
)
return resolved_room_id, remote_room_hosts
class ShutdownRoomRestServlet(RestServlet):
"""Shuts down a room by removing all local users from the room and blocking
all future invites and joins to the room. Any local aliases will be repointed

View file

@ -24,12 +24,14 @@ from synapse.api.errors import (
AuthError,
Codes,
InvalidClientCredentialsError,
MissingClientTokenError,
ShadowBanError,
SynapseError,
)
from synapse.api.filtering import Filter
from synapse.events.utils import format_event_for_client_v2
from synapse.http.servlet import (
ResolveRoomIdMixin,
RestServlet,
assert_params_in_dict,
parse_boolean,
@ -44,14 +46,7 @@ from synapse.rest.client.transactions import HttpTransactionCache
from synapse.rest.client.v2_alpha._base import client_patterns
from synapse.storage.state import StateFilter
from synapse.streams.config import PaginationConfig
from synapse.types import (
JsonDict,
RoomAlias,
RoomID,
StreamToken,
ThirdPartyInstanceID,
UserID,
)
from synapse.types import JsonDict, StreamToken, ThirdPartyInstanceID, UserID
from synapse.util import json_decoder
from synapse.util.stringutils import parse_and_validate_server_name, random_string
@ -266,10 +261,10 @@ class RoomSendEventRestServlet(TransactionRestServlet):
# TODO: Needs unit testing for room ID + alias joins
class JoinRoomAliasServlet(TransactionRestServlet):
class JoinRoomAliasServlet(ResolveRoomIdMixin, TransactionRestServlet):
def __init__(self, hs):
super().__init__(hs)
self.room_member_handler = hs.get_room_member_handler()
super(ResolveRoomIdMixin, self).__init__(hs) # ensure the Mixin is set up
self.auth = hs.get_auth()
def register(self, http_server):
@ -292,23 +287,12 @@ class JoinRoomAliasServlet(TransactionRestServlet):
# cheekily send invalid bodies.
content = {}
if RoomID.is_valid(room_identifier):
room_id = room_identifier
# twisted.web.server.Request.args is incorrectly defined as Optional[Any]
args: Dict[bytes, List[bytes]] = request.args # type: ignore
remote_room_hosts = parse_strings_from_args(
args, "server_name", required=False
)
elif RoomAlias.is_valid(room_identifier):
handler = self.room_member_handler
room_alias = RoomAlias.from_string(room_identifier)
room_id_obj, remote_room_hosts = await handler.lookup_room_alias(room_alias)
room_id = room_id_obj.to_string()
else:
raise SynapseError(
400, "%s was not legal room ID or room alias" % (room_identifier,)
remote_room_hosts = parse_strings_from_args(args, "server_name", required=False)
room_id, remote_room_hosts = await self.resolve_room_id(
room_identifier,
remote_room_hosts,
)
await self.room_member_handler.update_membership(
@ -1002,14 +986,14 @@ class RoomSpaceSummaryRestServlet(RestServlet):
def __init__(self, hs: "HomeServer"):
super().__init__()
self._auth = hs.get_auth()
self._space_summary_handler = hs.get_space_summary_handler()
self._room_summary_handler = hs.get_room_summary_handler()
async def on_GET(
self, request: SynapseRequest, room_id: str
) -> Tuple[int, JsonDict]:
requester = await self._auth.get_user_by_req(request, allow_guest=True)
return 200, await self._space_summary_handler.get_space_summary(
return 200, await self._room_summary_handler.get_space_summary(
requester.user.to_string(),
room_id,
suggested_only=parse_boolean(request, "suggested_only", default=False),
@ -1035,7 +1019,7 @@ class RoomSpaceSummaryRestServlet(RestServlet):
400, "'max_rooms_per_space' must be an integer", Codes.BAD_JSON
)
return 200, await self._space_summary_handler.get_space_summary(
return 200, await self._room_summary_handler.get_space_summary(
requester.user.to_string(),
room_id,
suggested_only=suggested_only,
@ -1054,7 +1038,7 @@ class RoomHierarchyRestServlet(RestServlet):
def __init__(self, hs: "HomeServer"):
super().__init__()
self._auth = hs.get_auth()
self._space_summary_handler = hs.get_space_summary_handler()
self._room_summary_handler = hs.get_room_summary_handler()
async def on_GET(
self, request: SynapseRequest, room_id: str
@ -1073,7 +1057,7 @@ class RoomHierarchyRestServlet(RestServlet):
400, "'limit' must be a positive integer", Codes.BAD_JSON
)
return 200, await self._space_summary_handler.get_room_hierarchy(
return 200, await self._room_summary_handler.get_room_hierarchy(
requester.user.to_string(),
room_id,
suggested_only=parse_boolean(request, "suggested_only", default=False),
@ -1083,6 +1067,44 @@ class RoomHierarchyRestServlet(RestServlet):
)
class RoomSummaryRestServlet(ResolveRoomIdMixin, RestServlet):
PATTERNS = (
re.compile(
"^/_matrix/client/unstable/im.nheko.summary"
"/rooms/(?P<room_identifier>[^/]*)/summary$"
),
)
def __init__(self, hs: "HomeServer"):
super().__init__(hs)
self._auth = hs.get_auth()
self._room_summary_handler = hs.get_room_summary_handler()
async def on_GET(
self, request: SynapseRequest, room_identifier: str
) -> Tuple[int, JsonDict]:
try:
requester = await self._auth.get_user_by_req(request, allow_guest=True)
requester_user_id: Optional[str] = requester.user.to_string()
except MissingClientTokenError:
# auth is optional
requester_user_id = None
# twisted.web.server.Request.args is incorrectly defined as Optional[Any]
args: Dict[bytes, List[bytes]] = request.args # type: ignore
remote_room_hosts = parse_strings_from_args(args, "via", required=False)
room_id, remote_room_hosts = await self.resolve_room_id(
room_identifier,
remote_room_hosts,
)
return 200, await self._room_summary_handler.get_room_summary(
requester_user_id,
room_id,
remote_room_hosts,
)
def register_servlets(hs: "HomeServer", http_server, is_worker=False):
RoomStateEventRestServlet(hs).register(http_server)
RoomMemberListRestServlet(hs).register(http_server)
@ -1098,6 +1120,8 @@ def register_servlets(hs: "HomeServer", http_server, is_worker=False):
RoomEventContextServlet(hs).register(http_server)
RoomSpaceSummaryRestServlet(hs).register(http_server)
RoomHierarchyRestServlet(hs).register(http_server)
if hs.config.experimental.msc3266_enabled:
RoomSummaryRestServlet(hs).register(http_server)
RoomEventServlet(hs).register(http_server)
JoinedRoomsRestServlet(hs).register(http_server)
RoomAliasListServlet(hs).register(http_server)

View file

@ -99,10 +99,10 @@ from synapse.handlers.room import (
from synapse.handlers.room_list import RoomListHandler
from synapse.handlers.room_member import RoomMemberHandler, RoomMemberMasterHandler
from synapse.handlers.room_member_worker import RoomMemberWorkerHandler
from synapse.handlers.room_summary import RoomSummaryHandler
from synapse.handlers.search import SearchHandler
from synapse.handlers.send_email import SendEmailHandler
from synapse.handlers.set_password import SetPasswordHandler
from synapse.handlers.space_summary import SpaceSummaryHandler
from synapse.handlers.sso import SsoHandler
from synapse.handlers.stats import StatsHandler
from synapse.handlers.sync import SyncHandler
@ -772,8 +772,8 @@ class HomeServer(metaclass=abc.ABCMeta):
return AccountDataHandler(self)
@cache_in_self
def get_space_summary_handler(self) -> SpaceSummaryHandler:
return SpaceSummaryHandler(self)
def get_room_summary_handler(self) -> RoomSummaryHandler:
return RoomSummaryHandler(self)
@cache_in_self
def get_event_auth_handler(self) -> EventAuthHandler:

View file

@ -23,10 +23,10 @@ from synapse.api.constants import (
RestrictedJoinRuleTypes,
RoomTypes,
)
from synapse.api.errors import AuthError, SynapseError
from synapse.api.errors import AuthError, NotFoundError, SynapseError
from synapse.api.room_versions import RoomVersions
from synapse.events import make_event_from_dict
from synapse.handlers.space_summary import _child_events_comparison_key, _RoomEntry
from synapse.handlers.room_summary import _child_events_comparison_key, _RoomEntry
from synapse.rest import admin
from synapse.rest.client.v1 import login, room
from synapse.server import HomeServer
@ -106,7 +106,7 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
def prepare(self, reactor, clock, hs: HomeServer):
self.hs = hs
self.handler = self.hs.get_space_summary_handler()
self.handler = self.hs.get_room_summary_handler()
# Create a user.
self.user = self.register_user("user", "pass")
@ -624,14 +624,14 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
),
]
async def summarize_remote_room_hiearchy(_self, room, suggested_only):
async def summarize_remote_room_hierarchy(_self, room, suggested_only):
return requested_room_entry, {subroom: child_room}, set()
# Add a room to the space which is on another server.
self._add_child(self.space, subspace, self.token)
with mock.patch(
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room",
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room",
new=summarize_remote_room,
):
result = self.get_success(
@ -647,8 +647,8 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
self._assert_rooms(result, expected)
with mock.patch(
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room_hiearchy",
new=summarize_remote_room_hiearchy,
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room_hierarchy",
new=summarize_remote_room_hierarchy,
):
result = self.get_success(
self.handler.get_room_hierarchy(self.user, self.space)
@ -774,14 +774,14 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
for child_room in children_rooms
]
async def summarize_remote_room_hiearchy(_self, room, suggested_only):
async def summarize_remote_room_hierarchy(_self, room, suggested_only):
return subspace_room_entry, dict(children_rooms), set()
# Add a room to the space which is on another server.
self._add_child(self.space, subspace, self.token)
with mock.patch(
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room",
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room",
new=summarize_remote_room,
):
result = self.get_success(
@ -814,8 +814,8 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
self._assert_rooms(result, expected)
with mock.patch(
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room_hiearchy",
new=summarize_remote_room_hiearchy,
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room_hierarchy",
new=summarize_remote_room_hierarchy,
):
result = self.get_success(
self.handler.get_room_hierarchy(self.user, self.space)
@ -850,14 +850,14 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
):
return [fed_room_entry]
async def summarize_remote_room_hiearchy(_self, room, suggested_only):
async def summarize_remote_room_hierarchy(_self, room, suggested_only):
return fed_room_entry, {}, set()
# Add a room to the space which is on another server.
self._add_child(self.space, fed_room, self.token)
with mock.patch(
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room",
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room",
new=summarize_remote_room,
):
result = self.get_success(
@ -872,10 +872,88 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
self._assert_rooms(result, expected)
with mock.patch(
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room_hiearchy",
new=summarize_remote_room_hiearchy,
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room_hierarchy",
new=summarize_remote_room_hierarchy,
):
result = self.get_success(
self.handler.get_room_hierarchy(self.user, self.space)
)
self._assert_hierarchy(result, expected)
class RoomSummaryTestCase(unittest.HomeserverTestCase):
servlets = [
admin.register_servlets_for_client_rest_resource,
room.register_servlets,
login.register_servlets,
]
def prepare(self, reactor, clock, hs: HomeServer):
self.hs = hs
self.handler = self.hs.get_room_summary_handler()
# Create a user.
self.user = self.register_user("user", "pass")
self.token = self.login("user", "pass")
# Create a simple room.
self.room = self.helper.create_room_as(self.user, tok=self.token)
self.helper.send_state(
self.room,
event_type=EventTypes.JoinRules,
body={"join_rule": JoinRules.INVITE},
tok=self.token,
)
def test_own_room(self):
"""Test a simple room created by the requester."""
result = self.get_success(self.handler.get_room_summary(self.user, self.room))
self.assertEqual(result.get("room_id"), self.room)
def test_visibility(self):
"""A user not in a private room cannot get its summary."""
user2 = self.register_user("user2", "pass")
token2 = self.login("user2", "pass")
# The user cannot see the room.
self.get_failure(self.handler.get_room_summary(user2, self.room), NotFoundError)
# If the room is made world-readable it should return a result.
self.helper.send_state(
self.room,
event_type=EventTypes.RoomHistoryVisibility,
body={"history_visibility": HistoryVisibility.WORLD_READABLE},
tok=self.token,
)
result = self.get_success(self.handler.get_room_summary(user2, self.room))
self.assertEqual(result.get("room_id"), self.room)
# Make it not world-readable again and confirm it results in an error.
self.helper.send_state(
self.room,
event_type=EventTypes.RoomHistoryVisibility,
body={"history_visibility": HistoryVisibility.JOINED},
tok=self.token,
)
self.get_failure(self.handler.get_room_summary(user2, self.room), NotFoundError)
# If the room is made public it should return a result.
self.helper.send_state(
self.room,
event_type=EventTypes.JoinRules,
body={"join_rule": JoinRules.PUBLIC},
tok=self.token,
)
result = self.get_success(self.handler.get_room_summary(user2, self.room))
self.assertEqual(result.get("room_id"), self.room)
# Join the space, make it invite-only again and results should be returned.
self.helper.join(self.room, user2, tok=token2)
self.helper.send_state(
self.room,
event_type=EventTypes.JoinRules,
body={"join_rule": JoinRules.INVITE},
tok=self.token,
)
result = self.get_success(self.handler.get_room_summary(user2, self.room))
self.assertEqual(result.get("room_id"), self.room)