Port the PresenceRouter module interface to the new generic interface introduced in v1.37.0tags/v1.42.0rc1
@@ -0,0 +1 @@ | |||||
Port the PresenceRouter module interface to the new generic interface. |
@@ -282,6 +282,52 @@ the request is a server admin. | |||||
Modules can modify the `request_content` (by e.g. adding events to its `initial_state`), | Modules can modify the `request_content` (by e.g. adding events to its `initial_state`), | ||||
or deny the room's creation by raising a `module_api.errors.SynapseError`. | or deny the room's creation by raising a `module_api.errors.SynapseError`. | ||||
#### Presence router callbacks | |||||
Presence router callbacks allow module developers to specify additional users (local or remote) | |||||
to receive certain presence updates from local users. Presence router callbacks can be | |||||
registered using the module API's `register_presence_router_callbacks` method. | |||||
The available presence router callbacks are: | |||||
```python | |||||
async def get_users_for_states( | |||||
self, | |||||
state_updates: Iterable["synapse.api.UserPresenceState"], | |||||
) -> Dict[str, Set["synapse.api.UserPresenceState"]]: | |||||
``` | |||||
**Requires** `get_interested_users` to also be registered | |||||
Called when processing updates to the presence state of one or more users. This callback can | |||||
be used to instruct the server to forward that presence state to specific users. The module | |||||
must return a dictionary that maps from Matrix user IDs (which can be local or remote) to the | |||||
`UserPresenceState` changes that they should be forwarded. | |||||
Synapse will then attempt to send the specified presence updates to each user when possible. | |||||
```python | |||||
async def get_interested_users( | |||||
self, | |||||
user_id: str | |||||
) -> Union[Set[str], "synapse.module_api.PRESENCE_ALL_USERS"] | |||||
``` | |||||
**Requires** `get_users_for_states` to also be registered | |||||
Called when determining which users someone should be able to see the presence state of. This | |||||
callback should return complementary results to `get_users_for_state` or the presence information | |||||
may not be properly forwarded. | |||||
The callback is given the Matrix user ID for a local user that is requesting presence data and | |||||
should return the Matrix user IDs of the users whose presence state they are allowed to | |||||
query. The returned users can be local or remote. | |||||
Alternatively the callback can return `synapse.module_api.PRESENCE_ALL_USERS` | |||||
to indicate that the user should receive updates from all known users. | |||||
For example, if the user `@alice:example.org` is passed to this method, and the Set | |||||
`{"@bob:example.com", "@charlie:somewhere.org"}` is returned, this signifies that Alice | |||||
should receive presence updates sent by Bob and Charlie, regardless of whether these users | |||||
share a room. | |||||
### Porting an existing module that uses the old interface | ### Porting an existing module that uses the old interface | ||||
@@ -1,3 +1,9 @@ | |||||
<h2 style="color:red"> | |||||
This page of the Synapse documentation is now deprecated. For up to date | |||||
documentation on setting up or writing a presence router module, please see | |||||
<a href="modules.md">this page</a>. | |||||
</h2> | |||||
# Presence Router Module | # Presence Router Module | ||||
Synapse supports configuring a module that can specify additional users | Synapse supports configuring a module that can specify additional users | ||||
@@ -108,20 +108,6 @@ presence: | |||||
# | # | ||||
#enabled: false | #enabled: false | ||||
# Presence routers are third-party modules that can specify additional logic | |||||
# to where presence updates from users are routed. | |||||
# | |||||
presence_router: | |||||
# The custom module's class. Uncomment to use a custom presence router module. | |||||
# | |||||
#module: "my_custom_router.PresenceRouter" | |||||
# Configuration options of the custom module. Refer to your module's | |||||
# documentation for available options. | |||||
# | |||||
#config: | |||||
# example_option: 'something' | |||||
# Whether to require authentication to retrieve profile data (avatars, | # Whether to require authentication to retrieve profile data (avatars, | ||||
# display names) of other users through the client API. Defaults to | # display names) of other users through the client API. Defaults to | ||||
# 'false'. Note that profile data is also available via the federation | # 'false'. Note that profile data is also available via the federation | ||||
@@ -37,6 +37,7 @@ from synapse.app import check_bind_error | |||||
from synapse.app.phone_stats_home import start_phone_stats_home | from synapse.app.phone_stats_home import start_phone_stats_home | ||||
from synapse.config.homeserver import HomeServerConfig | from synapse.config.homeserver import HomeServerConfig | ||||
from synapse.crypto import context_factory | from synapse.crypto import context_factory | ||||
from synapse.events.presence_router import load_legacy_presence_router | |||||
from synapse.events.spamcheck import load_legacy_spam_checkers | from synapse.events.spamcheck import load_legacy_spam_checkers | ||||
from synapse.events.third_party_rules import load_legacy_third_party_event_rules | from synapse.events.third_party_rules import load_legacy_third_party_event_rules | ||||
from synapse.logging.context import PreserveLoggingContext | from synapse.logging.context import PreserveLoggingContext | ||||
@@ -370,6 +371,7 @@ async def start(hs: "HomeServer"): | |||||
load_legacy_spam_checkers(hs) | load_legacy_spam_checkers(hs) | ||||
load_legacy_third_party_event_rules(hs) | load_legacy_third_party_event_rules(hs) | ||||
load_legacy_presence_router(hs) | |||||
# If we've configured an expiry time for caches, start the background job now. | # If we've configured an expiry time for caches, start the background job now. | ||||
setup_expire_lru_cache_entries(hs) | setup_expire_lru_cache_entries(hs) | ||||
@@ -248,6 +248,7 @@ class ServerConfig(Config): | |||||
self.use_presence = config.get("use_presence", True) | self.use_presence = config.get("use_presence", True) | ||||
# Custom presence router module | # Custom presence router module | ||||
# This is the legacy way of configuring it (the config should now be put in the modules section) | |||||
self.presence_router_module_class = None | self.presence_router_module_class = None | ||||
self.presence_router_config = None | self.presence_router_config = None | ||||
presence_router_config = presence_config.get("presence_router") | presence_router_config = presence_config.get("presence_router") | ||||
@@ -858,20 +859,6 @@ class ServerConfig(Config): | |||||
# | # | ||||
#enabled: false | #enabled: false | ||||
# Presence routers are third-party modules that can specify additional logic | |||||
# to where presence updates from users are routed. | |||||
# | |||||
presence_router: | |||||
# The custom module's class. Uncomment to use a custom presence router module. | |||||
# | |||||
#module: "my_custom_router.PresenceRouter" | |||||
# Configuration options of the custom module. Refer to your module's | |||||
# documentation for available options. | |||||
# | |||||
#config: | |||||
# example_option: 'something' | |||||
# Whether to require authentication to retrieve profile data (avatars, | # Whether to require authentication to retrieve profile data (avatars, | ||||
# display names) of other users through the client API. Defaults to | # display names) of other users through the client API. Defaults to | ||||
# 'false'. Note that profile data is also available via the federation | # 'false'. Note that profile data is also available via the federation | ||||
@@ -11,45 +11,115 @@ | |||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
# See the License for the specific language governing permissions and | # See the License for the specific language governing permissions and | ||||
# limitations under the License. | # limitations under the License. | ||||
from typing import TYPE_CHECKING, Dict, Iterable, Set, Union | |||||
import logging | |||||
from typing import ( | |||||
TYPE_CHECKING, | |||||
Awaitable, | |||||
Callable, | |||||
Dict, | |||||
Iterable, | |||||
List, | |||||
Optional, | |||||
Set, | |||||
Union, | |||||
) | |||||
from synapse.api.presence import UserPresenceState | from synapse.api.presence import UserPresenceState | ||||
from synapse.util.async_helpers import maybe_awaitable | |||||
if TYPE_CHECKING: | if TYPE_CHECKING: | ||||
from synapse.server import HomeServer | from synapse.server import HomeServer | ||||
GET_USERS_FOR_STATES_CALLBACK = Callable[ | |||||
[Iterable[UserPresenceState]], Awaitable[Dict[str, Set[UserPresenceState]]] | |||||
] | |||||
GET_INTERESTED_USERS_CALLBACK = Callable[ | |||||
[str], Awaitable[Union[Set[str], "PresenceRouter.ALL_USERS"]] | |||||
] | |||||
logger = logging.getLogger(__name__) | |||||
def load_legacy_presence_router(hs: "HomeServer"): | |||||
"""Wrapper that loads a presence router module configured using the old | |||||
configuration, and registers the hooks they implement. | |||||
""" | |||||
if hs.config.presence_router_module_class is None: | |||||
return | |||||
module = hs.config.presence_router_module_class | |||||
config = hs.config.presence_router_config | |||||
api = hs.get_module_api() | |||||
presence_router = module(config=config, module_api=api) | |||||
# The known hooks. If a module implements a method which name appears in this set, | |||||
# we'll want to register it. | |||||
presence_router_methods = { | |||||
"get_users_for_states", | |||||
"get_interested_users", | |||||
} | |||||
# All methods that the module provides should be async, but this wasn't enforced | |||||
# in the old module system, so we wrap them if needed | |||||
def async_wrapper(f: Optional[Callable]) -> Optional[Callable[..., Awaitable]]: | |||||
# f might be None if the callback isn't implemented by the module. In this | |||||
# case we don't want to register a callback at all so we return None. | |||||
if f is None: | |||||
return None | |||||
def run(*args, **kwargs): | |||||
# mypy doesn't do well across function boundaries so we need to tell it | |||||
# f is definitely not None. | |||||
assert f is not None | |||||
return maybe_awaitable(f(*args, **kwargs)) | |||||
return run | |||||
# Register the hooks through the module API. | |||||
hooks = { | |||||
hook: async_wrapper(getattr(presence_router, hook, None)) | |||||
for hook in presence_router_methods | |||||
} | |||||
api.register_presence_router_callbacks(**hooks) | |||||
class PresenceRouter: | class PresenceRouter: | ||||
""" | """ | ||||
A module that the homeserver will call upon to help route user presence updates to | A module that the homeserver will call upon to help route user presence updates to | ||||
additional destinations. If a custom presence router is configured, calls will be | |||||
passed to that instead. | |||||
additional destinations. | |||||
""" | """ | ||||
ALL_USERS = "ALL" | ALL_USERS = "ALL" | ||||
def __init__(self, hs: "HomeServer"): | def __init__(self, hs: "HomeServer"): | ||||
self.custom_presence_router = None | |||||
# Initially there are no callbacks | |||||
self._get_users_for_states_callbacks: List[GET_USERS_FOR_STATES_CALLBACK] = [] | |||||
self._get_interested_users_callbacks: List[GET_INTERESTED_USERS_CALLBACK] = [] | |||||
# Check whether a custom presence router module has been configured | |||||
if hs.config.presence_router_module_class: | |||||
# Initialise the module | |||||
self.custom_presence_router = hs.config.presence_router_module_class( | |||||
config=hs.config.presence_router_config, module_api=hs.get_module_api() | |||||
def register_presence_router_callbacks( | |||||
self, | |||||
get_users_for_states: Optional[GET_USERS_FOR_STATES_CALLBACK] = None, | |||||
get_interested_users: Optional[GET_INTERESTED_USERS_CALLBACK] = None, | |||||
): | |||||
# PresenceRouter modules are required to implement both of these methods | |||||
# or neither of them as they are assumed to act in a complementary manner | |||||
paired_methods = [get_users_for_states, get_interested_users] | |||||
if paired_methods.count(None) == 1: | |||||
raise RuntimeError( | |||||
"PresenceRouter modules must register neither or both of the paired callbacks: " | |||||
"[get_users_for_states, get_interested_users]" | |||||
) | ) | ||||
# Ensure the module has implemented the required methods | |||||
required_methods = ["get_users_for_states", "get_interested_users"] | |||||
for method_name in required_methods: | |||||
if not hasattr(self.custom_presence_router, method_name): | |||||
raise Exception( | |||||
"PresenceRouter module '%s' must implement all required methods: %s" | |||||
% ( | |||||
hs.config.presence_router_module_class.__name__, | |||||
", ".join(required_methods), | |||||
) | |||||
) | |||||
# Append the methods provided to the lists of callbacks | |||||
if get_users_for_states is not None: | |||||
self._get_users_for_states_callbacks.append(get_users_for_states) | |||||
if get_interested_users is not None: | |||||
self._get_interested_users_callbacks.append(get_interested_users) | |||||
async def get_users_for_states( | async def get_users_for_states( | ||||
self, | self, | ||||
@@ -66,14 +136,40 @@ class PresenceRouter: | |||||
A dictionary of user_id -> set of UserPresenceState, indicating which | A dictionary of user_id -> set of UserPresenceState, indicating which | ||||
presence updates each user should receive. | presence updates each user should receive. | ||||
""" | """ | ||||
if self.custom_presence_router is not None: | |||||
# Ask the custom module | |||||
return await self.custom_presence_router.get_users_for_states( | |||||
state_updates=state_updates | |||||
) | |||||
# Don't include any extra destinations for presence updates | |||||
return {} | |||||
# Bail out early if we don't have any callbacks to run. | |||||
if len(self._get_users_for_states_callbacks) == 0: | |||||
# Don't include any extra destinations for presence updates | |||||
return {} | |||||
users_for_states = {} | |||||
# run all the callbacks for get_users_for_states and combine the results | |||||
for callback in self._get_users_for_states_callbacks: | |||||
try: | |||||
result = await callback(state_updates) | |||||
except Exception as e: | |||||
logger.warning("Failed to run module API callback %s: %s", callback, e) | |||||
continue | |||||
if not isinstance(result, Dict): | |||||
logger.warning( | |||||
"Wrong type returned by module API callback %s: %s, expected Dict", | |||||
callback, | |||||
result, | |||||
) | |||||
continue | |||||
for key, new_entries in result.items(): | |||||
if not isinstance(new_entries, Set): | |||||
logger.warning( | |||||
"Wrong type returned by module API callback %s: %s, expected Set", | |||||
callback, | |||||
new_entries, | |||||
) | |||||
break | |||||
users_for_states.setdefault(key, set()).update(new_entries) | |||||
return users_for_states | |||||
async def get_interested_users(self, user_id: str) -> Union[Set[str], ALL_USERS]: | async def get_interested_users(self, user_id: str) -> Union[Set[str], ALL_USERS]: | ||||
""" | """ | ||||
@@ -92,12 +188,36 @@ class PresenceRouter: | |||||
A set of user IDs to return presence updates for, or ALL_USERS to return all | A set of user IDs to return presence updates for, or ALL_USERS to return all | ||||
known updates. | known updates. | ||||
""" | """ | ||||
if self.custom_presence_router is not None: | |||||
# Ask the custom module for interested users | |||||
return await self.custom_presence_router.get_interested_users( | |||||
user_id=user_id | |||||
) | |||||
# A custom presence router is not defined. | |||||
# Don't report any additional interested users | |||||
return set() | |||||
# Bail out early if we don't have any callbacks to run. | |||||
if len(self._get_interested_users_callbacks) == 0: | |||||
# Don't report any additional interested users | |||||
return set() | |||||
interested_users = set() | |||||
# run all the callbacks for get_interested_users and combine the results | |||||
for callback in self._get_interested_users_callbacks: | |||||
try: | |||||
result = await callback(user_id) | |||||
except Exception as e: | |||||
logger.warning("Failed to run module API callback %s: %s", callback, e) | |||||
continue | |||||
# If one of the callbacks returns ALL_USERS then we can stop calling all | |||||
# of the other callbacks, since the set of interested_users is already as | |||||
# large as it can possibly be | |||||
if result == PresenceRouter.ALL_USERS: | |||||
return PresenceRouter.ALL_USERS | |||||
if not isinstance(result, Set): | |||||
logger.warning( | |||||
"Wrong type returned by module API callback %s: %s, expected set", | |||||
callback, | |||||
result, | |||||
) | |||||
continue | |||||
# Add the new interested users to the set | |||||
interested_users.update(result) | |||||
return interested_users |
@@ -32,6 +32,7 @@ from twisted.internet import defer | |||||
from twisted.web.resource import IResource | from twisted.web.resource import IResource | ||||
from synapse.events import EventBase | from synapse.events import EventBase | ||||
from synapse.events.presence_router import PresenceRouter | |||||
from synapse.http.client import SimpleHttpClient | from synapse.http.client import SimpleHttpClient | ||||
from synapse.http.server import ( | from synapse.http.server import ( | ||||
DirectServeHtmlResource, | DirectServeHtmlResource, | ||||
@@ -57,6 +58,8 @@ This package defines the 'stable' API which can be used by extension modules whi | |||||
are loaded into Synapse. | are loaded into Synapse. | ||||
""" | """ | ||||
PRESENCE_ALL_USERS = PresenceRouter.ALL_USERS | |||||
__all__ = [ | __all__ = [ | ||||
"errors", | "errors", | ||||
"make_deferred_yieldable", | "make_deferred_yieldable", | ||||
@@ -70,6 +73,7 @@ __all__ = [ | |||||
"DirectServeHtmlResource", | "DirectServeHtmlResource", | ||||
"DirectServeJsonResource", | "DirectServeJsonResource", | ||||
"ModuleApi", | "ModuleApi", | ||||
"PRESENCE_ALL_USERS", | |||||
] | ] | ||||
logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||
@@ -111,6 +115,7 @@ class ModuleApi: | |||||
self._spam_checker = hs.get_spam_checker() | self._spam_checker = hs.get_spam_checker() | ||||
self._account_validity_handler = hs.get_account_validity_handler() | self._account_validity_handler = hs.get_account_validity_handler() | ||||
self._third_party_event_rules = hs.get_third_party_event_rules() | self._third_party_event_rules = hs.get_third_party_event_rules() | ||||
self._presence_router = hs.get_presence_router() | |||||
################################################################################# | ################################################################################# | ||||
# The following methods should only be called during the module's initialisation. | # The following methods should only be called during the module's initialisation. | ||||
@@ -130,6 +135,11 @@ class ModuleApi: | |||||
"""Registers callbacks for third party event rules capabilities.""" | """Registers callbacks for third party event rules capabilities.""" | ||||
return self._third_party_event_rules.register_third_party_rules_callbacks | return self._third_party_event_rules.register_third_party_rules_callbacks | ||||
@property | |||||
def register_presence_router_callbacks(self): | |||||
"""Registers callbacks for presence router capabilities.""" | |||||
return self._presence_router.register_presence_router_callbacks | |||||
def register_web_resource(self, path: str, resource: IResource): | def register_web_resource(self, path: str, resource: IResource): | ||||
"""Registers a web resource to be served at the given path. | """Registers a web resource to be served at the given path. | ||||
@@ -17,7 +17,7 @@ from unittest.mock import Mock | |||||
import attr | import attr | ||||
from synapse.api.constants import EduTypes | from synapse.api.constants import EduTypes | ||||
from synapse.events.presence_router import PresenceRouter | |||||
from synapse.events.presence_router import PresenceRouter, load_legacy_presence_router | |||||
from synapse.federation.units import Transaction | from synapse.federation.units import Transaction | ||||
from synapse.handlers.presence import UserPresenceState | from synapse.handlers.presence import UserPresenceState | ||||
from synapse.module_api import ModuleApi | from synapse.module_api import ModuleApi | ||||
@@ -34,7 +34,7 @@ class PresenceRouterTestConfig: | |||||
users_who_should_receive_all_presence = attr.ib(type=List[str], default=[]) | users_who_should_receive_all_presence = attr.ib(type=List[str], default=[]) | ||||
class PresenceRouterTestModule: | |||||
class LegacyPresenceRouterTestModule: | |||||
def __init__(self, config: PresenceRouterTestConfig, module_api: ModuleApi): | def __init__(self, config: PresenceRouterTestConfig, module_api: ModuleApi): | ||||
self._config = config | self._config = config | ||||
self._module_api = module_api | self._module_api = module_api | ||||
@@ -77,6 +77,53 @@ class PresenceRouterTestModule: | |||||
return config | return config | ||||
class PresenceRouterTestModule: | |||||
def __init__(self, config: PresenceRouterTestConfig, api: ModuleApi): | |||||
self._config = config | |||||
self._module_api = api | |||||
api.register_presence_router_callbacks( | |||||
get_users_for_states=self.get_users_for_states, | |||||
get_interested_users=self.get_interested_users, | |||||
) | |||||
async def get_users_for_states( | |||||
self, state_updates: Iterable[UserPresenceState] | |||||
) -> Dict[str, Set[UserPresenceState]]: | |||||
users_to_state = { | |||||
user_id: set(state_updates) | |||||
for user_id in self._config.users_who_should_receive_all_presence | |||||
} | |||||
return users_to_state | |||||
async def get_interested_users( | |||||
self, user_id: str | |||||
) -> Union[Set[str], PresenceRouter.ALL_USERS]: | |||||
if user_id in self._config.users_who_should_receive_all_presence: | |||||
return PresenceRouter.ALL_USERS | |||||
return set() | |||||
@staticmethod | |||||
def parse_config(config_dict: dict) -> PresenceRouterTestConfig: | |||||
"""Parse a configuration dictionary from the homeserver config, do | |||||
some validation and return a typed PresenceRouterConfig. | |||||
Args: | |||||
config_dict: The configuration dictionary. | |||||
Returns: | |||||
A validated config object. | |||||
""" | |||||
# Initialise a typed config object | |||||
config = PresenceRouterTestConfig() | |||||
config.users_who_should_receive_all_presence = config_dict.get( | |||||
"users_who_should_receive_all_presence" | |||||
) | |||||
return config | |||||
class PresenceRouterTestCase(FederatingHomeserverTestCase): | class PresenceRouterTestCase(FederatingHomeserverTestCase): | ||||
servlets = [ | servlets = [ | ||||
admin.register_servlets, | admin.register_servlets, | ||||
@@ -86,9 +133,17 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase): | |||||
] | ] | ||||
def make_homeserver(self, reactor, clock): | def make_homeserver(self, reactor, clock): | ||||
return self.setup_test_homeserver( | |||||
hs = self.setup_test_homeserver( | |||||
federation_transport_client=Mock(spec=["send_transaction"]), | federation_transport_client=Mock(spec=["send_transaction"]), | ||||
) | ) | ||||
# Load the modules into the homeserver | |||||
module_api = hs.get_module_api() | |||||
for module, config in hs.config.modules.loaded_modules: | |||||
module(config=config, api=module_api) | |||||
load_legacy_presence_router(hs) | |||||
return hs | |||||
def prepare(self, reactor, clock, homeserver): | def prepare(self, reactor, clock, homeserver): | ||||
self.sync_handler = self.hs.get_sync_handler() | self.sync_handler = self.hs.get_sync_handler() | ||||
@@ -98,7 +153,7 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase): | |||||
{ | { | ||||
"presence": { | "presence": { | ||||
"presence_router": { | "presence_router": { | ||||
"module": __name__ + ".PresenceRouterTestModule", | |||||
"module": __name__ + ".LegacyPresenceRouterTestModule", | |||||
"config": { | "config": { | ||||
"users_who_should_receive_all_presence": [ | "users_who_should_receive_all_presence": [ | ||||
"@presence_gobbler:test", | "@presence_gobbler:test", | ||||
@@ -109,7 +164,28 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase): | |||||
"send_federation": True, | "send_federation": True, | ||||
} | } | ||||
) | ) | ||||
def test_receiving_all_presence_legacy(self): | |||||
self.receiving_all_presence_test_body() | |||||
@override_config( | |||||
{ | |||||
"modules": [ | |||||
{ | |||||
"module": __name__ + ".PresenceRouterTestModule", | |||||
"config": { | |||||
"users_who_should_receive_all_presence": [ | |||||
"@presence_gobbler:test", | |||||
] | |||||
}, | |||||
}, | |||||
], | |||||
"send_federation": True, | |||||
} | |||||
) | |||||
def test_receiving_all_presence(self): | def test_receiving_all_presence(self): | ||||
self.receiving_all_presence_test_body() | |||||
def receiving_all_presence_test_body(self): | |||||
"""Test that a user that does not share a room with another other can receive | """Test that a user that does not share a room with another other can receive | ||||
presence for them, due to presence routing. | presence for them, due to presence routing. | ||||
""" | """ | ||||
@@ -203,7 +279,7 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase): | |||||
{ | { | ||||
"presence": { | "presence": { | ||||
"presence_router": { | "presence_router": { | ||||
"module": __name__ + ".PresenceRouterTestModule", | |||||
"module": __name__ + ".LegacyPresenceRouterTestModule", | |||||
"config": { | "config": { | ||||
"users_who_should_receive_all_presence": [ | "users_who_should_receive_all_presence": [ | ||||
"@presence_gobbler1:test", | "@presence_gobbler1:test", | ||||
@@ -216,7 +292,30 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase): | |||||
"send_federation": True, | "send_federation": True, | ||||
} | } | ||||
) | ) | ||||
def test_send_local_online_presence_to_with_module_legacy(self): | |||||
self.send_local_online_presence_to_with_module_test_body() | |||||
@override_config( | |||||
{ | |||||
"modules": [ | |||||
{ | |||||
"module": __name__ + ".PresenceRouterTestModule", | |||||
"config": { | |||||
"users_who_should_receive_all_presence": [ | |||||
"@presence_gobbler1:test", | |||||
"@presence_gobbler2:test", | |||||
"@far_away_person:island", | |||||
] | |||||
}, | |||||
}, | |||||
], | |||||
"send_federation": True, | |||||
} | |||||
) | |||||
def test_send_local_online_presence_to_with_module(self): | def test_send_local_online_presence_to_with_module(self): | ||||
self.send_local_online_presence_to_with_module_test_body() | |||||
def send_local_online_presence_to_with_module_test_body(self): | |||||
"""Tests that send_local_presence_to_users sends local online presence to a set | """Tests that send_local_presence_to_users sends local online presence to a set | ||||
of specified local and remote users, with a custom PresenceRouter module enabled. | of specified local and remote users, with a custom PresenceRouter module enabled. | ||||
""" | """ | ||||