From be9809347d2a00802d267772d42db9fb54175000 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 5 Jan 2026 14:03:56 +0000 Subject: [PATCH] Add a test for sticky TTL calculation and exposure to clients --- tests/rest/client/test_sticky_events.py | 117 ++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tests/rest/client/test_sticky_events.py diff --git a/tests/rest/client/test_sticky_events.py b/tests/rest/client/test_sticky_events.py new file mode 100644 index 0000000000..a16f74c425 --- /dev/null +++ b/tests/rest/client/test_sticky_events.py @@ -0,0 +1,117 @@ +# +# This file is licensed under the Affero General Public License (AGPL) version 3. +# +# Copyright (C) 2025 New Vector, Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# See the GNU Affero General Public License for more details: +# . +# +# + +from twisted.internet.testing import MemoryReactor + +from synapse.api.constants import EventTypes, EventUnsignedContentFields +from synapse.rest import admin +from synapse.rest.client import login, register, room +from synapse.server import HomeServer +from synapse.types import JsonDict +from synapse.util.clock import Clock + +from tests import unittest + + +class StickyEventsClientTestCase(unittest.HomeserverTestCase): + """Tests sticky events retrieved via the /event/ endpoint.""" + + servlets = [ + room.register_servlets, + login.register_servlets, + register.register_servlets, + admin.register_servlets, + ] + + def default_config(self) -> JsonDict: + config = super().default_config() + config["experimental_features"] = {"msc4354_enabled": True} + return config + + def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: + # Arrange: Register an account + self.user_id = self.register_user("user1", "pass") + self.token = self.login(self.user_id, "pass") + + # Arrange: Create a room + self.room_id = self.helper.create_room_as(self.user_id, tok=self.token) + + def _assert_event_sticky_for(self, event_id: str, sticky_ttl: int) -> None: + channel = self.make_request( + "GET", + f"/rooms/{self.room_id}/event/{event_id}", + access_token=self.token, + ) + + self.assertEqual( + channel.code, 200, f"could not retrieve event {event_id}: {channel.result}" + ) + event = channel.json_body + + self.assertIn( + EventUnsignedContentFields.STICKY_TTL, + event["unsigned"], + f"No {EventUnsignedContentFields.STICKY_TTL} field in {event_id}; event not sticky: {event}", + ) + self.assertEqual( + event["unsigned"][EventUnsignedContentFields.STICKY_TTL], + sticky_ttl, + f"{event_id} had an unexpected sticky TTL: {event}", + ) + + def _assert_event_not_sticky(self, event_id: str) -> None: + channel = self.make_request( + "GET", + f"/rooms/{self.room_id}/event/{event_id}", + access_token=self.token, + ) + + self.assertEqual( + channel.code, 200, f"could not retrieve event {event_id}: {channel.result}" + ) + event = channel.json_body + + self.assertNotIn( + EventUnsignedContentFields.STICKY_TTL, + event["unsigned"], + f"{EventUnsignedContentFields.STICKY_TTL} field unexpectedly found in {event_id}: {event}", + ) + + def test_sticky_event_via_event_endpoint(self) -> None: + # Arrange: Send a sticky event with a specific duration + sticky_event_response = self.helper.send_sticky_event( + self.room_id, + EventTypes.Message, + # sticky duration is 1 minute + duration_ms=60_000, + content={"body": "sticky message", "msgtype": "m.text"}, + tok=self.token, + ) + event_id = sticky_event_response["event_id"] + + # If we request the event immediately, it will still have + # 1 minute of stickiness + # The other 100 ms is advanced in FakeChannel.await_result. + self._assert_event_sticky_for(event_id, 59_900) + + # But if we advance time by 59.799 seconds... + # we will get the event on its last millisecond of stickiness + # The other 100 ms is advanced in FakeChannel.await_result. + self.reactor.advance(59.799) + self._assert_event_sticky_for(event_id, 1) + + # Advancing time any more, the event is no longer sticky + self.reactor.advance(0.001) + self._assert_event_not_sticky(event_id)