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.
This commit is contained in:
Andrew Morgan
2026-01-16 19:21:38 +00:00
parent 55ca2be0f6
commit 41922415ba
4 changed files with 119 additions and 31 deletions
@@ -30,6 +30,7 @@ dev =
twisted twisted
aiounittest aiounittest
coverage coverage
parameterized
# for type checking # for type checking
mypy == 1.10.0 mypy == 1.10.0
pydantic == 2.4.2 pydantic == 2.4.2
@@ -9,8 +9,8 @@
import sqlite3 import sqlite3
from asyncio import Future from asyncio import Future
from typing import Any, Awaitable, Callable, Dict, Tuple, TypeVar from typing import Any, Awaitable, Callable, Dict, List, Tuple, TypeVar
from unittest.mock import Mock from unittest.mock import AsyncMock, Mock
from synapse.http.client import SimpleHttpClient from synapse.http.client import SimpleHttpClient
from synapse.module_api import ModuleApi from synapse.module_api import ModuleApi
@@ -81,6 +81,20 @@ def make_awaitable(result: TV) -> Awaitable[TV]:
return future 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: def get_qualified_user_id(username: str) -> str:
return f"@{username}:matrix.local" return f"@{username}:matrix.local"
@@ -129,6 +143,17 @@ def create_module(
return module, module_api, store 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: def _setup_db(conn: sqlite3.Connection) -> None:
conn.execute("CREATE TABLE access_tokens(user_id text, token text)") conn.execute("CREATE TABLE access_tokens(user_id text, token text)")
conn.execute( conn.execute(
@@ -7,17 +7,20 @@
# Originally licensed under the Apache License, Version 2.0: # Originally licensed under the Apache License, Version 2.0:
# <http://www.apache.org/licenses/LICENSE-2.0>. # <http://www.apache.org/licenses/LICENSE-2.0>.
from typing import Tuple
from unittest.mock import Mock
import aiounittest import aiounittest
from parameterized import parameterized_class # type: ignore[import-untyped]
from synapse.module_api import ProfileInfo, UserProfile from synapse.module_api import ProfileInfo, UserProfile
from synapse.module_api.errors import ConfigError from synapse.module_api.errors import ConfigError
from synapse.types import UserID from synapse.types import UserID
from synapse_guest_module.config import GuestModuleConfig, MasConfig from synapse_guest_module.config import GuestModuleConfig, MasConfig
from synapse_guest_module.guest_module import GuestModule 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: async def test_parse_config_empty(self) -> None:
config = GuestModule.parse_config({}) 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: async def test_profile_update_no_guest(self) -> None:
module, module_api, _ = create_module() module, module_api, _ = self.create_module()
await module.profile_update( await module.profile_update(
"@my-user:matrix.local", "@my-user:matrix.local",
@@ -134,7 +149,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase):
module_api.set_displayname.assert_not_called() module_api.set_displayname.assert_not_called()
async def test_profile_update_guest_keep(self) -> None: async def test_profile_update_guest_keep(self) -> None:
module, module_api, _ = create_module() module, module_api, _ = self.create_module()
await module.profile_update( await module.profile_update(
"@guest-asdf:matrix.local", "@guest-asdf:matrix.local",
@@ -146,7 +161,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase):
module_api.set_displayname.assert_not_called() module_api.set_displayname.assert_not_called()
async def test_profile_update_guest_add_and_trim(self) -> None: 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( await module.profile_update(
"@guest-asdf:matrix.local", "@guest-asdf:matrix.local",
@@ -161,7 +176,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase):
) )
async def test_callback_user_may_create_room_no_guest(self) -> None: 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( allow = await module.callback_user_may_create_room(
"@my-user:matrix.local", "@my-user:matrix.local",
@@ -170,7 +185,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase):
self.assertTrue(allow) self.assertTrue(allow)
async def test_callback_user_may_create_room_guest(self) -> None: 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( allow = await module.callback_user_may_create_room(
"@guest-asdf:matrix.local", "@guest-asdf:matrix.local",
@@ -179,7 +194,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase):
self.assertFalse(allow) self.assertFalse(allow)
async def test_callback_user_may_invite_no_guest(self) -> None: 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( allow = await module.callback_user_may_invite(
"@my-user:matrix.local", "@my-user:matrix.local",
@@ -190,7 +205,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase):
self.assertTrue(allow) self.assertTrue(allow)
async def test_callback_user_may_invite_guest(self) -> None: async def test_callback_user_may_invite_guest(self) -> None:
module, _, _ = create_module() module, _, _ = self.create_module()
allow = await module.callback_user_may_invite( allow = await module.callback_user_may_invite(
"@guest-asdf:matrix.local", "@guest-asdf:matrix.local",
@@ -201,7 +216,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase):
self.assertFalse(allow) self.assertFalse(allow)
async def test_callback_check_username_for_spam_no_guest(self) -> None: 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( allow = await module.callback_check_username_for_spam(
UserProfile( UserProfile(
@@ -214,7 +229,7 @@ class GuestModuleTest(aiounittest.AsyncTestCase):
self.assertFalse(allow) self.assertFalse(allow)
async def test_callback_check_username_for_spam_guest(self) -> None: 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( allow = await module.callback_check_username_for_spam(
UserProfile( UserProfile(
@@ -8,19 +8,38 @@
# <http://www.apache.org/licenses/LICENSE-2.0>. # <http://www.apache.org/licenses/LICENSE-2.0>.
import io import io
from typing import cast from typing import Tuple, cast
from unittest.mock import ANY from unittest.mock import ANY, Mock
import aiounittest import aiounittest
from parameterized import parameterized_class # type: ignore[import-untyped]
from twisted.web.server import Request from twisted.web.server import Request
from twisted.web.test.requesthelper import DummyRequest 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): 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: async def test_async_render_POST_missing_displayname(self) -> None:
module, _, _ = create_module() module, _, _ = self.create_module()
request = cast(Request, DummyRequest([])) request = cast(Request, DummyRequest([]))
request.content = io.BytesIO(b"{}") request.content = io.BytesIO(b"{}")
@@ -33,7 +52,7 @@ class GuestUserReaperTest(aiounittest.AsyncTestCase):
) )
async def test_async_render_POST_empty_displayname(self) -> None: async def test_async_render_POST_empty_displayname(self) -> None:
module, _, _ = create_module() module, _, _ = self.create_module()
request = cast(Request, DummyRequest([])) request = cast(Request, DummyRequest([]))
request.content = io.BytesIO(b'{"displayname":" "}') 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: 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 = cast(Request, DummyRequest([]))
request.content = io.BytesIO(b'{"displayname":"My Name"}') 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) self.assertEqual(module_api.check_user_exists.call_count, 10)
async def test_async_render_POST_success(self) -> None: 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 = cast(Request, DummyRequest([]))
request.content = io.BytesIO(b'{"displayname":"My Name "}') 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) status, response = await module.registration_servlet._async_render_POST(request)
self.assertEqual(status, 201) self.assertEqual(status, 201)
self.assertRegex(response.pop("userId"), r"^@guest-[A-Za-z0-9]+:matrix.local$") self.assertRegex(response.pop("userId"), r"^@guest-[A-Za-z0-9]+:matrix.local$")
self.assertDictEqual( if self.config_override is None:
response, self.assertDictEqual(
{ response,
"accessToken": "syn_registered_token", {
"deviceId": "DEVICEID", "accessToken": "syn_registered_token",
"homeserverUrl": "https://matrix.local:1234/", "deviceId": "DEVICEID",
# "userId" was already checked by self.assertRegex and was removed from the object "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)") 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
},
)