Add unit tests

This commit is contained in:
Andrew Morgan
2026-07-22 17:15:20 +01:00
parent 3635fc0665
commit d9e07da7f0
2 changed files with 430 additions and 2 deletions
+117 -1
View File
@@ -19,8 +19,9 @@
#
#
import logging
import urllib.parse
from http import HTTPStatus
from unittest.mock import Mock
from unittest.mock import AsyncMock, Mock
from parameterized import parameterized
@@ -39,7 +40,9 @@ from synapse.rest.client import login, room
from synapse.server import HomeServer
from synapse.storage.controllers.state import server_acl_evaluator_from_event
from synapse.types import JsonDict
from synapse.types.state import StateEventQuery
from synapse.util.clock import Clock
from synapse.util.json import json_encoder
from tests import unittest
from tests.test_utils.event_builders import make_test_event, make_test_pdu_event
@@ -76,6 +79,119 @@ class FederationServerTests(unittest.FederatingHomeserverTestCase):
self.assertEqual(HTTPStatus.BAD_REQUEST, channel.code, channel.result)
self.assertEqual(channel.json_body["errcode"], "M_NOT_JSON")
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_hierarchy_additional_state_query_parameter(self) -> None:
"""The federation hierarchy servlet parses repeated MSC4507 JSON params."""
u1 = self.register_user("u1", "pass")
u1_token = self.login("u1", "pass")
room_id = self.helper.create_room_as(u1, tok=u1_token)
event_type = "m.some_event"
self.helper.send_state(
room_id,
event_type=event_type,
body={"foo": ["bar"]},
tok=u1_token,
)
self.helper.send_state(
room_id,
event_type=EventTypes.MSC4507PublicState,
body={"public_state": {event_type: {"state_keys": [""]}}},
tok=u1_token,
)
query = urllib.parse.urlencode(
[
(
"org.matrix.msc4507.additional_state",
json_encoder.encode({"type": event_type, "state_key": ""}),
)
]
)
channel = self.make_signed_federation_request(
"GET", f"/_matrix/federation/v1/hierarchy/{room_id}?{query}"
)
self.assertEqual(HTTPStatus.OK, channel.code, channel.result)
self.assertEqual(
channel.json_body["room"]["org.matrix.msc4507.additional_state"],
[
{
"type": event_type,
"state_key": "",
"content": {"foo": ["bar"]},
"sender": u1,
}
],
)
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_hierarchy_additional_state_percent_encoded_state_key(self) -> None:
"""MSC4507 query values are percent-decoded exactly once."""
room_id = "!unknown:test"
event_type = "m.some_event_type"
state_key = "%22"
room_summary_handler = self.hs.get_room_summary_handler()
room_summary_handler.get_federation_hierarchy = AsyncMock(return_value={}) # type: ignore[method-assign]
query = urllib.parse.urlencode(
[
(
"org.matrix.msc4507.additional_state",
json_encoder.encode({"type": event_type, "state_key": state_key}),
)
]
)
self.assertIn("%2522", query)
channel = self.make_signed_federation_request(
"GET", f"/_matrix/federation/v1/hierarchy/{room_id}?{query}"
)
self.assertEqual(HTTPStatus.OK, channel.code, channel.result)
room_summary_handler.get_federation_hierarchy.assert_awaited_once_with(
self.OTHER_SERVER_NAME,
room_id,
False,
(StateEventQuery(event_type, state_key),),
)
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_hierarchy_additional_state_rejects_non_string_state_key(self) -> None:
"""MSC4507 query objects must omit state_key or use a string."""
query = urllib.parse.urlencode(
[
(
"org.matrix.msc4507.additional_state",
json_encoder.encode(
{"type": "m.some_event_type", "state_key": None}
),
)
]
)
channel = self.make_signed_federation_request(
"GET", f"/_matrix/federation/v1/hierarchy/!unknown:test?{query}"
)
self.assertEqual(HTTPStatus.BAD_REQUEST, channel.code, channel.result)
self.assertEqual(channel.json_body["errcode"], "M_INVALID_PARAM")
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_hierarchy_additional_state_rejects_invalid_json(self) -> None:
"""MSC4507 query objects must be valid JSON."""
query = urllib.parse.urlencode(
[("org.matrix.msc4507.additional_state", "not json")]
)
channel = self.make_signed_federation_request(
"GET", f"/_matrix/federation/v1/hierarchy/!unknown:test?{query}"
)
self.assertEqual(HTTPStatus.BAD_REQUEST, channel.code, channel.result)
self.assertEqual(channel.json_body["errcode"], "M_NOT_JSON")
def test_failed_edu_causes_500(self) -> None:
"""If the EDU handler fails, /send should return a 500."""
+313 -1
View File
@@ -36,11 +36,17 @@ from synapse.api.constants import (
from synapse.api.errors import AuthError, NotFoundError, SynapseError
from synapse.api.room_versions import RoomVersions
from synapse.federation.transport.client import TransportLayerClient
from synapse.handlers.room_summary import _child_events_comparison_key, _RoomEntry
from synapse.handlers.room_summary import (
_child_events_comparison_key,
_get_permitted_public_state,
_parse_public_state,
_RoomEntry,
)
from synapse.rest import admin
from synapse.rest.client import login, room
from synapse.server import HomeServer
from synapse.types import JsonDict, UserID, create_requester
from synapse.types.state import StateEventQuery, StateFilter
from synapse.util.clock import Clock
from tests import unittest
@@ -120,6 +126,62 @@ class TestSpaceSummarySort(unittest.TestCase):
self.assertEqual([ev2, ev1], _order(ev1, ev2))
class PublicStateTestCase(unittest.TestCase):
def test_parse_public_state(self) -> None:
"""Public state rules are validated and normalized independently."""
public_state = _parse_public_state(
{
"wildcard": {},
"limited": {"state_keys": ["one", "two", "one"]},
"empty": {"state_keys": []},
"invalid_rule": [],
"invalid_state_keys": {"state_keys": ["one", 2]},
}
)
self.assertEqual(
public_state,
StateFilter.from_types(
[("wildcard", None), ("limited", "one"), ("limited", "two")]
),
)
def test_get_permitted_public_state(self) -> None:
"""Requested state is intersected with normalized public state rules."""
public_state = StateFilter.from_types(
[
("wildcard", None),
("limited", "one"),
("limited", "two"),
("overlap", "one"),
("overlap", "two"),
]
)
permitted_state = _get_permitted_public_state(
[
StateEventQuery("wildcard", "requested"),
StateEventQuery("limited"),
StateEventQuery("overlap", "two"),
StateEventQuery("overlap", "three"),
StateEventQuery("private"),
],
public_state,
)
self.assertEqual(
permitted_state,
StateFilter.from_types(
[
("wildcard", "requested"),
("limited", "one"),
("limited", "two"),
("overlap", "two"),
]
),
)
class SpaceSummaryTestCase(unittest.HomeserverTestCase):
servlets = [
admin.register_servlets_for_client_rest_resource,
@@ -246,6 +308,255 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
)
self._assert_hierarchy(result, expected)
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_federation_hierarchy_additional_state(self) -> None:
"""Federation hierarchy can include requested public state for each room."""
event_type = "m.security_labels"
self.helper.send_state(
self.space,
event_type=event_type,
body={"labels": ["space-label"]},
tok=self.token,
)
self.helper.send_state(
self.room,
event_type=event_type,
body={"labels": ["room-label"]},
tok=self.token,
)
for room_id in (self.space, self.room):
self.helper.send_state(
room_id,
event_type=EventTypes.MSC4507PublicState,
body={"public_state": {event_type: {"state_keys": [""]}}},
tok=self.token,
)
result = self.get_success(
self.handler.get_federation_hierarchy(
"remote.example.com",
self.space,
suggested_only=False,
additional_state=(StateEventQuery(event_type, ""),),
)
)
self.assertEqual(
result["room"]["org.matrix.msc4507.additional_state"],
[
{
"type": event_type,
"state_key": "",
"content": {"labels": ["space-label"]},
"sender": self.user,
}
],
)
self.assertEqual(
result["children"][0]["org.matrix.msc4507.additional_state"],
[
{
"type": event_type,
"state_key": "",
"content": {"labels": ["room-label"]},
"sender": self.user,
}
],
)
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_federation_hierarchy_additional_state_requires_public_state(
self,
) -> None:
"""Requested state is omitted if the room has not declared it public."""
event_type = "m.security_labels"
self.helper.send_state(
self.space,
event_type=event_type,
body={"labels": ["secret"]},
tok=self.token,
)
result = self.get_success(
self.handler.get_federation_hierarchy(
"remote.example.com",
self.space,
suggested_only=False,
additional_state=(StateEventQuery(event_type, ""),),
)
)
self.assertEqual(result["room"]["org.matrix.msc4507.additional_state"], [])
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_federation_hierarchy_additional_state_supports_state_key_wildcard(
self,
) -> None:
"""Omitting state_key requests all public state keys for that type."""
event_type = "com.example.some_state"
self.helper.send_state(
self.space,
event_type=event_type,
state_key="allowed",
body={"value": "allowed"},
tok=self.token,
)
self.helper.send_state(
self.space,
event_type=event_type,
state_key="private",
body={"value": "private"},
tok=self.token,
)
self.helper.send_state(
self.space,
event_type=EventTypes.MSC4507PublicState,
body={"public_state": {event_type: {}}},
tok=self.token,
)
result = self.get_success(
self.handler.get_federation_hierarchy(
"remote.example.com",
self.space,
suggested_only=False,
additional_state=(StateEventQuery(event_type),),
)
)
self.assertEqual(
result["room"]["org.matrix.msc4507.additional_state"],
[
{
"type": event_type,
"state_key": "allowed",
"content": {"value": "allowed"},
"sender": self.user,
},
{
"type": event_type,
"state_key": "private",
"content": {"value": "private"},
"sender": self.user,
},
],
)
self.helper.send_state(
self.space,
event_type=EventTypes.MSC4507PublicState,
body={"public_state": {event_type: {"state_keys": ["allowed"]}}},
tok=self.token,
)
result = self.get_success(
self.handler.get_federation_hierarchy(
"remote.example.com",
self.space,
suggested_only=False,
additional_state=(StateEventQuery(event_type),),
)
)
self.assertEqual(
result["room"]["org.matrix.msc4507.additional_state"],
[
{
"type": event_type,
"state_key": "allowed",
"content": {"value": "allowed"},
"sender": self.user,
}
],
)
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_federation_hierarchy_additional_state_omitted_without_query(
self,
) -> None:
"""The federation hierarchy response is unchanged without the MSC query."""
event_type = "m.security_labels"
self.helper.send_state(
self.space,
event_type=event_type,
body={"labels": ["public"]},
tok=self.token,
)
self.helper.send_state(
self.space,
event_type=EventTypes.MSC4507PublicState,
body={"public_state": {event_type: {"state_keys": [""]}}},
tok=self.token,
)
result = self.get_success(
self.handler.get_federation_hierarchy(
"remote.example.com", self.space, suggested_only=False
)
)
self.assertNotIn("org.matrix.msc4507.additional_state", result["room"])
def test_federation_hierarchy_additional_state_disabled_by_default(
self,
) -> None:
"""The MSC4507 hierarchy response field is gated by experimental config."""
event_type = "m.security_labels"
self.helper.send_state(
self.space,
event_type=event_type,
body={"labels": ["public"]},
tok=self.token,
)
self.helper.send_state(
self.space,
event_type=EventTypes.MSC4507PublicState,
body={"public_state": {event_type: {"state_keys": [""]}}},
tok=self.token,
)
result = self.get_success(
self.handler.get_federation_hierarchy(
"remote.example.com",
self.space,
suggested_only=False,
additional_state=(StateEventQuery(event_type, ""),),
)
)
self.assertNotIn("org.matrix.msc4507.additional_state", result["room"])
@override_config({"experimental_features": {"msc4507_enabled": True}})
def test_msc4507_public_state_default_power_level(self) -> None:
"""New rooms require PL100 to send the MSC4507 public-state event."""
state_ids = self.get_success(
self.hs.get_storage_controllers().state.get_current_state_ids(self.space)
)
power_levels = self.get_success(
self.hs.get_datastores().main.get_event(
state_ids[(EventTypes.PowerLevels, "")]
)
)
self.assertEqual(
power_levels.content["events"][EventTypes.MSC4507PublicState], 100
)
def test_msc4507_public_state_default_power_level_disabled_by_default(
self,
) -> None:
"""The MSC4507 public-state PL rule is gated by experimental config."""
state_ids = self.get_success(
self.hs.get_storage_controllers().state.get_current_state_ids(self.space)
)
power_levels = self.get_success(
self.hs.get_datastores().main.get_event(
state_ids[(EventTypes.PowerLevels, "")]
)
)
self.assertNotIn(EventTypes.MSC4507PublicState, power_levels.content["events"])
@override_config({"rc_room_creation": {"burst_count": 1000, "per_second": 1}})
def test_large_space(self) -> None:
"""Test a space with a large number of rooms."""
@@ -1024,6 +1335,7 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
destination: str,
room_id: str,
suggested_only: bool,
additional_state: Iterable[StateEventQuery] = (),
) -> JsonDict:
nonlocal federation_requests
federation_requests += 1