summary refs log tree commit diff
path: root/tests/rest/admin
diff options
context:
space:
mode:
authorPatrick Cloke <patrickc@matrix.org>2020-05-19 09:55:39 -0400
committerPatrick Cloke <patrickc@matrix.org>2020-05-19 09:55:39 -0400
commit13a82768ac4fdc1f5a24da9be5e51c5065382596 (patch)
treed14b2100876cde31e677d4f8a0db5036c882fcbe /tests/rest/admin
parentremove spurious changelog files (diff)
parentUpdate changelog based on feedback. (diff)
downloadsynapse-13a82768ac4fdc1f5a24da9be5e51c5065382596.tar.xz
Merge tag 'v1.13.0'
Synapse 1.13.0 (2020-05-19)
===========================

This release brings some potential changes necessary for certain
configurations of Synapse:

* If your Synapse is configured to use SSO and have a custom
  `sso_redirect_confirm_template_dir` configuration option set, you will need
  to duplicate the new `sso_auth_confirm.html`, `sso_auth_success.html` and
  `sso_account_deactivated.html` templates into that directory.
* Synapse plugins using the `complete_sso_login` method of
  `synapse.module_api.ModuleApi` should instead switch to the async/await
  version, `complete_sso_login_async`, which includes additional checks. The
  former version is now deprecated.
* A bug was introduced in Synapse 1.4.0 which could cause the room directory
  to be incomplete or empty if Synapse was upgraded directly from v1.2.1 or
  earlier, to versions between v1.4.0 and v1.12.x.

