From 41922415badb87dd993f9ec006cbaf68a30748be Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 16 Jan 2026 19:20:13 +0000 Subject: [PATCH] Update tests to run with MAS module variant We now run the standard battery of tests with a MAS-enabled module. Some minimal adjustment was needed to check MAS-specific outputs. --- modules/restricted-guests/synapse/setup.cfg | 1 + .../synapse/tests/__init__.py | 29 ++++++- .../synapse/tests/test_guest_module.py | 37 ++++++--- .../tests/test_guest_registration_servlet.py | 83 +++++++++++++++---- 4 files changed, 119 insertions(+), 31 deletions(-) diff --git a/modules/restricted-guests/synapse/setup.cfg b/modules/restricted-guests/synapse/setup.cfg index 75fead2743..d190edbb11 100644 --- a/modules/restricted-guests/synapse/setup.cfg +++ b/modules/restricted-guests/synapse/setup.cfg @@ -30,6 +30,7 @@ dev = twisted aiounittest coverage + parameterized # for type checking mypy == 1.10.0 pydantic == 2.4.2 diff --git a/modules/restricted-guests/synapse/tests/__init__.py b/modules/restricted-guests/synapse/tests/__init__.py index d6bc7b5f3f..d79a60a891 100644 --- a/modules/restricted-guests/synapse/tests/__init__.py +++ b/modules/restricted-guests/synapse/tests/__init__.py @@ -9,8 +9,8 @@ import sqlite3 from asyncio import Future -from typing import Any, Awaitable, Callable, Dict, Tuple, TypeVar -from unittest.mock import Mock +from typing import Any, Awaitable, Callable, Dict, List, Tuple, TypeVar +from unittest.mock import AsyncMock, Mock from synapse.http.client import SimpleHttpClient from synapse.module_api import ModuleApi @@ -81,6 +81,20 @@ def make_awaitable(result: TV) -> Awaitable[TV]: return future +def set_async_return_value(target: Any, value: Any) -> None: + if isinstance(target, AsyncMock): + target.return_value = value + else: + target.return_value = make_awaitable(value) + + +def set_async_side_effect(target: Any, values: List[Any]) -> None: + if isinstance(target, AsyncMock): + target.side_effect = values + else: + target.side_effect = [make_awaitable(value) for value in values] + + def get_qualified_user_id(username: str) -> str: return f"@{username}:matrix.local" @@ -129,6 +143,17 @@ def create_module( return module, module_api, store +def mas_config_override() -> Dict[str, Any]: + return { + "mas": { + "admin_api_base_url": "https://mas.example.org", + "oauth_base_url": "https://oauth.mas.example.org", + "client_id": "client-id", + "client_secret": "client-secret", + }, + } + + def _setup_db(conn: sqlite3.Connection) -> None: conn.execute("CREATE TABLE access_tokens(user_id text, token text)") conn.execute( diff --git a/modules/restricted-guests/synapse/tests/test_guest_module.py b/modules/restricted-guests/synapse/tests/test_guest_module.py index 01605528a0..10e51a178e 100644 --- a/modules/restricted-guests/synapse/tests/test_guest_module.py +++ b/modules/restricted-guests/synapse/tests/test_guest_module.py @@ -7,17 +7,20 @@ # Originally licensed under the Apache License, Version 2.0: # . +from typing import Tuple +from unittest.mock import Mock import aiounittest +from parameterized import parameterized_class # type: ignore[import-untyped] from synapse.module_api import ProfileInfo, UserProfile from synapse.module_api.errors import ConfigError from synapse.types import UserID from synapse_guest_module.config import GuestModuleConfig, MasConfig from synapse_guest_module.guest_module import GuestModule -from tests import create_module +from tests import SQLiteStore, create_module, mas_config_override -class GuestModuleTest(aiounittest.AsyncTestCase): +class GuestModuleConfigTest(aiounittest.AsyncTestCase): async def test_parse_config_empty(self) -> None: config = GuestModule.parse_config({}) @@ -121,8 +124,20 @@ class GuestModuleTest(aiounittest.AsyncTestCase): } ) + +@parameterized_class( + ("variant", "config_override"), + [ + ("synapse", None), + ("mas", mas_config_override()), + ], +) +class GuestModuleRuntimeTest(aiounittest.AsyncTestCase): + def create_module(self) -> Tuple[GuestModule, Mock, SQLiteStore]: + return create_module(self.config_override) + async def test_profile_update_no_guest(self) -> None: - module, module_api, _ = create_module() + module, module_api, _ = self.create_module() await module.profile_update( "@my-user:matrix.local", @@ -134,7 +149,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase): module_api.set_displayname.assert_not_called() async def test_profile_update_guest_keep(self) -> None: - module, module_api, _ = create_module() + module, module_api, _ = self.create_module() await module.profile_update( "@guest-asdf:matrix.local", @@ -146,7 +161,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase): module_api.set_displayname.assert_not_called() async def test_profile_update_guest_add_and_trim(self) -> None: - module, module_api, _ = create_module() + module, module_api, _ = self.create_module() await module.profile_update( "@guest-asdf:matrix.local", @@ -161,7 +176,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase): ) async def test_callback_user_may_create_room_no_guest(self) -> None: - module, _, _ = create_module() + module, _, _ = self.create_module() allow = await module.callback_user_may_create_room( "@my-user:matrix.local", @@ -170,7 +185,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase): self.assertTrue(allow) async def test_callback_user_may_create_room_guest(self) -> None: - module, _, _ = create_module() + module, _, _ = self.create_module() allow = await module.callback_user_may_create_room( "@guest-asdf:matrix.local", @@ -179,7 +194,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase): self.assertFalse(allow) async def test_callback_user_may_invite_no_guest(self) -> None: - module, _, _ = create_module() + module, _, _ = self.create_module() allow = await module.callback_user_may_invite( "@my-user:matrix.local", @@ -190,7 +205,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase): self.assertTrue(allow) async def test_callback_user_may_invite_guest(self) -> None: - module, _, _ = create_module() + module, _, _ = self.create_module() allow = await module.callback_user_may_invite( "@guest-asdf:matrix.local", @@ -201,7 +216,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase): self.assertFalse(allow) async def test_callback_check_username_for_spam_no_guest(self) -> None: - module, _, _ = create_module() + module, _, _ = self.create_module() allow = await module.callback_check_username_for_spam( UserProfile( @@ -214,7 +229,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase): self.assertFalse(allow) async def test_callback_check_username_for_spam_guest(self) -> None: - module, _, _ = create_module() + module, _, _ = self.create_module() allow = await module.callback_check_username_for_spam( UserProfile( diff --git a/modules/restricted-guests/synapse/tests/test_guest_registration_servlet.py b/modules/restricted-guests/synapse/tests/test_guest_registration_servlet.py index f0a63b7a62..402b52020e 100644 --- a/modules/restricted-guests/synapse/tests/test_guest_registration_servlet.py +++ b/modules/restricted-guests/synapse/tests/test_guest_registration_servlet.py @@ -8,19 +8,38 @@ # . import io -from typing import cast -from unittest.mock import ANY +from typing import Tuple, cast +from unittest.mock import ANY, Mock import aiounittest +from parameterized import parameterized_class # type: ignore[import-untyped] from twisted.web.server import Request from twisted.web.test.requesthelper import DummyRequest +from synapse_guest_module import GuestModule -from tests import create_module, make_awaitable +from tests import ( + SQLiteStore, + create_module, + make_awaitable, + mas_config_override, + set_async_return_value, + set_async_side_effect, +) +@parameterized_class( + ("variant", "config_override"), + [ + ("synapse", None), + ("mas", mas_config_override()), + ], +) class GuestUserReaperTest(aiounittest.AsyncTestCase): + def create_module(self) -> Tuple[GuestModule, Mock, SQLiteStore]: + return create_module(self.config_override) + async def test_async_render_POST_missing_displayname(self) -> None: - module, _, _ = create_module() + module, _, _ = self.create_module() request = cast(Request, DummyRequest([])) request.content = io.BytesIO(b"{}") @@ -33,7 +52,7 @@ class GuestUserReaperTest(aiounittest.AsyncTestCase): ) async def test_async_render_POST_empty_displayname(self) -> None: - module, _, _ = create_module() + module, _, _ = self.create_module() request = cast(Request, DummyRequest([])) request.content = io.BytesIO(b'{"displayname":" "}') @@ -46,7 +65,7 @@ class GuestUserReaperTest(aiounittest.AsyncTestCase): ) async def test_async_render_POST_no_free_username(self) -> None: - module, module_api, _ = create_module() + module, module_api, _ = self.create_module() request = cast(Request, DummyRequest([])) request.content = io.BytesIO(b'{"displayname":"My Name"}') @@ -63,24 +82,52 @@ class GuestUserReaperTest(aiounittest.AsyncTestCase): self.assertEqual(module_api.check_user_exists.call_count, 10) async def test_async_render_POST_success(self) -> None: - module, module_api, _ = create_module() + module, module_api, _ = self.create_module() request = cast(Request, DummyRequest([])) request.content = io.BytesIO(b'{"displayname":"My Name "}') + if self.config_override is not None: + set_async_return_value( + module_api.http_client.post_urlencoded_get_json, + {"access_token": "mas_admin_token"}, + ) + set_async_side_effect( + module_api.http_client.post_json_get_json, + [ + {"data": {"id": "mas-user-id"}}, + { + "data": { + "id": "MASDEVICE", + "attributes": {"access_token": "mas_access_token"}, + } + }, + ], + ) + status, response = await module.registration_servlet._async_render_POST(request) self.assertEqual(status, 201) self.assertRegex(response.pop("userId"), r"^@guest-[A-Za-z0-9]+:matrix.local$") - self.assertDictEqual( - response, - { - "accessToken": "syn_registered_token", - "deviceId": "DEVICEID", - "homeserverUrl": "https://matrix.local:1234/", - # "userId" was already checked by self.assertRegex and was removed from the object - }, - ) - - module_api.register_user.assert_called_with(ANY, "My Name (Guest)") + if self.config_override is None: + self.assertDictEqual( + response, + { + "accessToken": "syn_registered_token", + "deviceId": "DEVICEID", + "homeserverUrl": "https://matrix.local:1234/", + # "userId" was already checked by self.assertRegex and was removed from the object + }, + ) + module_api.register_user.assert_called_with(ANY, "My Name (Guest)") + else: + self.assertDictEqual( + response, + { + "accessToken": "mas_access_token", + "deviceId": "MASDEVICE", + "homeserverUrl": "https://matrix.local:1234/", + # "userId" was already checked by self.assertRegex and was removed from the object + }, + )