Synapse 1.45.0 (2021-10-19) =========================== No functional changes since Synapse 1.45.0rc2. Known Issues ------------ - A suspected [performance regression](https://github.com/matrix-org/synapse/issues/11049) which was first reported after the release of 1.44.0 remains unresolved. We have not been able to identify a probable cause. Affected users report that setting up a federation sender worker appears to alleviate symptoms of the regression. Improved Documentation ---------------------- - Reword changelog to clarify concerns about a suspected performance regression in 1.44.0. ([\#11117](https://github.com/matrix-org/synapse/issues/11117)) Synapse 1.45.0rc2 (2021-10-14) ============================== This release candidate [fixes](https://github.com/matrix-org/synapse/issues/11053) a user directory [bug](https://github.com/matrix-org/synapse/issues/11025) present in 1.45.0rc1. Known Issues ------------ - A suspected [performance regression](https://github.com/matrix-org/synapse/issues/11049) which was first reported after the release of 1.44.0 remains unresolved. We have not been able to identify a probable cause. Affected users report that setting up a federation sender worker appears to alleviate symptoms of the regression. Bugfixes -------- - Fix a long-standing bug when using multiple event persister workers where events were not correctly sent down `/sync` due to a race. ([\#11045](https://github.com/matrix-org/synapse/issues/11045)) - Fix a bug introduced in Synapse 1.45.0rc1 where the user directory would stop updating if it processed an event from a user not in the `users` table. ([\#11053](https://github.com/matrix-org/synapse/issues/11053)) - Fix a bug introduced in Synapse 1.44.0 when logging errors during oEmbed processing. ([\#11061](https://github.com/matrix-org/synapse/issues/11061)) Internal Changes ---------------- - Add an 'approximate difference' method to `StateFilter`. ([\#10825](https://github.com/matrix-org/synapse/issues/10825)) - Fix inconsistent behavior of `get_last_client_by_ip` when reporting data that has not been stored in the database yet. ([\#10970](https://github.com/matrix-org/synapse/issues/10970)) - Fix a bug introduced in Synapse 1.21.0 that causes opentracing and Prometheus metrics for replication requests to be measured incorrectly. ([\#10996](https://github.com/matrix-org/synapse/issues/10996)) - Ensure that cache config tests do not share state. ([\#11036](https://github.com/matrix-org/synapse/issues/11036)) Synapse 1.45.0rc1 (2021-10-12) ============================== **Note:** Media storage providers module that read from Synapse's configuration need changes as of this version, see the [upgrade notes](https://matrix-org.github.io/synapse/develop/upgrade#upgrading-to-v1450) for more information. Known Issues ------------ - We are investigating [a performance issue](https://github.com/matrix-org/synapse/issues/11049) which was reported after the release of 1.44.0. - We are aware of [a bug](https://github.com/matrix-org/synapse/issues/11025) with the user directory when using application services. A second release candidate is expected which will resolve this. Features -------- - Add [MSC3069](https://github.com/matrix-org/matrix-doc/pull/3069) support to `/account/whoami`. ([\#9655](https://github.com/matrix-org/synapse/issues/9655)) - Support autodiscovery of oEmbed previews. ([\#10822](https://github.com/matrix-org/synapse/issues/10822)) - Add a `user_may_send_3pid_invite` spam checker callback for modules to allow or deny 3PID invites. ([\#10894](https://github.com/matrix-org/synapse/issues/10894)) - Add a spam checker callback to allow or deny room joins. ([\#10910](https://github.com/matrix-org/synapse/issues/10910)) - Include an `update_synapse_database` script in the distribution. Contributed by @Fizzadar at Beeper. ([\#10954](https://github.com/matrix-org/synapse/issues/10954)) - Include exception information in JSON logging output. Contributed by @Fizzadar at Beeper. ([\#11028](https://github.com/matrix-org/synapse/issues/11028)) Bugfixes -------- - Fix a minor bug in the response to `/_matrix/client/r0/voip/turnServer`. Contributed by @lukaslihotzki. ([\#10922](https://github.com/matrix-org/synapse/issues/10922)) - Fix a bug where empty `yyyy-mm-dd/` directories would be left behind in the media store's `url_cache_thumbnails/` directory. ([\#10924](https://github.com/matrix-org/synapse/issues/10924)) - Fix a bug introduced in Synapse v1.40.0 where the signature checks for room version 8 and 9 could be applied to earlier room versions in some situations. ([\#10927](https://github.com/matrix-org/synapse/issues/10927)) - Fix a long-standing bug wherein deactivated users still count towards the monthly active users limit. ([\#10947](https://github.com/matrix-org/synapse/issues/10947)) - Fix a long-standing bug which meant that events received over federation were sometimes incorrectly accepted into the room state. ([\#10956](https://github.com/matrix-org/synapse/issues/10956)) - Fix a long-standing bug where rebuilding the user directory wouldn't exclude support and deactivated users. ([\#10960](https://github.com/matrix-org/synapse/issues/10960)) - Fix [MSC2716](https://github.com/matrix-org/matrix-doc/pull/2716) `/batch_send` endpoint rejecting subsequent batches with unknown batch ID error in existing room versions from the room creator. ([\#10962](https://github.com/matrix-org/synapse/issues/10962)) - Fix a bug that could leak local users' per-room nicknames and avatars when the user directory is rebuilt. ([\#10981](https://github.com/matrix-org/synapse/issues/10981)) - Fix a long-standing bug where the remainder of a batch of user directory changes would be silently dropped if the server left a room early in the batch. ([\#10982](https://github.com/matrix-org/synapse/issues/10982)) - Correct a bugfix introduced in Synapse v1.44.0 that would catch the wrong error if a connection is lost before a response could be written to it. ([\#10995](https://github.com/matrix-org/synapse/issues/10995)) - Fix a long-standing bug where local users' per-room nicknames/avatars were visible to anyone who could see you in the user directory. ([\#11002](https://github.com/matrix-org/synapse/issues/11002)) - Fix a long-standing bug where a user's per-room nickname/avatar would overwrite their profile in the user directory when a room was made public. ([\#11003](https://github.com/matrix-org/synapse/issues/11003)) - Work around a regression, introduced in Synapse v1.39.0, that caused `SynapseError`s raised by the experimental third-party rules module callback `check_event_allowed` to be ignored. ([\#11042](https://github.com/matrix-org/synapse/issues/11042)) - Fix a bug in [MSC2716](https://github.com/matrix-org/matrix-doc/pull/2716) insertion events in rooms that could cause cross-talk/conflicts between batches. ([\#10877](https://github.com/matrix-org/synapse/issues/10877)) Improved Documentation ---------------------- - Change wording ("reference homeserver") in Synapse repository documentation. Contributed by @maxkratz. ([\#10971](https://github.com/matrix-org/synapse/issues/10971)) - Fix a dead URL in development documentation (SAML) and change wording from "Riot" to "Element". Contributed by @maxkratz. ([\#10973](https://github.com/matrix-org/synapse/issues/10973)) - Add additional content to the Welcome and Overview page of the documentation. ([\#10990](https://github.com/matrix-org/synapse/issues/10990)) - Update links to MSCs in documentation. Contributed by @dklimpel. ([\#10991](https://github.com/matrix-org/synapse/issues/10991)) Internal Changes ---------------- - Improve type hinting in `synapse.util`. ([\#10888](https://github.com/matrix-org/synapse/issues/10888)) - Add further type hints to `synapse.storage.util`. ([\#10892](https://github.com/matrix-org/synapse/issues/10892)) - Fix type hints to be compatible with an upcoming change to Twisted. ([\#10895](https://github.com/matrix-org/synapse/issues/10895)) - Update utility code to handle C implementations of frozendict. ([\#10902](https://github.com/matrix-org/synapse/issues/10902)) - Drop old functionality which maintained database compatibility with Synapse versions before v1.31. ([\#10903](https://github.com/matrix-org/synapse/issues/10903)) - Clean-up configuration helper classes for the `ServerConfig` class. ([\#10915](https://github.com/matrix-org/synapse/issues/10915)) - Use direct references to config flags. ([\#10916](https://github.com/matrix-org/synapse/issues/10916), [\#10959](https://github.com/matrix-org/synapse/issues/10959), [\#10985](https://github.com/matrix-org/synapse/issues/10985)) - Clean up some of the federation event authentication code for clarity. ([\#10926](https://github.com/matrix-org/synapse/issues/10926), [\#10940](https://github.com/matrix-org/synapse/issues/10940), [\#10986](https://github.com/matrix-org/synapse/issues/10986), [\#10987](https://github.com/matrix-org/synapse/issues/10987), [\#10988](https://github.com/matrix-org/synapse/issues/10988), [\#11010](https://github.com/matrix-org/synapse/issues/11010), [\#11011](https://github.com/matrix-org/synapse/issues/11011)) - Refactor various parts of the codebase to use `RoomVersion` objects instead of room version identifier strings. ([\#10934](https://github.com/matrix-org/synapse/issues/10934)) - Refactor user directory tests in preparation for upcoming changes. ([\#10935](https://github.com/matrix-org/synapse/issues/10935)) - Include the event id in the logcontext when handling PDUs received over federation. ([\#10936](https://github.com/matrix-org/synapse/issues/10936)) - Fix logged errors in unit tests. ([\#10939](https://github.com/matrix-org/synapse/issues/10939)) - Fix a broken test to ensure that consent configuration works during registration. ([\#10945](https://github.com/matrix-org/synapse/issues/10945)) - Add type hints to filtering classes. ([\#10958](https://github.com/matrix-org/synapse/issues/10958)) - Add type-hint to `HomeserverTestcase.setup_test_homeserver`. ([\#10961](https://github.com/matrix-org/synapse/issues/10961)) - Fix the test utility function `create_room_as` so that `is_public=True` will explicitly set the `visibility` parameter of room creation requests to `public`. Contributed by @AndrewFerr. ([\#10963](https://github.com/matrix-org/synapse/issues/10963)) - Make the release script more robust and transparent. ([\#10966](https://github.com/matrix-org/synapse/issues/10966)) - Refactor [MSC2716](https://github.com/matrix-org/matrix-doc/pull/2716) `/batch_send` mega function into smaller handler functions. ([\#10974](https://github.com/matrix-org/synapse/issues/10974)) - Log stack traces when a missing opentracing span is detected. ([\#10983](https://github.com/matrix-org/synapse/issues/10983)) - Update GHA config to run tests against Python 3.10 and PostgreSQL 14. ([\#10992](https://github.com/matrix-org/synapse/issues/10992)) - Fix a long-standing bug where `ReadWriteLock`s could drop logging contexts on exit. ([\#10993](https://github.com/matrix-org/synapse/issues/10993)) - Add a `CODEOWNERS` file to automatically request reviews from the `@matrix-org/synapse-core` team on new pull requests. ([\#10994](https://github.com/matrix-org/synapse/issues/10994)) - Add further type hints to `synapse.state`. ([\#11004](https://github.com/matrix-org/synapse/issues/11004)) - Remove the deprecated `BaseHandler` object. ([\#11005](https://github.com/matrix-org/synapse/issues/11005)) - Bump mypy version for CI to 0.910, and pull in new type stubs for dependencies. ([\#11006](https://github.com/matrix-org/synapse/issues/11006)) - Fix CI to run the unit tests without optional deps. ([\#11017](https://github.com/matrix-org/synapse/issues/11017)) - Ensure that cache config tests do not share state. ([\#11019](https://github.com/matrix-org/synapse/issues/11019)) - Add additional type hints to `synapse.server_notices`. ([\#11021](https://github.com/matrix-org/synapse/issues/11021)) - Add additional type hints for `synapse.push`. ([\#11023](https://github.com/matrix-org/synapse/issues/11023)) - When installing the optional developer dependencies, also include the dependencies needed for type-checking and unit testing. ([\#11034](https://github.com/matrix-org/synapse/issues/11034)) - Remove unnecessary list comprehension from `synapse_port_db` to satisfy code style requirements. ([\#11043](https://github.com/matrix-org/synapse/issues/11043))
616 lines
24 KiB
Python
616 lines
24 KiB
Python
# Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
#
|
|
# 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.
|
|
from unittest.mock import Mock
|
|
|
|
from twisted.internet import defer
|
|
|
|
from synapse.api.constants import EduTypes, EventTypes
|
|
from synapse.events import EventBase
|
|
from synapse.federation.units import Transaction
|
|
from synapse.handlers.presence import UserPresenceState
|
|
from synapse.rest import admin
|
|
from synapse.rest.client import login, presence, room
|
|
from synapse.types import create_requester
|
|
|
|
from tests.events.test_presence_router import send_presence_update, sync_presence
|
|
from tests.replication._base import BaseMultiWorkerStreamTestCase
|
|
from tests.test_utils import simple_async_mock
|
|
from tests.test_utils.event_injection import inject_member_event
|
|
from tests.unittest import HomeserverTestCase, override_config
|
|
from tests.utils import USE_POSTGRES_FOR_TESTS
|
|
|
|
|
|
class ModuleApiTestCase(HomeserverTestCase):
|
|
servlets = [
|
|
admin.register_servlets,
|
|
login.register_servlets,
|
|
room.register_servlets,
|
|
presence.register_servlets,
|
|
]
|
|
|
|
def prepare(self, reactor, clock, homeserver):
|
|
self.store = homeserver.get_datastore()
|
|
self.module_api = homeserver.get_module_api()
|
|
self.event_creation_handler = homeserver.get_event_creation_handler()
|
|
self.sync_handler = homeserver.get_sync_handler()
|
|
self.auth_handler = homeserver.get_auth_handler()
|
|
|
|
def make_homeserver(self, reactor, clock):
|
|
# Mock out the calls over federation.
|
|
fed_transport_client = Mock(spec=["send_transaction"])
|
|
fed_transport_client.send_transaction = simple_async_mock({})
|
|
|
|
return self.setup_test_homeserver(
|
|
federation_transport_client=fed_transport_client,
|
|
)
|
|
|
|
def test_can_register_user(self):
|
|
"""Tests that an external module can register a user"""
|
|
# Register a new user
|
|
user_id, access_token = self.get_success(
|
|
self.module_api.register(
|
|
"bob", displayname="Bobberino", emails=["bob@bobinator.bob"]
|
|
)
|
|
)
|
|
|
|
# Check that the new user exists with all provided attributes
|
|
self.assertEqual(user_id, "@bob:test")
|
|
self.assertTrue(access_token)
|
|
self.assertTrue(self.get_success(self.store.get_user_by_id(user_id)))
|
|
|
|
# Check that the email was assigned
|
|
emails = self.get_success(self.store.user_get_threepids(user_id))
|
|
self.assertEqual(len(emails), 1)
|
|
|
|
email = emails[0]
|
|
self.assertEqual(email["medium"], "email")
|
|
self.assertEqual(email["address"], "bob@bobinator.bob")
|
|
|
|
# Should these be 0?
|
|
self.assertEqual(email["validated_at"], 0)
|
|
self.assertEqual(email["added_at"], 0)
|
|
|
|
# Check that the displayname was assigned
|
|
displayname = self.get_success(self.store.get_profile_displayname("bob"))
|
|
self.assertEqual(displayname, "Bobberino")
|
|
|
|
def test_get_userinfo_by_id(self):
|
|
user_id = self.register_user("alice", "1234")
|
|
found_user = self.get_success(self.module_api.get_userinfo_by_id(user_id))
|
|
self.assertEqual(found_user.user_id.to_string(), user_id)
|
|
self.assertIdentical(found_user.is_admin, False)
|
|
|
|
def test_get_userinfo_by_id__no_user_found(self):
|
|
found_user = self.get_success(self.module_api.get_userinfo_by_id("@alice:test"))
|
|
self.assertIsNone(found_user)
|
|
|
|
def test_get_user_ip_and_agents(self):
|
|
user_id = self.register_user("test_get_user_ip_and_agents_user", "1234")
|
|
|
|
# Initially, we should have no ip/agent for our user.
|
|
info = self.get_success(self.module_api.get_user_ip_and_agents(user_id))
|
|
self.assertEqual(info, [])
|
|
|
|
# Insert a first ip, agent. We should be able to retrieve it.
|
|
self.get_success(
|
|
self.store.insert_client_ip(
|
|
user_id, "access_token", "ip_1", "user_agent_1", "device_1", None
|
|
)
|
|
)
|
|
info = self.get_success(self.module_api.get_user_ip_and_agents(user_id))
|
|
|
|
self.assertEqual(len(info), 1)
|
|
last_seen_1 = info[0].last_seen
|
|
|
|
# Insert a second ip, agent at a later date. We should be able to retrieve it.
|
|
last_seen_2 = last_seen_1 + 10000
|
|
print("%s => %s" % (last_seen_1, last_seen_2))
|
|
self.get_success(
|
|
self.store.insert_client_ip(
|
|
user_id, "access_token", "ip_2", "user_agent_2", "device_2", last_seen_2
|
|
)
|
|
)
|
|
info = self.get_success(self.module_api.get_user_ip_and_agents(user_id))
|
|
|
|
self.assertEqual(len(info), 2)
|
|
ip_1_seen = False
|
|
ip_2_seen = False
|
|
|
|
for i in info:
|
|
if i.ip == "ip_1":
|
|
ip_1_seen = True
|
|
self.assertEqual(i.user_agent, "user_agent_1")
|
|
self.assertEqual(i.last_seen, last_seen_1)
|
|
elif i.ip == "ip_2":
|
|
ip_2_seen = True
|
|
self.assertEqual(i.user_agent, "user_agent_2")
|
|
self.assertEqual(i.last_seen, last_seen_2)
|
|
self.assertTrue(ip_1_seen)
|
|
self.assertTrue(ip_2_seen)
|
|
|
|
# If we fetch from a midpoint between last_seen_1 and last_seen_2,
|
|
# we should only find the second ip, agent.
|
|
info = self.get_success(
|
|
self.module_api.get_user_ip_and_agents(
|
|
user_id, (last_seen_1 + last_seen_2) / 2
|
|
)
|
|
)
|
|
self.assertEqual(len(info), 1)
|
|
self.assertEqual(info[0].ip, "ip_2")
|
|
self.assertEqual(info[0].user_agent, "user_agent_2")
|
|
self.assertEqual(info[0].last_seen, last_seen_2)
|
|
|
|
# If we fetch from a point later than last_seen_2, we shouldn't
|
|
# find anything.
|
|
info = self.get_success(
|
|
self.module_api.get_user_ip_and_agents(user_id, last_seen_2 + 10000)
|
|
)
|
|
self.assertEqual(info, [])
|
|
|
|
def test_get_user_ip_and_agents__no_user_found(self):
|
|
info = self.get_success(
|
|
self.module_api.get_user_ip_and_agents(
|
|
"@test_get_user_ip_and_agents_user_nonexistent:example.com"
|
|
)
|
|
)
|
|
self.assertEqual(info, [])
|
|
|
|
def test_sending_events_into_room(self):
|
|
"""Tests that a module can send events into a room"""
|
|
# Mock out create_and_send_nonmember_event to check whether events are being sent
|
|
self.event_creation_handler.create_and_send_nonmember_event = Mock(
|
|
spec=[],
|
|
side_effect=self.event_creation_handler.create_and_send_nonmember_event,
|
|
)
|
|
|
|
# Create a user and room to play with
|
|
user_id = self.register_user("summer", "monkey")
|
|
tok = self.login("summer", "monkey")
|
|
room_id = self.helper.create_room_as(user_id, tok=tok)
|
|
|
|
# Create and send a non-state event
|
|
content = {"body": "I am a puppet", "msgtype": "m.text"}
|
|
event_dict = {
|
|
"room_id": room_id,
|
|
"type": "m.room.message",
|
|
"content": content,
|
|
"sender": user_id,
|
|
}
|
|
event: EventBase = self.get_success(
|
|
self.module_api.create_and_send_event_into_room(event_dict)
|
|
)
|
|
self.assertEqual(event.sender, user_id)
|
|
self.assertEqual(event.type, "m.room.message")
|
|
self.assertEqual(event.room_id, room_id)
|
|
self.assertFalse(hasattr(event, "state_key"))
|
|
self.assertDictEqual(event.content, content)
|
|
|
|
expected_requester = create_requester(
|
|
user_id, authenticated_entity=self.hs.hostname
|
|
)
|
|
|
|
# Check that the event was sent
|
|
self.event_creation_handler.create_and_send_nonmember_event.assert_called_with(
|
|
expected_requester,
|
|
event_dict,
|
|
ratelimit=False,
|
|
ignore_shadow_ban=True,
|
|
)
|
|
|
|
# Create and send a state event
|
|
content = {
|
|
"events_default": 0,
|
|
"users": {user_id: 100},
|
|
"state_default": 50,
|
|
"users_default": 0,
|
|
"events": {"test.event.type": 25},
|
|
}
|
|
event_dict = {
|
|
"room_id": room_id,
|
|
"type": "m.room.power_levels",
|
|
"content": content,
|
|
"sender": user_id,
|
|
"state_key": "",
|
|
}
|
|
event: EventBase = self.get_success(
|
|
self.module_api.create_and_send_event_into_room(event_dict)
|
|
)
|
|
self.assertEqual(event.sender, user_id)
|
|
self.assertEqual(event.type, "m.room.power_levels")
|
|
self.assertEqual(event.room_id, room_id)
|
|
self.assertEqual(event.state_key, "")
|
|
self.assertDictEqual(event.content, content)
|
|
|
|
# Check that the event was sent
|
|
self.event_creation_handler.create_and_send_nonmember_event.assert_called_with(
|
|
expected_requester,
|
|
{
|
|
"type": "m.room.power_levels",
|
|
"content": content,
|
|
"room_id": room_id,
|
|
"sender": user_id,
|
|
"state_key": "",
|
|
},
|
|
ratelimit=False,
|
|
ignore_shadow_ban=True,
|
|
)
|
|
|
|
# Check that we can't send membership events
|
|
content = {
|
|
"membership": "leave",
|
|
}
|
|
event_dict = {
|
|
"room_id": room_id,
|
|
"type": "m.room.member",
|
|
"content": content,
|
|
"sender": user_id,
|
|
"state_key": user_id,
|
|
}
|
|
self.get_failure(
|
|
self.module_api.create_and_send_event_into_room(event_dict), Exception
|
|
)
|
|
|
|
def test_public_rooms(self):
|
|
"""Tests that a room can be added and removed from the public rooms list,
|
|
as well as have its public rooms directory state queried.
|
|
"""
|
|
# Create a user and room to play with
|
|
user_id = self.register_user("kermit", "monkey")
|
|
tok = self.login("kermit", "monkey")
|
|
room_id = self.helper.create_room_as(user_id, tok=tok)
|
|
|
|
# The room should not currently be in the public rooms directory
|
|
is_in_public_rooms = self.get_success(
|
|
self.module_api.public_room_list_manager.room_is_in_public_room_list(
|
|
room_id
|
|
)
|
|
)
|
|
self.assertFalse(is_in_public_rooms)
|
|
|
|
# Let's try adding it to the public rooms directory
|
|
self.get_success(
|
|
self.module_api.public_room_list_manager.add_room_to_public_room_list(
|
|
room_id
|
|
)
|
|
)
|
|
|
|
# And checking whether it's in there...
|
|
is_in_public_rooms = self.get_success(
|
|
self.module_api.public_room_list_manager.room_is_in_public_room_list(
|
|
room_id
|
|
)
|
|
)
|
|
self.assertTrue(is_in_public_rooms)
|
|
|
|
# Let's remove it again
|
|
self.get_success(
|
|
self.module_api.public_room_list_manager.remove_room_from_public_room_list(
|
|
room_id
|
|
)
|
|
)
|
|
|
|
# Should be gone
|
|
is_in_public_rooms = self.get_success(
|
|
self.module_api.public_room_list_manager.room_is_in_public_room_list(
|
|
room_id
|
|
)
|
|
)
|
|
self.assertFalse(is_in_public_rooms)
|
|
|
|
def test_send_local_online_presence_to(self):
|
|
# Test sending local online presence to users from the main process
|
|
_test_sending_local_online_presence_to_local_user(self, test_with_workers=False)
|
|
|
|
@override_config({"send_federation": True})
|
|
def test_send_local_online_presence_to_federation(self):
|
|
"""Tests that send_local_presence_to_users sends local online presence to remote users."""
|
|
# Create a user who will send presence updates
|
|
self.presence_sender_id = self.register_user("presence_sender1", "monkey")
|
|
self.presence_sender_tok = self.login("presence_sender1", "monkey")
|
|
|
|
# And a room they're a part of
|
|
room_id = self.helper.create_room_as(
|
|
self.presence_sender_id,
|
|
tok=self.presence_sender_tok,
|
|
)
|
|
|
|
# Mark them as online
|
|
send_presence_update(
|
|
self,
|
|
self.presence_sender_id,
|
|
self.presence_sender_tok,
|
|
"online",
|
|
"I'm online!",
|
|
)
|
|
|
|
# Make up a remote user to send presence to
|
|
remote_user_id = "@far_away_person:island"
|
|
|
|
# Create a join membership event for the remote user into the room.
|
|
# This allows presence information to flow from one user to the other.
|
|
self.get_success(
|
|
inject_member_event(
|
|
self.hs,
|
|
room_id,
|
|
sender=remote_user_id,
|
|
target=remote_user_id,
|
|
membership="join",
|
|
)
|
|
)
|
|
|
|
# The remote user would have received the existing room members' presence
|
|
# when they joined the room.
|
|
#
|
|
# Thus we reset the mock, and try sending online local user
|
|
# presence again
|
|
self.hs.get_federation_transport_client().send_transaction.reset_mock()
|
|
|
|
# Broadcast local user online presence
|
|
self.get_success(
|
|
self.module_api.send_local_online_presence_to([remote_user_id])
|
|
)
|
|
|
|
# Check that a presence update was sent as part of a federation transaction
|
|
found_update = False
|
|
calls = (
|
|
self.hs.get_federation_transport_client().send_transaction.call_args_list
|
|
)
|
|
for call in calls:
|
|
call_args = call[0]
|
|
federation_transaction: Transaction = call_args[0]
|
|
|
|
# Get the sent EDUs in this transaction
|
|
edus = federation_transaction.get_dict()["edus"]
|
|
|
|
for edu in edus:
|
|
# Make sure we're only checking presence-type EDUs
|
|
if edu["edu_type"] != EduTypes.Presence:
|
|
continue
|
|
|
|
# EDUs can contain multiple presence updates
|
|
for presence_update in edu["content"]["push"]:
|
|
if presence_update["user_id"] == self.presence_sender_id:
|
|
found_update = True
|
|
|
|
self.assertTrue(found_update)
|
|
|
|
def test_get_room_state(self):
|
|
"""Tests that a module can retrieve the state of a room through the module API."""
|
|
user_id = self.register_user("peter", "hackme")
|
|
tok = self.login("peter", "hackme")
|
|
|
|
# Create a room and send some custom state in it.
|
|
room_id = self.helper.create_room_as(tok=tok)
|
|
self.helper.send_state(room_id, "org.matrix.test", {}, tok=tok)
|
|
|
|
# Check that the module API can successfully fetch state for the room.
|
|
state = self.get_success(
|
|
defer.ensureDeferred(self.module_api.get_room_state(room_id))
|
|
)
|
|
|
|
# Check that a few standard events are in the returned state.
|
|
self.assertIn((EventTypes.Create, ""), state)
|
|
self.assertIn((EventTypes.Member, user_id), state)
|
|
|
|
# Check that our custom state event is in the returned state.
|
|
self.assertEqual(state[("org.matrix.test", "")].sender, user_id)
|
|
self.assertEqual(state[("org.matrix.test", "")].state_key, "")
|
|
self.assertEqual(state[("org.matrix.test", "")].content, {})
|
|
|
|
|
|
class ModuleApiWorkerTestCase(BaseMultiWorkerStreamTestCase):
|
|
"""For testing ModuleApi functionality in a multi-worker setup"""
|
|
|
|
# Testing stream ID replication from the main to worker processes requires postgres
|
|
# (due to needing `MultiWriterIdGenerator`).
|
|
if not USE_POSTGRES_FOR_TESTS:
|
|
skip = "Requires Postgres"
|
|
|
|
servlets = [
|
|
admin.register_servlets,
|
|
login.register_servlets,
|
|
room.register_servlets,
|
|
presence.register_servlets,
|
|
]
|
|
|
|
def default_config(self):
|
|
conf = super().default_config()
|
|
conf["redis"] = {"enabled": "true"}
|
|
conf["stream_writers"] = {"presence": ["presence_writer"]}
|
|
conf["instance_map"] = {
|
|
"presence_writer": {"host": "testserv", "port": 1001},
|
|
}
|
|
return conf
|
|
|
|
def prepare(self, reactor, clock, homeserver):
|
|
self.module_api = homeserver.get_module_api()
|
|
self.sync_handler = homeserver.get_sync_handler()
|
|
|
|
def test_send_local_online_presence_to_workers(self):
|
|
# Test sending local online presence to users from a worker process
|
|
_test_sending_local_online_presence_to_local_user(self, test_with_workers=True)
|
|
|
|
|
|
def _test_sending_local_online_presence_to_local_user(
|
|
test_case: HomeserverTestCase, test_with_workers: bool = False
|
|
):
|
|
"""Tests that send_local_presence_to_users sends local online presence to local users.
|
|
|
|
This simultaneously tests two different usecases:
|
|
* Testing that this method works when either called from a worker or the main process.
|
|
- We test this by calling this method from both a TestCase that runs in monolith mode, and one that
|
|
runs with a main and generic_worker.
|
|
* Testing that multiple devices syncing simultaneously will all receive a snapshot of local,
|
|
online presence - but only once per device.
|
|
|
|
Args:
|
|
test_with_workers: If True, this method will call ModuleApi.send_local_online_presence_to on a
|
|
worker process. The test users will still sync with the main process. The purpose of testing
|
|
with a worker is to check whether a Synapse module running on a worker can inform other workers/
|
|
the main process that they should include additional presence when a user next syncs.
|
|
"""
|
|
if test_with_workers:
|
|
# Create a worker process to make module_api calls against
|
|
worker_hs = test_case.make_worker_hs(
|
|
"synapse.app.generic_worker", {"worker_name": "presence_writer"}
|
|
)
|
|
|
|
# Create a user who will send presence updates
|
|
test_case.presence_receiver_id = test_case.register_user(
|
|
"presence_receiver1", "monkey"
|
|
)
|
|
test_case.presence_receiver_tok = test_case.login("presence_receiver1", "monkey")
|
|
|
|
# And another user that will send presence updates out
|
|
test_case.presence_sender_id = test_case.register_user("presence_sender2", "monkey")
|
|
test_case.presence_sender_tok = test_case.login("presence_sender2", "monkey")
|
|
|
|
# Put them in a room together so they will receive each other's presence updates
|
|
room_id = test_case.helper.create_room_as(
|
|
test_case.presence_receiver_id,
|
|
tok=test_case.presence_receiver_tok,
|
|
)
|
|
test_case.helper.join(
|
|
room_id, test_case.presence_sender_id, tok=test_case.presence_sender_tok
|
|
)
|
|
|
|
# Presence sender comes online
|
|
send_presence_update(
|
|
test_case,
|
|
test_case.presence_sender_id,
|
|
test_case.presence_sender_tok,
|
|
"online",
|
|
"I'm online!",
|
|
)
|
|
|
|
# Presence receiver should have received it
|
|
presence_updates, sync_token = sync_presence(
|
|
test_case, test_case.presence_receiver_id
|
|
)
|
|
test_case.assertEqual(len(presence_updates), 1)
|
|
|
|
presence_update: UserPresenceState = presence_updates[0]
|
|
test_case.assertEqual(presence_update.user_id, test_case.presence_sender_id)
|
|
test_case.assertEqual(presence_update.state, "online")
|
|
|
|
if test_with_workers:
|
|
# Replicate the current sync presence token from the main process to the worker process.
|
|
# We need to do this so that the worker process knows the current presence stream ID to
|
|
# insert into the database when we call ModuleApi.send_local_online_presence_to.
|
|
test_case.replicate()
|
|
|
|
# Syncing again should result in no presence updates
|
|
presence_updates, sync_token = sync_presence(
|
|
test_case, test_case.presence_receiver_id, sync_token
|
|
)
|
|
test_case.assertEqual(len(presence_updates), 0)
|
|
|
|
# We do an (initial) sync with a second "device" now, getting a new sync token.
|
|
# We'll use this in a moment.
|
|
_, sync_token_second_device = sync_presence(
|
|
test_case, test_case.presence_receiver_id
|
|
)
|
|
|
|
# Determine on which process (main or worker) to call ModuleApi.send_local_online_presence_to on
|
|
if test_with_workers:
|
|
module_api_to_use = worker_hs.get_module_api()
|
|
else:
|
|
module_api_to_use = test_case.module_api
|
|
|
|
# Trigger sending local online presence. We expect this information
|
|
# to be saved to the database where all processes can access it.
|
|
# Note that we're syncing via the master.
|
|
d = module_api_to_use.send_local_online_presence_to(
|
|
[
|
|
test_case.presence_receiver_id,
|
|
]
|
|
)
|
|
d = defer.ensureDeferred(d)
|
|
|
|
if test_with_workers:
|
|
# In order for the required presence_set_state replication request to occur between the
|
|
# worker and main process, we need to pump the reactor. Otherwise, the coordinator that
|
|
# reads the request on the main process won't do so, and the request will time out.
|
|
while not d.called:
|
|
test_case.reactor.advance(0.1)
|
|
|
|
test_case.get_success(d)
|
|
|
|
# The presence receiver should have received online presence again.
|
|
presence_updates, sync_token = sync_presence(
|
|
test_case, test_case.presence_receiver_id, sync_token
|
|
)
|
|
test_case.assertEqual(len(presence_updates), 1)
|
|
|
|
presence_update: UserPresenceState = presence_updates[0]
|
|
test_case.assertEqual(presence_update.user_id, test_case.presence_sender_id)
|
|
test_case.assertEqual(presence_update.state, "online")
|
|
|
|
# We attempt to sync with the second sync token we received above - just to check that
|
|
# multiple syncing devices will each receive the necessary online presence.
|
|
presence_updates, sync_token_second_device = sync_presence(
|
|
test_case, test_case.presence_receiver_id, sync_token_second_device
|
|
)
|
|
test_case.assertEqual(len(presence_updates), 1)
|
|
|
|
presence_update: UserPresenceState = presence_updates[0]
|
|
test_case.assertEqual(presence_update.user_id, test_case.presence_sender_id)
|
|
test_case.assertEqual(presence_update.state, "online")
|
|
|
|
# However, if we now sync with either "device", we won't receive another burst of online presence
|
|
# until the API is called again sometime in the future
|
|
presence_updates, sync_token = sync_presence(
|
|
test_case, test_case.presence_receiver_id, sync_token
|
|
)
|
|
|
|
# Now we check that we don't receive *offline* updates using ModuleApi.send_local_online_presence_to.
|
|
|
|
# Presence sender goes offline
|
|
send_presence_update(
|
|
test_case,
|
|
test_case.presence_sender_id,
|
|
test_case.presence_sender_tok,
|
|
"offline",
|
|
"I slink back into the darkness.",
|
|
)
|
|
|
|
# Presence receiver should have received the updated, offline state
|
|
presence_updates, sync_token = sync_presence(
|
|
test_case, test_case.presence_receiver_id, sync_token
|
|
)
|
|
test_case.assertEqual(len(presence_updates), 1)
|
|
|
|
# Now trigger sending local online presence.
|
|
d = module_api_to_use.send_local_online_presence_to(
|
|
[
|
|
test_case.presence_receiver_id,
|
|
]
|
|
)
|
|
d = defer.ensureDeferred(d)
|
|
|
|
if test_with_workers:
|
|
# In order for the required presence_set_state replication request to occur between the
|
|
# worker and main process, we need to pump the reactor. Otherwise, the coordinator that
|
|
# reads the request on the main process won't do so, and the request will time out.
|
|
while not d.called:
|
|
test_case.reactor.advance(0.1)
|
|
|
|
test_case.get_success(d)
|
|
|
|
# Presence receiver should *not* have received offline state
|
|
presence_updates, sync_token = sync_presence(
|
|
test_case, test_case.presence_receiver_id, sync_token
|
|
)
|
|
test_case.assertEqual(len(presence_updates), 0)
|