Please review [UPGRADE.rst](https://github.com/matrix-org/synapse/blob/master/UPGRADE.rst)
for more details on these changes and for general upgrade guidance.

Notice of change to the default `git` branch for Synapse
--------------------------------------------------------

With the release of Synapse 1.13.0, the default `git` branch for Synapse has
changed to `develop`, which is the development tip. This is more consistent with
common practice and modern `git` usage.

The `master` branch, which tracks the latest release, is still available. It is
recommended that developers and distributors who have scripts which run builds
using the default branch of Synapse should therefore consider pinning their
scripts to `master`.

Features
--------

- Extend the `web_client_location` option to accept an absolute URL to use as a redirect. Adds a warning when running the web client on the same hostname as homeserver. Contributed by Martin Milata. ([\#7006](https://github.com/matrix-org/synapse/issues/7006))
- Set `Referrer-Policy` header to `no-referrer` on media downloads. ([\#7009](https://github.com/matrix-org/synapse/issues/7009))
- Add support for running replication over Redis when using workers. ([\#7040](https://github.com/matrix-org/synapse/issues/7040), [\#7325](https://github.com/matrix-org/synapse/issues/7325), [\#7352](https://github.com/matrix-org/synapse/issues/7352), [\#7401](https://github.com/matrix-org/synapse/issues/7401), [\#7427](https://github.com/matrix-org/synapse/issues/7427), [\#7439](https://github.com/matrix-org/synapse/issues/7439), [\#7446](https://github.com/matrix-org/synapse/issues/7446), [\#7450](https://github.com/matrix-org/synapse/issues/7450), [\#7454](https://github.com/matrix-org/synapse/issues/7454))
- Admin API `POST /_synapse/admin/v1/join/<roomIdOrAlias>` to join users to a room like `auto_join_rooms` for creation of users. ([\#7051](https://github.com/matrix-org/synapse/issues/7051))
- Add options to prevent users from changing their profile or associated 3PIDs. ([\#7096](https://github.com/matrix-org/synapse/issues/7096))
- Support SSO in the user interactive authentication workflow. ([\#7102](https://github.com/matrix-org/synapse/issues/7102), [\#7186](https://github.com/matrix-org/synapse/issues/7186), [\#7279](https://github.com/matrix-org/synapse/issues/7279), [\#7343](https://github.com/matrix-org/synapse/issues/7343))
- Allow server admins to define and enforce a password policy ([MSC2000](https://github.com/matrix-org/matrix-doc/issues/2000)). ([\#7118](https://github.com/matrix-org/synapse/issues/7118))
- Improve the support for SSO authentication on the login fallback page. ([\#7152](https://github.com/matrix-org/synapse/issues/7152), [\#7235](https://github.com/matrix-org/synapse/issues/7235))
- Always whitelist the login fallback in the SSO configuration if `public_baseurl` is set. ([\#7153](https://github.com/matrix-org/synapse/issues/7153))
- Admin users are no longer required to be in a room to create an alias for it. ([\#7191](https://github.com/matrix-org/synapse/issues/7191))
- Require admin privileges to enable room encryption by default. This does not affect existing rooms. ([\#7230](https://github.com/matrix-org/synapse/issues/7230))
- Add a config option for specifying the value of the Accept-Language HTTP header when generating URL previews. ([\#7265](https://github.com/matrix-org/synapse/issues/7265))
- Allow `/requestToken` endpoints to hide the existence (or lack thereof) of 3PID associations on the homeserver. ([\#7315](https://github.com/matrix-org/synapse/issues/7315))
- Add a configuration setting to tweak the threshold for dummy events. ([\#7422](https://github.com/matrix-org/synapse/issues/7422))

Bugfixes
--------

- Don't attempt to use an invalid sqlite config if no database configuration is provided. Contributed by @nekatak. ([\#6573](https://github.com/matrix-org/synapse/issues/6573))
- Fix single-sign on with CAS systems: pass the same service URL when requesting the CAS ticket and when calling the `proxyValidate` URL. Contributed by @Naugrimm. ([\#6634](https://github.com/matrix-org/synapse/issues/6634))
- Fix missing field `default` when fetching user-defined push rules. ([\#6639](https://github.com/matrix-org/synapse/issues/6639))
- Improve error responses when accessing remote public room lists. ([\#6899](https://github.com/matrix-org/synapse/issues/6899), [\#7368](https://github.com/matrix-org/synapse/issues/7368))
- Transfer alias mappings on room upgrade. ([\#6946](https://github.com/matrix-org/synapse/issues/6946))
- Ensure that a user interactive authentication session is tied to a single request. ([\#7068](https://github.com/matrix-org/synapse/issues/7068), [\#7455](https://github.com/matrix-org/synapse/issues/7455))
- Fix a bug in the federation API which could cause occasional "Failed to get PDU" errors. ([\#7089](https://github.com/matrix-org/synapse/issues/7089))
- Return the proper error (`M_BAD_ALIAS`) when a non-existant canonical alias is provided. ([\#7109](https://github.com/matrix-org/synapse/issues/7109))
- Fix a bug which meant that groups updates were not correctly replicated between workers. ([\#7117](https://github.com/matrix-org/synapse/issues/7117))
- Fix starting workers when federation sending not split out. ([\#7133](https://github.com/matrix-org/synapse/issues/7133))
- Ensure `is_verified` is a boolean in responses to `GET /_matrix/client/r0/room_keys/keys`. Also warn the user if they forgot the `version` query param. ([\#7150](https://github.com/matrix-org/synapse/issues/7150))
- Fix error page being shown when a custom SAML handler attempted to redirect when processing an auth response. ([\#7151](https://github.com/matrix-org/synapse/issues/7151))
- Avoid importing `sqlite3` when using the postgres backend. Contributed by David Vo. ([\#7155](https://github.com/matrix-org/synapse/issues/7155))
- Fix excessive CPU usage by `prune_old_outbound_device_pokes` job. ([\#7159](https://github.com/matrix-org/synapse/issues/7159))
- Fix a bug which could cause outbound federation traffic to stop working if a client uploaded an incorrect e2e device signature. ([\#7177](https://github.com/matrix-org/synapse/issues/7177))
- Fix a bug which could cause incorrect 'cyclic dependency' error. ([\#7178](https://github.com/matrix-org/synapse/issues/7178))
- Fix a bug that could cause a user to be invited to a server notices (aka System Alerts) room without any notice being sent. ([\#7199](https://github.com/matrix-org/synapse/issues/7199))
- Fix some worker-mode replication handling not being correctly recorded in CPU usage stats. ([\#7203](https://github.com/matrix-org/synapse/issues/7203))
- Do not allow a deactivated user to login via SSO. ([\#7240](https://github.com/matrix-org/synapse/issues/7240), [\#7259](https://github.com/matrix-org/synapse/issues/7259))
- Fix --help command-line argument. ([\#7249](https://github.com/matrix-org/synapse/issues/7249))
- Fix room publish permissions not being checked on room creation. ([\#7260](https://github.com/matrix-org/synapse/issues/7260))
- Reject unknown session IDs during user interactive authentication instead of silently creating a new session. ([\#7268](https://github.com/matrix-org/synapse/issues/7268))
- Fix a SQL query introduced in Synapse 1.12.0 which could cause large amounts of logging to the postgres slow-query log. ([\#7274](https://github.com/matrix-org/synapse/issues/7274))
- Persist user interactive authentication sessions across workers and Synapse restarts. ([\#7302](https://github.com/matrix-org/synapse/issues/7302))
- Fixed backwards compatibility logic of the first value of `trusted_third_party_id_servers` being used for `account_threepid_delegates.email`, which occurs when the former, deprecated option is set and the latter is not. ([\#7316](https://github.com/matrix-org/synapse/issues/7316))
- Fix a bug where event updates might not be sent over replication to worker processes after the stream falls behind. ([\#7337](https://github.com/matrix-org/synapse/issues/7337), [\#7358](https://github.com/matrix-org/synapse/issues/7358))
- Fix bad error handling that would cause Synapse to crash if it's provided with a YAML configuration file that's either empty or doesn't parse into a key-value map. ([\#7341](https://github.com/matrix-org/synapse/issues/7341))
- Fix incorrect metrics reporting for `renew_attestations` background task. ([\#7344](https://github.com/matrix-org/synapse/issues/7344))
- Prevent non-federating rooms from appearing in responses to federated `POST /publicRoom` requests when a filter was included. ([\#7367](https://github.com/matrix-org/synapse/issues/7367))
- Fix a bug which would cause the room durectory to be incorrectly populated if Synapse was upgraded directly from v1.2.1 or earlier to v1.4.0 or later. Note that this fix does not apply retrospectively; see the [upgrade notes](UPGRADE.rst#upgrading-to-v1130) for more information. ([\#7387](https://github.com/matrix-org/synapse/issues/7387))
- Fix bug in `EventContext.deserialize`. ([\#7393](https://github.com/matrix-org/synapse/issues/7393))
- Fix a long-standing bug which could cause messages not to be sent over federation, when state events with state keys matching user IDs (such as custom user statuses) were received. ([\#7376](https://github.com/matrix-org/synapse/issues/7376))
- Restore compatibility with non-compliant clients during the user interactive authentication process, fixing a problem introduced in v1.13.0rc1. ([\#7483](https://github.com/matrix-org/synapse/issues/7483))
- Hash passwords as early as possible during registration. ([\#7523](https://github.com/matrix-org/synapse/issues/7523))

Improved Documentation
----------------------

- Update Debian installation instructions to recommend installing the `virtualenv` package instead of `python3-virtualenv`. ([\#6892](https://github.com/matrix-org/synapse/issues/6892))
- Improve the documentation for database configuration. ([\#6988](https://github.com/matrix-org/synapse/issues/6988))
- Improve the documentation of application service configuration files. ([\#7091](https://github.com/matrix-org/synapse/issues/7091))
- Update pre-built package name for FreeBSD. ([\#7107](https://github.com/matrix-org/synapse/issues/7107))
- Update postgres docs with login troubleshooting information. ([\#7119](https://github.com/matrix-org/synapse/issues/7119))
- Clean up INSTALL.md a bit. ([\#7141](https://github.com/matrix-org/synapse/issues/7141))
- Add documentation for running a local CAS server for testing. ([\#7147](https://github.com/matrix-org/synapse/issues/7147))
- Improve README.md by being explicit about public IP recommendation for TURN relaying. ([\#7167](https://github.com/matrix-org/synapse/issues/7167))
- Fix a small typo in the `metrics_flags` config option. ([\#7171](https://github.com/matrix-org/synapse/issues/7171))
- Update the contributed documentation on managing synapse workers with systemd, and bring it into the core distribution. ([\#7234](https://github.com/matrix-org/synapse/issues/7234))
- Add documentation to the `password_providers` config option. Add known password provider implementations to docs. ([\#7238](https://github.com/matrix-org/synapse/issues/7238), [\#7248](https://github.com/matrix-org/synapse/issues/7248))
- Modify suggested nginx reverse proxy configuration to match Synapse's default file upload size. Contributed by @ProCycleDev. ([\#7251](https://github.com/matrix-org/synapse/issues/7251))
- Documentation of media_storage_providers options updated to avoid misunderstandings. Contributed by Tristan Lins. ([\#7272](https://github.com/matrix-org/synapse/issues/7272))
- Add documentation on monitoring workers with Prometheus. ([\#7357](https://github.com/matrix-org/synapse/issues/7357))
- Clarify endpoint usage in the users admin api documentation. ([\#7361](https://github.com/matrix-org/synapse/issues/7361))

Deprecations and Removals
-------------------------

- Remove nonfunctional `captcha_bypass_secret` option from `homeserver.yaml`. ([\#7137](https://github.com/matrix-org/synapse/issues/7137))

Internal Changes
----------------

- Add benchmarks for LruCache. ([\#6446](https://github.com/matrix-org/synapse/issues/6446))
- Return total number of users and profile attributes in admin users endpoint. Contributed by Awesome Technologies Innovationslabor GmbH. ([\#6881](https://github.com/matrix-org/synapse/issues/6881))
- Change device list streams to have one row per ID. ([\#7010](https://github.com/matrix-org/synapse/issues/7010))
- Remove concept of a non-limited stream. ([\#7011](https://github.com/matrix-org/synapse/issues/7011))
- Move catchup of replication streams logic to worker. ([\#7024](https://github.com/matrix-org/synapse/issues/7024), [\#7195](https://github.com/matrix-org/synapse/issues/7195), [\#7226](https://github.com/matrix-org/synapse/issues/7226), [\#7239](https://github.com/matrix-org/synapse/issues/7239), [\#7286](https://github.com/matrix-org/synapse/issues/7286), [\#7290](https://github.com/matrix-org/synapse/issues/7290), [\#7318](https://github.com/matrix-org/synapse/issues/7318), [\#7326](https://github.com/matrix-org/synapse/issues/7326), [\#7378](https://github.com/matrix-org/synapse/issues/7378), [\#7421](https://github.com/matrix-org/synapse/issues/7421))
- Convert some of synapse.rest.media to async/await. ([\#7110](https://github.com/matrix-org/synapse/issues/7110), [\#7184](https://github.com/matrix-org/synapse/issues/7184), [\#7241](https://github.com/matrix-org/synapse/issues/7241))
- De-duplicate / remove unused REST code for login and auth. ([\#7115](https://github.com/matrix-org/synapse/issues/7115))
- Convert `*StreamRow` classes to inner classes. ([\#7116](https://github.com/matrix-org/synapse/issues/7116))
- Clean up some LoggingContext code. ([\#7120](https://github.com/matrix-org/synapse/issues/7120), [\#7181](https://github.com/matrix-org/synapse/issues/7181), [\#7183](https://github.com/matrix-org/synapse/issues/7183), [\#7408](https://github.com/matrix-org/synapse/issues/7408), [\#7426](https://github.com/matrix-org/synapse/issues/7426))
- Add explicit `instance_id` for USER_SYNC commands and remove implicit `conn_id` usage. ([\#7128](https://github.com/matrix-org/synapse/issues/7128))
- Refactored the CAS authentication logic to a separate class. ([\#7136](https://github.com/matrix-org/synapse/issues/7136))
- Run replication streamers on workers. ([\#7146](https://github.com/matrix-org/synapse/issues/7146))
- Add tests for outbound device pokes. ([\#7157](https://github.com/matrix-org/synapse/issues/7157))
- Fix device list update stream ids going backward. ([\#7158](https://github.com/matrix-org/synapse/issues/7158))
- Use `stream.current_token()` and remove `stream_positions()`. ([\#7172](https://github.com/matrix-org/synapse/issues/7172))
- Move client command handling out of TCP protocol. ([\#7185](https://github.com/matrix-org/synapse/issues/7185))
- Move server command handling out of TCP protocol. ([\#7187](https://github.com/matrix-org/synapse/issues/7187))
- Fix consistency of HTTP status codes reported in log lines. ([\#7188](https://github.com/matrix-org/synapse/issues/7188))
- Only run one background database update at a time. ([\#7190](https://github.com/matrix-org/synapse/issues/7190))
- Remove sent outbound device list pokes from the database. ([\#7192](https://github.com/matrix-org/synapse/issues/7192))
- Add a background database update job to clear out duplicate `device_lists_outbound_pokes`. ([\#7193](https://github.com/matrix-org/synapse/issues/7193))
- Remove some extraneous debugging log lines. ([\#7207](https://github.com/matrix-org/synapse/issues/7207))
- Add explicit Python build tooling as dependencies for the snapcraft build. ([\#7213](https://github.com/matrix-org/synapse/issues/7213))
- Add typing information to federation server code. ([\#7219](https://github.com/matrix-org/synapse/issues/7219))
- Extend room admin api (`GET /_synapse/admin/v1/rooms`) with additional attributes. ([\#7225](https://github.com/matrix-org/synapse/issues/7225))
- Unblacklist '/upgrade creates a new room' sytest for workers. ([\#7228](https://github.com/matrix-org/synapse/issues/7228))
- Remove redundant checks on `daemonize` from synctl. ([\#7233](https://github.com/matrix-org/synapse/issues/7233))
- Upgrade jQuery to v3.4.1 on fallback login/registration pages. ([\#7236](https://github.com/matrix-org/synapse/issues/7236))
- Change log line that told user to implement onLogin/onRegister fallback js functions to a warning, instead of an info, so it's more visible. ([\#7237](https://github.com/matrix-org/synapse/issues/7237))
- Correct the parameters of a test fixture. Contributed by Isaiah Singletary. ([\#7243](https://github.com/matrix-org/synapse/issues/7243))
- Convert auth handler to async/await. ([\#7261](https://github.com/matrix-org/synapse/issues/7261))
- Add some unit tests for replication. ([\#7278](https://github.com/matrix-org/synapse/issues/7278))
- Improve typing annotations in `synapse.replication.tcp.streams.Stream`. ([\#7291](https://github.com/matrix-org/synapse/issues/7291))
- Reduce log verbosity of url cache cleanup tasks. ([\#7295](https://github.com/matrix-org/synapse/issues/7295))
- Fix sample SAML Service Provider configuration. Contributed by @frcl. ([\#7300](https://github.com/matrix-org/synapse/issues/7300))
- Fix StreamChangeCache to work with multiple entities changing on the same stream id. ([\#7303](https://github.com/matrix-org/synapse/issues/7303))
- Fix an incorrect import in IdentityHandler. ([\#7319](https://github.com/matrix-org/synapse/issues/7319))
- Reduce logging verbosity for successful federation requests. ([\#7321](https://github.com/matrix-org/synapse/issues/7321))
- Convert some federation handler code to async/await. ([\#7338](https://github.com/matrix-org/synapse/issues/7338))
- Fix collation for postgres for unit tests. ([\#7359](https://github.com/matrix-org/synapse/issues/7359))
- Convert RegistrationWorkerStore.is_server_admin and dependent code to async/await. ([\#7363](https://github.com/matrix-org/synapse/issues/7363))
- Add an `instance_name` to `RDATA` and `POSITION` replication commands. ([\#7364](https://github.com/matrix-org/synapse/issues/7364))
- Thread through instance name to replication client. ([\#7369](https://github.com/matrix-org/synapse/issues/7369))
- Convert synapse.server_notices to async/await. ([\#7394](https://github.com/matrix-org/synapse/issues/7394))
- Convert synapse.notifier to async/await. ([\#7395](https://github.com/matrix-org/synapse/issues/7395))
- Fix issues with the Python package manifest. ([\#7404](https://github.com/matrix-org/synapse/issues/7404))
- Prevent methods in `synapse.handlers.auth` from polling the homeserver config every request. ([\#7420](https://github.com/matrix-org/synapse/issues/7420))
- Speed up fetching device lists changes when handling `/sync` requests. ([\#7423](https://github.com/matrix-org/synapse/issues/7423))
- Run group attestation renewal in series rather than parallel for performance. ([\#7442](https://github.com/matrix-org/synapse/issues/7442))
- Fix linting errors in new version of Flake8. ([\#7470](https://github.com/matrix-org/synapse/issues/7470))
- Update the version of dh-virtualenv we use to build debs, and add focal to the list of target distributions. ([\#7526](https://github.com/matrix-org/synapse/issues/7526))
Diffstat (limited to 'tests/rest/admin')
-rw-r--r--tests/rest/admin/test_admin.py592
-rw-r--r--tests/rest/admin/test_room.py966
-rw-r--r--tests/rest/admin/test_user.py2
3 files changed, 969 insertions, 591 deletions
diff --git a/tests/rest/admin/test_admin.py b/tests/rest/admin/test_admin.py
index 0342aed416..977615ebef 100644
--- a/tests/rest/admin/test_admin.py
+++ b/tests/rest/admin/test_admin.py
@@ -17,7 +17,6 @@ import json
 import os
 import urllib.parse
 from binascii import unhexlify
-from typing import List, Optional
 
 from mock import Mock
 
@@ -27,7 +26,7 @@ import synapse.rest.admin
 from synapse.http.server import JsonResource
 from synapse.logging.context import make_deferred_yieldable
 from synapse.rest.admin import VersionServlet
-from synapse.rest.client.v1 import directory, events, login, room
+from synapse.rest.client.v1 import login, room
 from synapse.rest.client.v2_alpha import groups
 
 from tests import unittest
@@ -51,129 +50,6 @@ class VersionTestCase(unittest.HomeserverTestCase):
         )
 
 
-class ShutdownRoomTestCase(unittest.HomeserverTestCase):
-    servlets = [
-        synapse.rest.admin.register_servlets_for_client_rest_resource,
-        login.register_servlets,
-        events.register_servlets,
-        room.register_servlets,
-        room.register_deprecated_servlets,
-    ]
-
-    def prepare(self, reactor, clock, hs):
-        self.event_creation_handler = hs.get_event_creation_handler()
-        hs.config.user_consent_version = "1"
-
-        consent_uri_builder = Mock()
-        consent_uri_builder.build_user_consent_uri.return_value = "http://example.com"
-        self.event_creation_handler._consent_uri_builder = consent_uri_builder
-
-        self.store = hs.get_datastore()
-
-        self.admin_user = self.register_user("admin", "pass", admin=True)
-        self.admin_user_tok = self.login("admin", "pass")
-
-        self.other_user = self.register_user("user", "pass")
-        self.other_user_token = self.login("user", "pass")
-
-        # Mark the admin user as having consented
-        self.get_success(self.store.user_set_consent_version(self.admin_user, "1"))
-
-    def test_shutdown_room_consent(self):
-        """Test that we can shutdown rooms with local users who have not
-        yet accepted the privacy policy. This used to fail when we tried to
-        force part the user from the old room.
-        """
-        self.event_creation_handler._block_events_without_consent_error = None
-
-        room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_token)
-
-        # Assert one user in room
-        users_in_room = self.get_success(self.store.get_users_in_room(room_id))
-        self.assertEqual([self.other_user], users_in_room)
-
-        # Enable require consent to send events
-        self.event_creation_handler._block_events_without_consent_error = "Error"
-
-        # Assert that the user is getting consent error
-        self.helper.send(
-            room_id, body="foo", tok=self.other_user_token, expect_code=403
-        )
-
-        # Test that the admin can still send shutdown
-        url = "admin/shutdown_room/" + room_id
-        request, channel = self.make_request(
-            "POST",
-            url.encode("ascii"),
-            json.dumps({"new_room_user_id": self.admin_user}),
-            access_token=self.admin_user_tok,
-        )
-        self.render(request)
-
-        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
-
-        # Assert there is now no longer anyone in the room
-        users_in_room = self.get_success(self.store.get_users_in_room(room_id))
-        self.assertEqual([], users_in_room)
-
-    def test_shutdown_room_block_peek(self):
-        """Test that a world_readable room can no longer be peeked into after
-        it has been shut down.
-        """
-
-        self.event_creation_handler._block_events_without_consent_error = None
-
-        room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_token)
-
-        # Enable world readable
-        url = "rooms/%s/state/m.room.history_visibility" % (room_id,)
-        request, channel = self.make_request(
-            "PUT",
-            url.encode("ascii"),
-            json.dumps({"history_visibility": "world_readable"}),
-            access_token=self.other_user_token,
-        )
-        self.render(request)
-        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
-
-        # Test that the admin can still send shutdown
-        url = "admin/shutdown_room/" + room_id
-        request, channel = self.make_request(
-            "POST",
-            url.encode("ascii"),
-            json.dumps({"new_room_user_id": self.admin_user}),
-            access_token=self.admin_user_tok,
-        )
-        self.render(request)
-
-        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
-
-        # Assert we can no longer peek into the room
-        self._assert_peek(room_id, expect_code=403)
-
-    def _assert_peek(self, room_id, expect_code):
-        """Assert that the admin user can (or cannot) peek into the room.
-        """
-
-        url = "rooms/%s/initialSync" % (room_id,)
-        request, channel = self.make_request(
-            "GET", url.encode("ascii"), access_token=self.admin_user_tok
-        )
-        self.render(request)
-        self.assertEqual(
-            expect_code, int(channel.result["code"]), msg=channel.result["body"]
-        )
-
-        url = "events?timeout=0&room_id=" + room_id
-        request, channel = self.make_request(
-            "GET", url.encode("ascii"), access_token=self.admin_user_tok
-        )
-        self.render(request)
-        self.assertEqual(
-            expect_code, int(channel.result["code"]), msg=channel.result["body"]
-        )
-
-
 class DeleteGroupTestCase(unittest.HomeserverTestCase):
     servlets = [
         synapse.rest.admin.register_servlets_for_client_rest_resource,
@@ -273,86 +149,6 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
         return channel.json_body["groups"]
 
 
-class PurgeRoomTestCase(unittest.HomeserverTestCase):
-    """Test /purge_room admin API.
-    """
-
-    servlets = [
-        synapse.rest.admin.register_servlets,
-        login.register_servlets,
-        room.register_servlets,
-    ]
-
-    def prepare(self, reactor, clock, hs):
-        self.store = hs.get_datastore()
-
-        self.admin_user = self.register_user("admin", "pass", admin=True)
-        self.admin_user_tok = self.login("admin", "pass")
-
-    def test_purge_room(self):
-        room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
-
-        # All users have to have left the room.
-        self.helper.leave(room_id, user=self.admin_user, tok=self.admin_user_tok)
-
-        url = "/_synapse/admin/v1/purge_room"
-        request, channel = self.make_request(
-            "POST",
-            url.encode("ascii"),
-            {"room_id": room_id},
-            access_token=self.admin_user_tok,
-        )
-        self.render(request)
-
-        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
-
-        # Test that the following tables have been purged of all rows related to the room.
-        for table in (
-            "current_state_events",
-            "event_backward_extremities",
-            "event_forward_extremities",
-            "event_json",
-            "event_push_actions",
-            "event_search",
-            "events",
-            "group_rooms",
-            "public_room_list_stream",
-            "receipts_graph",
-            "receipts_linearized",
-            "room_aliases",
-            "room_depth",
-            "room_memberships",
-            "room_stats_state",
-            "room_stats_current",
-            "room_stats_historical",
-            "room_stats_earliest_token",
-            "rooms",
-            "stream_ordering_to_exterm",
-            "users_in_public_rooms",
-            "users_who_share_private_rooms",
-            "appservice_room_list",
-            "e2e_room_keys",
-            "event_push_summary",
-            "pusher_throttle",
-            "group_summary_rooms",
-            "local_invites",
-            "room_account_data",
-            "room_tags",
-            # "state_groups",  # Current impl leaves orphaned state groups around.
-            "state_groups_state",
-        ):
-            count = self.get_success(
-                self.store.db.simple_select_one_onecol(
-                    table=table,
-                    keyvalues={"room_id": room_id},
-                    retcol="COUNT(*)",
-                    desc="test_purge_room",
-                )
-            )
-
-            self.assertEqual(count, 0, msg="Rows not purged in {}".format(table))
-
-
 class QuarantineMediaTestCase(unittest.HomeserverTestCase):
     """Test /quarantine_media admin API.
     """
@@ -691,389 +487,3 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
                 % server_and_media_id_2
             ),
         )
-
-
-class RoomTestCase(unittest.HomeserverTestCase):
-    """Test /room admin API.
-    """
-
-    servlets = [
-        synapse.rest.admin.register_servlets,
-        login.register_servlets,
-        room.register_servlets,
-        directory.register_servlets,
-    ]
-
-    def prepare(self, reactor, clock, hs):
-        self.store = hs.get_datastore()
-
-        # Create user
-        self.admin_user = self.register_user("admin", "pass", admin=True)
-        self.admin_user_tok = self.login("admin", "pass")
-
-    def test_list_rooms(self):
-        """Test that we can list rooms"""
-        # Create 3 test rooms
-        total_rooms = 3
-        room_ids = []
-        for x in range(total_rooms):
-            room_id = self.helper.create_room_as(
-                self.admin_user, tok=self.admin_user_tok
-            )
-            room_ids.append(room_id)
-
-        # Request the list of rooms
-        url = "/_synapse/admin/v1/rooms"
-        request, channel = self.make_request(
-            "GET", url.encode("ascii"), access_token=self.admin_user_tok,
-        )
-        self.render(request)
-
-        # Check request completed successfully
-        self.assertEqual(200, int(channel.code), msg=channel.json_body)
-
-        # Check that response json body contains a "rooms" key
-        self.assertTrue(
-            "rooms" in channel.json_body,
-            msg="Response body does not " "contain a 'rooms' key",
-        )
-
-        # Check that 3 rooms were returned
-        self.assertEqual(3, len(channel.json_body["rooms"]), msg=channel.json_body)
-
-        # Check their room_ids match
-        returned_room_ids = [room["room_id"] for room in channel.json_body["rooms"]]
-        self.assertEqual(room_ids, returned_room_ids)
-
-        # Check that all fields are available
-        for r in channel.json_body["rooms"]:
-            self.assertIn("name", r)
-            self.assertIn("canonical_alias", r)
-            self.assertIn("joined_members", r)
-
-        # Check that the correct number of total rooms was returned
-        self.assertEqual(channel.json_body["total_rooms"], total_rooms)
-
-        # Check that the offset is correct
-        # Should be 0 as we aren't paginating
-        self.assertEqual(channel.json_body["offset"], 0)
-
-        # Check that the prev_batch parameter is not present
-        self.assertNotIn("prev_batch", channel.json_body)
-
-        # We shouldn't receive a next token here as there's no further rooms to show
-        self.assertNotIn("next_batch", channel.json_body)
-
-    def test_list_rooms_pagination(self):
-        """Test that we can get a full list of rooms through pagination"""
-        # Create 5 test rooms
-        total_rooms = 5
-        room_ids = []
-        for x in range(total_rooms):
-            room_id = self.helper.create_room_as(
-                self.admin_user, tok=self.admin_user_tok
-            )
-            room_ids.append(room_id)
-
-        # Set the name of the rooms so we get a consistent returned ordering
-        for idx, room_id in enumerate(room_ids):
-            self.helper.send_state(
-                room_id, "m.room.name", {"name": str(idx)}, tok=self.admin_user_tok,
-            )
-
-        # Request the list of rooms
-        returned_room_ids = []
-        start = 0
-        limit = 2
-
-        run_count = 0
-        should_repeat = True
-        while should_repeat:
-            run_count += 1
-
-            url = "/_synapse/admin/v1/rooms?from=%d&limit=%d&order_by=%s" % (
-                start,
-                limit,
-                "alphabetical",
-            )
-            request, channel = self.make_request(
-                "GET", url.encode("ascii"), access_token=self.admin_user_tok,
-            )
-            self.render(request)
-            self.assertEqual(
-                200, int(channel.result["code"]), msg=channel.result["body"]
-            )
-
-            self.assertTrue("rooms" in channel.json_body)
-            for r in channel.json_body["rooms"]:
-                returned_room_ids.append(r["room_id"])
-
-            # Check that the correct number of total rooms was returned
-            self.assertEqual(channel.json_body["total_rooms"], total_rooms)
-
-            # Check that the offset is correct
-            # We're only getting 2 rooms each page, so should be 2 * last run_count
-            self.assertEqual(channel.json_body["offset"], 2 * (run_count - 1))
-
-            if run_count > 1:
-                # Check the value of prev_batch is correct
-                self.assertEqual(channel.json_body["prev_batch"], 2 * (run_count - 2))
-
-            if "next_batch" not in channel.json_body:
-                # We have reached the end of the list
-                should_repeat = False
-            else:
-                # Make another query with an updated start value
-                start = channel.json_body["next_batch"]
-
-        # We should've queried the endpoint 3 times
-        self.assertEqual(
-            run_count,
-            3,
-            msg="Should've queried 3 times for 5 rooms with limit 2 per query",
-        )
-
-        # Check that we received all of the room ids
-        self.assertEqual(room_ids, returned_room_ids)
-
-        url = "/_synapse/admin/v1/rooms?from=%d&limit=%d" % (start, limit)
-        request, channel = self.make_request(
-            "GET", url.encode("ascii"), access_token=self.admin_user_tok,
-        )
-        self.render(request)
-        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
-
-    def test_correct_room_attributes(self):
-        """Test the correct attributes for a room are returned"""
-        # Create a test room
-        room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
-
-        test_alias = "#test:test"
-        test_room_name = "something"
-
-        # Have another user join the room
-        user_2 = self.register_user("user4", "pass")
-        user_tok_2 = self.login("user4", "pass")
-        self.helper.join(room_id, user_2, tok=user_tok_2)
-
-        # Create a new alias to this room
-        url = "/_matrix/client/r0/directory/room/%s" % (urllib.parse.quote(test_alias),)
-        request, channel = self.make_request(
-            "PUT",
-            url.encode("ascii"),
-            {"room_id": room_id},
-            access_token=self.admin_user_tok,
-        )
-        self.render(request)
-        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
-
-        # Set this new alias as the canonical alias for this room
-        self.helper.send_state(
-            room_id,
-            "m.room.aliases",
-            {"aliases": [test_alias]},
-            tok=self.admin_user_tok,
-            state_key="test",
-        )
-        self.helper.send_state(
-            room_id,
-            "m.room.canonical_alias",
-            {"alias": test_alias},
-            tok=self.admin_user_tok,
-        )
-
-        # Set a name for the room
-        self.helper.send_state(
-            room_id, "m.room.name", {"name": test_room_name}, tok=self.admin_user_tok,
-        )
-
-        # Request the list of rooms
-        url = "/_synapse/admin/v1/rooms"
-        request, channel = self.make_request(
-            "GET", url.encode("ascii"), access_token=self.admin_user_tok,
-        )
-        self.render(request)
-        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
-
-        # Check that rooms were returned
-        self.assertTrue("rooms" in channel.json_body)
-        rooms = channel.json_body["rooms"]
-
-        # Check that only one room was returned
-        self.assertEqual(len(rooms), 1)
-
-        # And that the value of the total_rooms key was correct
-        self.assertEqual(channel.json_body["total_rooms"], 1)
-
-        # Check that the offset is correct
-        # We're not paginating, so should be 0
-        self.assertEqual(channel.json_body["offset"], 0)
-
-        # Check that there is no `prev_batch`
-        self.assertNotIn("prev_batch", channel.json_body)
-
-        # Check that there is no `next_batch`
-        self.assertNotIn("next_batch", channel.json_body)
-
-        # Check that all provided attributes are set
-        r = rooms[0]
-        self.assertEqual(room_id, r["room_id"])
-        self.assertEqual(test_room_name, r["name"])
-        self.assertEqual(test_alias, r["canonical_alias"])
-
-    def test_room_list_sort_order(self):
-        """Test room list sort ordering. alphabetical versus number of members,
-        reversing the order, etc.
-        """
-        # Create 3 test rooms
-        room_id_1 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
-        room_id_2 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
-        room_id_3 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
-
-        # Set room names in alphabetical order. room 1 -> A, 2 -> B, 3 -> C
-        self.helper.send_state(
-            room_id_1, "m.room.name", {"name": "A"}, tok=self.admin_user_tok,
-        )
-        self.helper.send_state(
-            room_id_2, "m.room.name", {"name": "B"}, tok=self.admin_user_tok,
-        )
-        self.helper.send_state(
-            room_id_3, "m.room.name", {"name": "C"}, tok=self.admin_user_tok,
-        )
-
-        # Set room member size in the reverse order. room 1 -> 1 member, 2 -> 2, 3 -> 3
-        user_1 = self.register_user("bob1", "pass")
-        user_1_tok = self.login("bob1", "pass")
-        self.helper.join(room_id_2, user_1, tok=user_1_tok)
-
-        user_2 = self.register_user("bob2", "pass")
-        user_2_tok = self.login("bob2", "pass")
-        self.helper.join(room_id_3, user_2, tok=user_2_tok)
-
-        user_3 = self.register_user("bob3", "pass")
-        user_3_tok = self.login("bob3", "pass")
-        self.helper.join(room_id_3, user_3, tok=user_3_tok)
-
-        def _order_test(
-            order_type: str, expected_room_list: List[str], reverse: bool = False,
-        ):
-            """Request the list of rooms in a certain order. Assert that order is what
-            we expect
-
-            Args:
-                order_type: The type of ordering to give the server
-                expected_room_list: The list of room_ids in the order we expect to get
-                    back from the server
-            """
-            # Request the list of rooms in the given order
-            url = "/_synapse/admin/v1/rooms?order_by=%s" % (order_type,)
-            if reverse:
-                url += "&dir=b"
-            request, channel = self.make_request(
-                "GET", url.encode("ascii"), access_token=self.admin_user_tok,
-            )
-            self.render(request)
-            self.assertEqual(200, channel.code, msg=channel.json_body)
-
-            # Check that rooms were returned
-            self.assertTrue("rooms" in channel.json_body)
-            rooms = channel.json_body["rooms"]
-
-            # Check for the correct total_rooms value
-            self.assertEqual(channel.json_body["total_rooms"], 3)
-
-            # Check that the offset is correct
-            # We're not paginating, so should be 0
-            self.assertEqual(channel.json_body["offset"], 0)
-
-            # Check that there is no `prev_batch`
-            self.assertNotIn("prev_batch", channel.json_body)
-
-            # Check that there is no `next_batch`
-            self.assertNotIn("next_batch", channel.json_body)
-
-            # Check that rooms were returned in alphabetical order
-            returned_order = [r["room_id"] for r in rooms]
-            self.assertListEqual(expected_room_list, returned_order)  # order is checked
-
-        # Test different sort orders, with forward and reverse directions
-        _order_test("alphabetical", [room_id_1, room_id_2, room_id_3])
-        _order_test("alphabetical", [room_id_3, room_id_2, room_id_1], reverse=True)
-
-        _order_test("size", [room_id_3, room_id_2, room_id_1])
-        _order_test("size", [room_id_1, room_id_2, room_id_3], reverse=True)
-
-    def test_search_term(self):
-        """Test that searching for a room works correctly"""
-        # Create two test rooms
-        room_id_1 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
-        room_id_2 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
-
-        room_name_1 = "something"
-        room_name_2 = "else"
-
-        # Set the name for each room
-        self.helper.send_state(
-            room_id_1, "m.room.name", {"name": room_name_1}, tok=self.admin_user_tok,
-        )
-        self.helper.send_state(
-            room_id_2, "m.room.name", {"name": room_name_2}, tok=self.admin_user_tok,
-        )
-
-        def _search_test(
-            expected_room_id: Optional[str],
-            search_term: str,
-            expected_http_code: int = 200,
-        ):
-            """Search for a room and check that the returned room's id is a match
-
-            Args:
-                expected_room_id: The room_id expected to be returned by the API. Set
-                    to None to expect zero results for the search
-                search_term: The term to search for room names with
-                expected_http_code: The expected http code for the request
-            """
-            url = "/_synapse/admin/v1/rooms?search_term=%s" % (search_term,)
-            request, channel = self.make_request(
-                "GET", url.encode("ascii"), access_token=self.admin_user_tok,
-            )
-            self.render(request)
-            self.assertEqual(expected_http_code, channel.code, msg=channel.json_body)
-
-            if expected_http_code != 200:
-                return
-
-            # Check that rooms were returned
-            self.assertTrue("rooms" in channel.json_body)
-            rooms = channel.json_body["rooms"]
-
-            # Check that the expected number of rooms were returned
-            expected_room_count = 1 if expected_room_id else 0
-            self.assertEqual(len(rooms), expected_room_count)
-            self.assertEqual(channel.json_body["total_rooms"], expected_room_count)
-
-            # Check that the offset is correct
-            # We're not paginating, so should be 0
-            self.assertEqual(channel.json_body["offset"], 0)
-
-            # Check that there is no `prev_batch`
-            self.assertNotIn("prev_batch", channel.json_body)
-
-            # Check that there is no `next_batch`
-            self.assertNotIn("next_batch", channel.json_body)
-
-            if expected_room_id:
-                # Check that the first returned room id is correct
-                r = rooms[0]
-                self.assertEqual(expected_room_id, r["room_id"])
-
-        # Perform search tests
-        _search_test(room_id_1, "something")
-        _search_test(room_id_1, "thing")
-
-        _search_test(room_id_2, "else")
-        _search_test(room_id_2, "se")
-
-        _search_test(None, "foo")
-        _search_test(None, "bar")
-        _search_test(None, "", expected_http_code=400)
diff --git a/tests/rest/admin/test_room.py b/tests/rest/admin/test_room.py
new file mode 100644
index 0000000000..249c93722f
--- /dev/null
+++ b/tests/rest/admin/test_room.py
@@ -0,0 +1,966 @@
+# -*- coding: utf-8 -*-

+# Copyright 2020 Dirk Klimpel

+#

+# Licensed under the Apache License, Version 2.0 (the "License");

+# you may not use this file except in compliance with the License.

+# You may obtain a copy of the License at

+#

+#     http://www.apache.org/licenses/LICENSE-2.0

+#

+# Unless required by applicable law or agreed to in writing, software

+# distributed under the License is distributed on an "AS IS" BASIS,

+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+# See the License for the specific language governing permissions and

+# limitations under the License.

+

+import json

+import urllib.parse

+from typing import List, Optional

+

+from mock import Mock

+

+import synapse.rest.admin

+from synapse.api.errors import Codes

+from synapse.rest.client.v1 import directory, events, login, room

+

+from tests import unittest

+

+"""Tests admin REST events for /rooms paths."""

+

+

+class ShutdownRoomTestCase(unittest.HomeserverTestCase):

+    servlets = [

+        synapse.rest.admin.register_servlets_for_client_rest_resource,

+        login.register_servlets,

+        events.register_servlets,

+        room.register_servlets,

+        room.register_deprecated_servlets,

+    ]

+

+    def prepare(self, reactor, clock, hs):

+        self.event_creation_handler = hs.get_event_creation_handler()

+        hs.config.user_consent_version = "1"

+

+        consent_uri_builder = Mock()

+        consent_uri_builder.build_user_consent_uri.return_value = "http://example.com"

+        self.event_creation_handler._consent_uri_builder = consent_uri_builder

+

+        self.store = hs.get_datastore()

+

+        self.admin_user = self.register_user("admin", "pass", admin=True)

+        self.admin_user_tok = self.login("admin", "pass")

+

+        self.other_user = self.register_user("user", "pass")

+        self.other_user_token = self.login("user", "pass")

+

+        # Mark the admin user as having consented

+        self.get_success(self.store.user_set_consent_version(self.admin_user, "1"))

+

+    def test_shutdown_room_consent(self):

+        """Test that we can shutdown rooms with local users who have not

+        yet accepted the privacy policy. This used to fail when we tried to

+        force part the user from the old room.

+        """

+        self.event_creation_handler._block_events_without_consent_error = None

+

+        room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_token)

+

+        # Assert one user in room

+        users_in_room = self.get_success(self.store.get_users_in_room(room_id))

+        self.assertEqual([self.other_user], users_in_room)

+

+        # Enable require consent to send events

+        self.event_creation_handler._block_events_without_consent_error = "Error"

+

+        # Assert that the user is getting consent error

+        self.helper.send(

+            room_id, body="foo", tok=self.other_user_token, expect_code=403

+        )

+

+        # Test that the admin can still send shutdown

+        url = "admin/shutdown_room/" + room_id

+        request, channel = self.make_request(

+            "POST",

+            url.encode("ascii"),

+            json.dumps({"new_room_user_id": self.admin_user}),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+

+        # Assert there is now no longer anyone in the room

+        users_in_room = self.get_success(self.store.get_users_in_room(room_id))

+        self.assertEqual([], users_in_room)

+

+    def test_shutdown_room_block_peek(self):

+        """Test that a world_readable room can no longer be peeked into after

+        it has been shut down.

+        """

+

+        self.event_creation_handler._block_events_without_consent_error = None

+

+        room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_token)

+

+        # Enable world readable

+        url = "rooms/%s/state/m.room.history_visibility" % (room_id,)

+        request, channel = self.make_request(

+            "PUT",

+            url.encode("ascii"),

+            json.dumps({"history_visibility": "world_readable"}),

+            access_token=self.other_user_token,

+        )

+        self.render(request)

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+

+        # Test that the admin can still send shutdown

+        url = "admin/shutdown_room/" + room_id

+        request, channel = self.make_request(

+            "POST",

+            url.encode("ascii"),

+            json.dumps({"new_room_user_id": self.admin_user}),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+

+        # Assert we can no longer peek into the room

+        self._assert_peek(room_id, expect_code=403)

+

+    def _assert_peek(self, room_id, expect_code):

+        """Assert that the admin user can (or cannot) peek into the room.

+        """

+

+        url = "rooms/%s/initialSync" % (room_id,)

+        request, channel = self.make_request(

+            "GET", url.encode("ascii"), access_token=self.admin_user_tok

+        )

+        self.render(request)

+        self.assertEqual(

+            expect_code, int(channel.result["code"]), msg=channel.result["body"]

+        )

+

+        url = "events?timeout=0&room_id=" + room_id

+        request, channel = self.make_request(

+            "GET", url.encode("ascii"), access_token=self.admin_user_tok

+        )

+        self.render(request)

+        self.assertEqual(

+            expect_code, int(channel.result["code"]), msg=channel.result["body"]

+        )

+

+

+class PurgeRoomTestCase(unittest.HomeserverTestCase):

+    """Test /purge_room admin API.

+    """

+

+    servlets = [

+        synapse.rest.admin.register_servlets,

+        login.register_servlets,

+        room.register_servlets,

+    ]

+

+    def prepare(self, reactor, clock, hs):

+        self.store = hs.get_datastore()

+

+        self.admin_user = self.register_user("admin", "pass", admin=True)

+        self.admin_user_tok = self.login("admin", "pass")

+

+    def test_purge_room(self):

+        room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)

+

+        # All users have to have left the room.

+        self.helper.leave(room_id, user=self.admin_user, tok=self.admin_user_tok)

+

+        url = "/_synapse/admin/v1/purge_room"

+        request, channel = self.make_request(

+            "POST",

+            url.encode("ascii"),

+            {"room_id": room_id},

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+

+        # Test that the following tables have been purged of all rows related to the room.

+        for table in (

+            "current_state_events",

+            "event_backward_extremities",

+            "event_forward_extremities",

+            "event_json",

+            "event_push_actions",

+            "event_search",

+            "events",

+            "group_rooms",

+            "public_room_list_stream",

+            "receipts_graph",

+            "receipts_linearized",

+            "room_aliases",

+            "room_depth",

+            "room_memberships",

+            "room_stats_state",

+            "room_stats_current",

+            "room_stats_historical",

+            "room_stats_earliest_token",

+            "rooms",

+            "stream_ordering_to_exterm",

+            "users_in_public_rooms",

+            "users_who_share_private_rooms",

+            "appservice_room_list",

+            "e2e_room_keys",

+            "event_push_summary",

+            "pusher_throttle",

+            "group_summary_rooms",

+            "local_invites",

+            "room_account_data",

+            "room_tags",

+            # "state_groups",  # Current impl leaves orphaned state groups around.

+            "state_groups_state",

+        ):

+            count = self.get_success(

+                self.store.db.simple_select_one_onecol(

+                    table=table,

+                    keyvalues={"room_id": room_id},

+                    retcol="COUNT(*)",

+                    desc="test_purge_room",

+                )

+            )

+

+            self.assertEqual(count, 0, msg="Rows not purged in {}".format(table))

+

+

+class RoomTestCase(unittest.HomeserverTestCase):

+    """Test /room admin API.

+    """

+

+    servlets = [

+        synapse.rest.admin.register_servlets,

+        login.register_servlets,

+        room.register_servlets,

+        directory.register_servlets,

+    ]

+

+    def prepare(self, reactor, clock, hs):

+        self.store = hs.get_datastore()

+

+        # Create user

+        self.admin_user = self.register_user("admin", "pass", admin=True)

+        self.admin_user_tok = self.login("admin", "pass")

+

+    def test_list_rooms(self):

+        """Test that we can list rooms"""

+        # Create 3 test rooms

+        total_rooms = 3

+        room_ids = []

+        for x in range(total_rooms):

+            room_id = self.helper.create_room_as(

+                self.admin_user, tok=self.admin_user_tok

+            )

+            room_ids.append(room_id)

+

+        # Request the list of rooms

+        url = "/_synapse/admin/v1/rooms"

+        request, channel = self.make_request(

+            "GET", url.encode("ascii"), access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        # Check request completed successfully

+        self.assertEqual(200, int(channel.code), msg=channel.json_body)

+

+        # Check that response json body contains a "rooms" key

+        self.assertTrue(

+            "rooms" in channel.json_body,

+            msg="Response body does not " "contain a 'rooms' key",

+        )

+

+        # Check that 3 rooms were returned

+        self.assertEqual(3, len(channel.json_body["rooms"]), msg=channel.json_body)

+

+        # Check their room_ids match

+        returned_room_ids = [room["room_id"] for room in channel.json_body["rooms"]]

+        self.assertEqual(room_ids, returned_room_ids)

+

+        # Check that all fields are available

+        for r in channel.json_body["rooms"]:

+            self.assertIn("name", r)

+            self.assertIn("canonical_alias", r)

+            self.assertIn("joined_members", r)

+            self.assertIn("joined_local_members", r)

+            self.assertIn("version", r)

+            self.assertIn("creator", r)

+            self.assertIn("encryption", r)

+            self.assertIn("federatable", r)

+            self.assertIn("public", r)

+            self.assertIn("join_rules", r)

+            self.assertIn("guest_access", r)

+            self.assertIn("history_visibility", r)

+            self.assertIn("state_events", r)

+

+        # Check that the correct number of total rooms was returned

+        self.assertEqual(channel.json_body["total_rooms"], total_rooms)

+

+        # Check that the offset is correct

+        # Should be 0 as we aren't paginating

+        self.assertEqual(channel.json_body["offset"], 0)

+

+        # Check that the prev_batch parameter is not present

+        self.assertNotIn("prev_batch", channel.json_body)

+

+        # We shouldn't receive a next token here as there's no further rooms to show

+        self.assertNotIn("next_batch", channel.json_body)

+

+    def test_list_rooms_pagination(self):

+        """Test that we can get a full list of rooms through pagination"""

+        # Create 5 test rooms

+        total_rooms = 5

+        room_ids = []

+        for x in range(total_rooms):

+            room_id = self.helper.create_room_as(

+                self.admin_user, tok=self.admin_user_tok

+            )

+            room_ids.append(room_id)

+

+        # Set the name of the rooms so we get a consistent returned ordering

+        for idx, room_id in enumerate(room_ids):

+            self.helper.send_state(

+                room_id, "m.room.name", {"name": str(idx)}, tok=self.admin_user_tok,

+            )

+

+        # Request the list of rooms

+        returned_room_ids = []

+        start = 0

+        limit = 2

+

+        run_count = 0

+        should_repeat = True

+        while should_repeat:

+            run_count += 1

+

+            url = "/_synapse/admin/v1/rooms?from=%d&limit=%d&order_by=%s" % (

+                start,

+                limit,

+                "name",

+            )

+            request, channel = self.make_request(

+                "GET", url.encode("ascii"), access_token=self.admin_user_tok,

+            )

+            self.render(request)

+            self.assertEqual(

+                200, int(channel.result["code"]), msg=channel.result["body"]

+            )

+

+            self.assertTrue("rooms" in channel.json_body)

+            for r in channel.json_body["rooms"]:

+                returned_room_ids.append(r["room_id"])

+

+            # Check that the correct number of total rooms was returned

+            self.assertEqual(channel.json_body["total_rooms"], total_rooms)

+

+            # Check that the offset is correct

+            # We're only getting 2 rooms each page, so should be 2 * last run_count

+            self.assertEqual(channel.json_body["offset"], 2 * (run_count - 1))

+

+            if run_count > 1:

+                # Check the value of prev_batch is correct

+                self.assertEqual(channel.json_body["prev_batch"], 2 * (run_count - 2))

+

+            if "next_batch" not in channel.json_body:

+                # We have reached the end of the list

+                should_repeat = False

+            else:

+                # Make another query with an updated start value

+                start = channel.json_body["next_batch"]

+

+        # We should've queried the endpoint 3 times

+        self.assertEqual(

+            run_count,

+            3,

+            msg="Should've queried 3 times for 5 rooms with limit 2 per query",

+        )

+

+        # Check that we received all of the room ids

+        self.assertEqual(room_ids, returned_room_ids)

+

+        url = "/_synapse/admin/v1/rooms?from=%d&limit=%d" % (start, limit)

+        request, channel = self.make_request(

+            "GET", url.encode("ascii"), access_token=self.admin_user_tok,

+        )

+        self.render(request)

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+

+    def test_correct_room_attributes(self):

+        """Test the correct attributes for a room are returned"""

+        # Create a test room

+        room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)

+

+        test_alias = "#test:test"

+        test_room_name = "something"

+

+        # Have another user join the room

+        user_2 = self.register_user("user4", "pass")

+        user_tok_2 = self.login("user4", "pass")

+        self.helper.join(room_id, user_2, tok=user_tok_2)

+

+        # Create a new alias to this room

+        url = "/_matrix/client/r0/directory/room/%s" % (urllib.parse.quote(test_alias),)

+        request, channel = self.make_request(

+            "PUT",

+            url.encode("ascii"),

+            {"room_id": room_id},

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+

+        # Set this new alias as the canonical alias for this room

+        self.helper.send_state(

+            room_id,

+            "m.room.aliases",

+            {"aliases": [test_alias]},

+            tok=self.admin_user_tok,

+            state_key="test",

+        )

+        self.helper.send_state(

+            room_id,

+            "m.room.canonical_alias",

+            {"alias": test_alias},

+            tok=self.admin_user_tok,

+        )

+

+        # Set a name for the room

+        self.helper.send_state(

+            room_id, "m.room.name", {"name": test_room_name}, tok=self.admin_user_tok,

+        )

+

+        # Request the list of rooms

+        url = "/_synapse/admin/v1/rooms"

+        request, channel = self.make_request(

+            "GET", url.encode("ascii"), access_token=self.admin_user_tok,

+        )

+        self.render(request)

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+

+        # Check that rooms were returned

+        self.assertTrue("rooms" in channel.json_body)

+        rooms = channel.json_body["rooms"]

+

+        # Check that only one room was returned

+        self.assertEqual(len(rooms), 1)

+

+        # And that the value of the total_rooms key was correct

+        self.assertEqual(channel.json_body["total_rooms"], 1)

+

+        # Check that the offset is correct

+        # We're not paginating, so should be 0

+        self.assertEqual(channel.json_body["offset"], 0)

+

+        # Check that there is no `prev_batch`

+        self.assertNotIn("prev_batch", channel.json_body)

+

+        # Check that there is no `next_batch`

+        self.assertNotIn("next_batch", channel.json_body)

+

+        # Check that all provided attributes are set

+        r = rooms[0]

+        self.assertEqual(room_id, r["room_id"])

+        self.assertEqual(test_room_name, r["name"])

+        self.assertEqual(test_alias, r["canonical_alias"])

+

+    def test_room_list_sort_order(self):

+        """Test room list sort ordering. alphabetical name versus number of members,

+        reversing the order, etc.

+        """

+

+        def _set_canonical_alias(room_id: str, test_alias: str, admin_user_tok: str):

+            # Create a new alias to this room

+            url = "/_matrix/client/r0/directory/room/%s" % (

+                urllib.parse.quote(test_alias),

+            )

+            request, channel = self.make_request(

+                "PUT",

+                url.encode("ascii"),

+                {"room_id": room_id},

+                access_token=admin_user_tok,

+            )

+            self.render(request)

+            self.assertEqual(

+                200, int(channel.result["code"]), msg=channel.result["body"]

+            )

+

+            # Set this new alias as the canonical alias for this room

+            self.helper.send_state(

+                room_id,

+                "m.room.aliases",

+                {"aliases": [test_alias]},

+                tok=admin_user_tok,

+                state_key="test",

+            )

+            self.helper.send_state(

+                room_id,

+                "m.room.canonical_alias",

+                {"alias": test_alias},

+                tok=admin_user_tok,

+            )

+

+        def _order_test(

+            order_type: str, expected_room_list: List[str], reverse: bool = False,

+        ):

+            """Request the list of rooms in a certain order. Assert that order is what

+            we expect

+

+            Args:

+                order_type: The type of ordering to give the server

+                expected_room_list: The list of room_ids in the order we expect to get

+                    back from the server

+            """

+            # Request the list of rooms in the given order

+            url = "/_synapse/admin/v1/rooms?order_by=%s" % (order_type,)

+            if reverse:

+                url += "&dir=b"

+            request, channel = self.make_request(

+                "GET", url.encode("ascii"), access_token=self.admin_user_tok,

+            )

+            self.render(request)

+            self.assertEqual(200, channel.code, msg=channel.json_body)

+

+            # Check that rooms were returned

+            self.assertTrue("rooms" in channel.json_body)

+            rooms = channel.json_body["rooms"]

+

+            # Check for the correct total_rooms value

+            self.assertEqual(channel.json_body["total_rooms"], 3)

+

+            # Check that the offset is correct

+            # We're not paginating, so should be 0

+            self.assertEqual(channel.json_body["offset"], 0)

+

+            # Check that there is no `prev_batch`

+            self.assertNotIn("prev_batch", channel.json_body)

+

+            # Check that there is no `next_batch`

+            self.assertNotIn("next_batch", channel.json_body)

+

+            # Check that rooms were returned in alphabetical order

+            returned_order = [r["room_id"] for r in rooms]

+            self.assertListEqual(expected_room_list, returned_order)  # order is checked

+

+        # Create 3 test rooms

+        room_id_1 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)

+        room_id_2 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)

+        room_id_3 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)

+

+        # Set room names in alphabetical order. room 1 -> A, 2 -> B, 3 -> C

+        self.helper.send_state(

+            room_id_1, "m.room.name", {"name": "A"}, tok=self.admin_user_tok,

+        )

+        self.helper.send_state(

+            room_id_2, "m.room.name", {"name": "B"}, tok=self.admin_user_tok,

+        )

+        self.helper.send_state(

+            room_id_3, "m.room.name", {"name": "C"}, tok=self.admin_user_tok,

+        )

+

+        # Set room canonical room aliases

+        _set_canonical_alias(room_id_1, "#A_alias:test", self.admin_user_tok)

+        _set_canonical_alias(room_id_2, "#B_alias:test", self.admin_user_tok)

+        _set_canonical_alias(room_id_3, "#C_alias:test", self.admin_user_tok)

+

+        # Set room member size in the reverse order. room 1 -> 1 member, 2 -> 2, 3 -> 3

+        user_1 = self.register_user("bob1", "pass")

+        user_1_tok = self.login("bob1", "pass")

+        self.helper.join(room_id_2, user_1, tok=user_1_tok)

+

+        user_2 = self.register_user("bob2", "pass")

+        user_2_tok = self.login("bob2", "pass")

+        self.helper.join(room_id_3, user_2, tok=user_2_tok)

+

+        user_3 = self.register_user("bob3", "pass")

+        user_3_tok = self.login("bob3", "pass")

+        self.helper.join(room_id_3, user_3, tok=user_3_tok)

+

+        # Test different sort orders, with forward and reverse directions

+        _order_test("name", [room_id_1, room_id_2, room_id_3])

+        _order_test("name", [room_id_3, room_id_2, room_id_1], reverse=True)

+

+        _order_test("canonical_alias", [room_id_1, room_id_2, room_id_3])

+        _order_test("canonical_alias", [room_id_3, room_id_2, room_id_1], reverse=True)

+

+        _order_test("joined_members", [room_id_3, room_id_2, room_id_1])

+        _order_test("joined_members", [room_id_1, room_id_2, room_id_3], reverse=True)

+

+        _order_test("joined_local_members", [room_id_3, room_id_2, room_id_1])

+        _order_test(

+            "joined_local_members", [room_id_1, room_id_2, room_id_3], reverse=True

+        )

+

+        _order_test("version", [room_id_1, room_id_2, room_id_3])

+        _order_test("version", [room_id_1, room_id_2, room_id_3], reverse=True)

+

+        _order_test("creator", [room_id_1, room_id_2, room_id_3])

+        _order_test("creator", [room_id_1, room_id_2, room_id_3], reverse=True)

+

+        _order_test("encryption", [room_id_1, room_id_2, room_id_3])

+        _order_test("encryption", [room_id_1, room_id_2, room_id_3], reverse=True)

+

+        _order_test("federatable", [room_id_1, room_id_2, room_id_3])

+        _order_test("federatable", [room_id_1, room_id_2, room_id_3], reverse=True)

+

+        _order_test("public", [room_id_1, room_id_2, room_id_3])

+        # Different sort order of SQlite and PostreSQL

+        # _order_test("public", [room_id_3, room_id_2, room_id_1], reverse=True)

+

+        _order_test("join_rules", [room_id_1, room_id_2, room_id_3])

+        _order_test("join_rules", [room_id_1, room_id_2, room_id_3], reverse=True)

+

+        _order_test("guest_access", [room_id_1, room_id_2, room_id_3])

+        _order_test("guest_access", [room_id_1, room_id_2, room_id_3], reverse=True)

+

+        _order_test("history_visibility", [room_id_1, room_id_2, room_id_3])

+        _order_test(

+            "history_visibility", [room_id_1, room_id_2, room_id_3], reverse=True

+        )

+

+        _order_test("state_events", [room_id_3, room_id_2, room_id_1])

+        _order_test("state_events", [room_id_1, room_id_2, room_id_3], reverse=True)

+

+    def test_search_term(self):

+        """Test that searching for a room works correctly"""

+        # Create two test rooms

+        room_id_1 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)

+        room_id_2 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)

+

+        room_name_1 = "something"

+        room_name_2 = "else"

+

+        # Set the name for each room

+        self.helper.send_state(

+            room_id_1, "m.room.name", {"name": room_name_1}, tok=self.admin_user_tok,

+        )

+        self.helper.send_state(

+            room_id_2, "m.room.name", {"name": room_name_2}, tok=self.admin_user_tok,

+        )

+

+        def _search_test(

+            expected_room_id: Optional[str],

+            search_term: str,

+            expected_http_code: int = 200,

+        ):

+            """Search for a room and check that the returned room's id is a match

+

+            Args:

+                expected_room_id: The room_id expected to be returned by the API. Set

+                    to None to expect zero results for the search

+                search_term: The term to search for room names with

+                expected_http_code: The expected http code for the request

+            """

+            url = "/_synapse/admin/v1/rooms?search_term=%s" % (search_term,)

+            request, channel = self.make_request(

+                "GET", url.encode("ascii"), access_token=self.admin_user_tok,

+            )

+            self.render(request)

+            self.assertEqual(expected_http_code, channel.code, msg=channel.json_body)

+

+            if expected_http_code != 200:

+                return

+

+            # Check that rooms were returned

+            self.assertTrue("rooms" in channel.json_body)

+            rooms = channel.json_body["rooms"]

+

+            # Check that the expected number of rooms were returned

+            expected_room_count = 1 if expected_room_id else 0

+            self.assertEqual(len(rooms), expected_room_count)

+            self.assertEqual(channel.json_body["total_rooms"], expected_room_count)

+

+            # Check that the offset is correct

+            # We're not paginating, so should be 0

+            self.assertEqual(channel.json_body["offset"], 0)

+

+            # Check that there is no `prev_batch`

+            self.assertNotIn("prev_batch", channel.json_body)

+

+            # Check that there is no `next_batch`

+            self.assertNotIn("next_batch", channel.json_body)

+

+            if expected_room_id:

+                # Check that the first returned room id is correct

+                r = rooms[0]

+                self.assertEqual(expected_room_id, r["room_id"])

+

+        # Perform search tests

+        _search_test(room_id_1, "something")

+        _search_test(room_id_1, "thing")

+

+        _search_test(room_id_2, "else")

+        _search_test(room_id_2, "se")

+

+        _search_test(None, "foo")

+        _search_test(None, "bar")

+        _search_test(None, "", expected_http_code=400)

+

+

+class JoinAliasRoomTestCase(unittest.HomeserverTestCase):

+

+    servlets = [

+        synapse.rest.admin.register_servlets,

+        room.register_servlets,

+        login.register_servlets,

+    ]

+

+    def prepare(self, reactor, clock, homeserver):

+        self.admin_user = self.register_user("admin", "pass", admin=True)

+        self.admin_user_tok = self.login("admin", "pass")

+

+        self.creator = self.register_user("creator", "test")

+        self.creator_tok = self.login("creator", "test")

+

+        self.second_user_id = self.register_user("second", "test")

+        self.second_tok = self.login("second", "test")

+

+        self.public_room_id = self.helper.create_room_as(

+            self.creator, tok=self.creator_tok, is_public=True

+        )

+        self.url = "/_synapse/admin/v1/join/{}".format(self.public_room_id)

+

+    def test_requester_is_no_admin(self):

+        """

+        If the user is not a server admin, an error 403 is returned.

+        """

+        body = json.dumps({"user_id": self.second_user_id})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.second_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])

+

+    def test_invalid_parameter(self):

+        """

+        If a parameter is missing, return an error

+        """

+        body = json.dumps({"unknown_parameter": "@unknown:test"})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(Codes.MISSING_PARAM, channel.json_body["errcode"])

+

+    def test_local_user_does_not_exist(self):

+        """

+        Tests that a lookup for a user that does not exist returns a 404

+        """

+        body = json.dumps({"user_id": "@unknown:test"})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])

+

+    def test_remote_user(self):

+        """

+        Check that only local user can join rooms.

+        """

+        body = json.dumps({"user_id": "@not:exist.bla"})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(

+            "This endpoint can only be used with local users",

+            channel.json_body["error"],

+        )

+

+    def test_room_does_not_exist(self):

+        """

+        Check that unknown rooms/server return error 404.

+        """

+        body = json.dumps({"user_id": self.second_user_id})

+        url = "/_synapse/admin/v1/join/!unknown:test"

+

+        request, channel = self.make_request(

+            "POST",

+            url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual("No known servers", channel.json_body["error"])

+

+    def test_room_is_not_valid(self):

+        """

+        Check that invalid room names, return an error 400.

+        """

+        body = json.dumps({"user_id": self.second_user_id})

+        url = "/_synapse/admin/v1/join/invalidroom"

+

+        request, channel = self.make_request(

+            "POST",

+            url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(

+            "invalidroom was not legal room ID or room alias",

+            channel.json_body["error"],

+        )

+

+    def test_join_public_room(self):

+        """

+        Test joining a local user to a public room with "JoinRules.PUBLIC"

+        """

+        body = json.dumps({"user_id": self.second_user_id})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(self.public_room_id, channel.json_body["room_id"])

+

+        # Validate if user is a member of the room

+

+        request, channel = self.make_request(

+            "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,

+        )

+        self.render(request)

+        self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(self.public_room_id, channel.json_body["joined_rooms"][0])

+

+    def test_join_private_room_if_not_member(self):

+        """

+        Test joining a local user to a private room with "JoinRules.INVITE"

+        when server admin is not member of this room.

+        """

+        private_room_id = self.helper.create_room_as(

+            self.creator, tok=self.creator_tok, is_public=False

+        )

+        url = "/_synapse/admin/v1/join/{}".format(private_room_id)

+        body = json.dumps({"user_id": self.second_user_id})

+

+        request, channel = self.make_request(

+            "POST",

+            url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])

+

+    def test_join_private_room_if_member(self):

+        """

+        Test joining a local user to a private room with "JoinRules.INVITE",

+        when server admin is member of this room.

+        """

+        private_room_id = self.helper.create_room_as(

+            self.creator, tok=self.creator_tok, is_public=False

+        )

+        self.helper.invite(

+            room=private_room_id,

+            src=self.creator,

+            targ=self.admin_user,

+            tok=self.creator_tok,

+        )

+        self.helper.join(

+            room=private_room_id, user=self.admin_user, tok=self.admin_user_tok

+        )

+

+        # Validate if server admin is a member of the room

+

+        request, channel = self.make_request(

+            "GET", "/_matrix/client/r0/joined_rooms", access_token=self.admin_user_tok,

+        )

+        self.render(request)

+        self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])

+

+        # Join user to room.

+

+        url = "/_synapse/admin/v1/join/{}".format(private_room_id)

+        body = json.dumps({"user_id": self.second_user_id})

+

+        request, channel = self.make_request(

+            "POST",

+            url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(private_room_id, channel.json_body["room_id"])

+

+        # Validate if user is a member of the room

+

+        request, channel = self.make_request(

+            "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,

+        )

+        self.render(request)

+        self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])

+

+    def test_join_private_room_if_owner(self):

+        """

+        Test joining a local user to a private room with "JoinRules.INVITE",

+        when server admin is owner of this room.

+        """

+        private_room_id = self.helper.create_room_as(

+            self.admin_user, tok=self.admin_user_tok, is_public=False

+        )

+        url = "/_synapse/admin/v1/join/{}".format(private_room_id)

+        body = json.dumps({"user_id": self.second_user_id})

+

+        request, channel = self.make_request(

+            "POST",

+            url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(private_room_id, channel.json_body["room_id"])

+

+        # Validate if user is a member of the room

+

+        request, channel = self.make_request(

+            "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,

+        )

+        self.render(request)

+        self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])

diff --git a/tests/rest/admin/test_user.py b/tests/rest/admin/test_user.py
index 6416fb5d2a..6c88ab06e2 100644
--- a/tests/rest/admin/test_user.py
+++ b/tests/rest/admin/test_user.py
@@ -360,6 +360,7 @@ class UsersListTestCase(unittest.HomeserverTestCase):
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(3, len(channel.json_body["users"]))
+        self.assertEqual(3, channel.json_body["total"])
 
 
 class UserRestTestCase(unittest.HomeserverTestCase):
@@ -434,6 +435,7 @@ class UserRestTestCase(unittest.HomeserverTestCase):
                 "admin": True,
                 "displayname": "Bob's name",
                 "threepids": [{"medium": "email", "address": "bob@bob.bob"}],
+                "avatar_url": None,
             }
         )