diff --git a/tests/rest/client/sliding_sync/test_rooms_required_state.py b/tests/rest/client/sliding_sync/test_rooms_required_state.py index 586b127f8a..a989598738 100644 --- a/tests/rest/client/sliding_sync/test_rooms_required_state.py +++ b/tests/rest/client/sliding_sync/test_rooms_required_state.py @@ -2245,3 +2245,69 @@ class SlidingSyncRoomsRequiredStateTestCase(SlidingSyncBase): response_body["rooms"][room_id]["required_state"][0]["event_id"], first_event_id, ) + + def test_changing_required_state_returns_immediately(self) -> None: + """Test that if we change the required state, then we return immediately + with the new required state.""" + + user1_id = self.register_user("user1", "pass") + user1_tok = self.login(user1_id, "pass") + + room_id1 = self.helper.create_room_as(user1_id, tok=user1_tok) + + # Make an initial sync request with no required state + sync_body = { + "lists": { + "foo-list": { + "ranges": [[0, 1]], + "required_state": [], + "timeline_limit": 0, + } + } + } + response_body, from_token = self.do_sync(sync_body, tok=user1_tok) + + # We should see no required state + self.assertIsNone(response_body["rooms"][room_id1].get("required_state")) + + # Get the state_map before we change the state as this is the final state we + # expect to see when we update the required state. + state_map = self.get_success( + self.storage_controllers.state.get_current_state(room_id1) + ) + + # There is no new data, and so making another sync request will block. + channel = self.make_sync_request( + sync_body, + since=from_token, + tok=user1_tok, + timeout_ms=10_000, + await_result=False, + ) + self.reactor.advance(0.1) # Allow the request to start processing + self.reactor.advance(9.5) + self.assertFalse(channel.is_finished()) + + # Advance past the timeout to make sure the request finishes. (We do this + # to ensure log contexts don't leak between tests). + self.reactor.advance(1) + self.assertTrue(channel.is_finished()) + + # Now update the sliding sync requests to include a required state + # event, and make another sync request. + sync_body["lists"]["foo-list"]["required_state"] = [ + [EventTypes.Create, ""], + ] + + response_body, _ = self.do_sync( + sync_body, since=from_token, tok=user1_tok, timeout_ms=10_000 + ) + + # We should see the new required state immediately without waiting. + self._assertRequiredStateIncludes( + response_body["rooms"][room_id1]["required_state"], + { + state_map[(EventTypes.Create, "")], + }, + exact=True, + ) diff --git a/tests/rest/client/sliding_sync/test_sliding_sync.py b/tests/rest/client/sliding_sync/test_sliding_sync.py index ac8dfd37d8..2b258ecb06 100644 --- a/tests/rest/client/sliding_sync/test_sliding_sync.py +++ b/tests/rest/client/sliding_sync/test_sliding_sync.py @@ -12,6 +12,7 @@ # . # import logging +import urllib.parse from typing import Any, Iterable, Literal from unittest.mock import AsyncMock @@ -81,7 +82,13 @@ class SlidingSyncBase(unittest.HomeserverTestCase): return config def make_sync_request( - self, sync_body: JsonDict, *, since: str | None = None, tok: str + self, + sync_body: JsonDict, + *, + since: str | None = None, + tok: str, + timeout_ms: int | None = None, + await_result: bool = True, ) -> FakeChannel: """Make a sliding sync request with given body. @@ -89,25 +96,40 @@ class SlidingSyncBase(unittest.HomeserverTestCase): sync_body: The full request body to use since: Optional since token tok: Access token to use - + timeout_ms: Optional timeout in milliseconds to use for the request. + await_result: Whether to block and wait for the result before returning. Returns: A tuple of the response body and the `pos` field. """ sync_path = self.sync_endpoint + + query_params: dict[str, Any] = {} if since: - sync_path += f"?pos={since}" + query_params["pos"] = since + if timeout_ms is not None: + query_params["timeout"] = timeout_ms + + if query_params: + query_str = urllib.parse.urlencode(query_params) + sync_path += f"?{query_str}" channel = self.make_request( method="POST", path=sync_path, content=sync_body, access_token=tok, + await_result=await_result, ) return channel def do_sync( - self, sync_body: JsonDict, *, since: str | None = None, tok: str + self, + sync_body: JsonDict, + *, + since: str | None = None, + tok: str, + timeout_ms: int | None = None, ) -> tuple[JsonDict, str]: """Do a sliding sync request with given body. @@ -117,11 +139,14 @@ class SlidingSyncBase(unittest.HomeserverTestCase): sync_body: The full request body to use since: Optional since token tok: Access token to use + timeout_ms: Optional timeout in milliseconds to use for the request. Returns: A tuple of the response body and the `pos` field. """ - channel = self.make_sync_request(sync_body, since=since, tok=tok) + channel = self.make_sync_request( + sync_body, since=since, tok=tok, timeout_ms=timeout_ms + ) self.assertEqual(channel.code, 200, channel.json_body) return channel.json_body, channel.json_body["pos"]