|
15 | 15 | from typing import Dict
|
16 | 16 | from unittest.mock import Mock
|
17 | 17 |
|
| 18 | +from synapse.api.constants import EventTypes |
18 | 19 | from synapse.events import EventBase
|
19 | 20 | from synapse.events.third_party_rules import load_legacy_third_party_event_rules
|
20 | 21 | from synapse.module_api import ModuleApi
|
@@ -327,3 +328,86 @@ def test_legacy_on_create_room(self):
|
327 | 328 | correctly.
|
328 | 329 | """
|
329 | 330 | self.helper.create_room_as(self.user_id, tok=self.tok, expect_code=403)
|
| 331 | + |
| 332 | + def test_sent_event_end_up_in_room_state(self): |
| 333 | + """Tests that a state event sent by a module while processing another state event |
| 334 | + doesn't get dropped from the state of the room. This is to guard against a bug |
| 335 | + where Synapse has been observed doing so, see https://github.com/matrix-org/synapse/issues/10830 |
| 336 | + """ |
| 337 | + event_type = "org.matrix.test_state" |
| 338 | + |
| 339 | + # This content will be updated later on, and since we actually use a reference on |
| 340 | + # the dict it does the right thing. It's a bit hacky but a handy way of making |
| 341 | + # sure the state actually gets updated. |
| 342 | + event_content = {"i": -1} |
| 343 | + |
| 344 | + api = self.hs.get_module_api() |
| 345 | + |
| 346 | + # Define a callback that sends a custom event on power levels update. |
| 347 | + async def test_fn(event: EventBase, state_events): |
| 348 | + if event.is_state and event.type == EventTypes.PowerLevels: |
| 349 | + await api.create_and_send_event_into_room( |
| 350 | + { |
| 351 | + "room_id": event.room_id, |
| 352 | + "sender": event.sender, |
| 353 | + "type": event_type, |
| 354 | + "content": event_content, |
| 355 | + "state_key": "", |
| 356 | + } |
| 357 | + ) |
| 358 | + return True, None |
| 359 | + |
| 360 | + self.hs.get_third_party_event_rules()._check_event_allowed_callbacks = [test_fn] |
| 361 | + |
| 362 | + # Sometimes the bug might not happen the first time the event type is added |
| 363 | + # to the state but might happen when an event updates the state of the room for |
| 364 | + # that type, so we test updating the state several times. |
| 365 | + for i in range(5): |
| 366 | + # Update the content of the custom state event to be sent by the callback. |
| 367 | + event_content["i"] = i |
| 368 | + |
| 369 | + # Update the room's power levels with a different value each time so Synapse |
| 370 | + # doesn't consider an update redundant. |
| 371 | + self._update_power_levels(event_default=i) |
| 372 | + |
| 373 | + # Check that the new event made it to the room's state. |
| 374 | + channel = self.make_request( |
| 375 | + method="GET", |
| 376 | + path="/rooms/" + self.room_id + "/state/" + event_type, |
| 377 | + access_token=self.tok, |
| 378 | + ) |
| 379 | + |
| 380 | + self.assertEqual(channel.code, 200) |
| 381 | + self.assertEqual(channel.json_body["i"], i) |
| 382 | + |
| 383 | + def _update_power_levels(self, event_default: int = 0): |
| 384 | + """Updates the room's power levels. |
| 385 | +
|
| 386 | + Args: |
| 387 | + event_default: Value to use for 'events_default'. |
| 388 | + """ |
| 389 | + self.helper.send_state( |
| 390 | + room_id=self.room_id, |
| 391 | + event_type=EventTypes.PowerLevels, |
| 392 | + body={ |
| 393 | + "ban": 50, |
| 394 | + "events": { |
| 395 | + "m.room.avatar": 50, |
| 396 | + "m.room.canonical_alias": 50, |
| 397 | + "m.room.encryption": 100, |
| 398 | + "m.room.history_visibility": 100, |
| 399 | + "m.room.name": 50, |
| 400 | + "m.room.power_levels": 100, |
| 401 | + "m.room.server_acl": 100, |
| 402 | + "m.room.tombstone": 100, |
| 403 | + }, |
| 404 | + "events_default": event_default, |
| 405 | + "invite": 0, |
| 406 | + "kick": 50, |
| 407 | + "redact": 50, |
| 408 | + "state_default": 50, |
| 409 | + "users": {self.user_id: 100}, |
| 410 | + "users_default": 0, |
| 411 | + }, |
| 412 | + tok=self.tok, |
| 413 | + ) |
0 commit comments