diff --git a/synapse/handlers/sync.py b/synapse/handlers/sync.py index 0ebc6802ac..1f31b53786 100644 --- a/synapse/handlers/sync.py +++ b/synapse/handlers/sync.py @@ -252,7 +252,7 @@ class SyncResult: presence: list[UserPresenceState] account_data: list[JsonDict] # user ID -> {profile field -> value | null if unset } - profile_updates: dict[str, dict[str, JsonValue | None]] + profile_updates: dict[str, dict[str, JsonValue | None] | None] joined: list[JoinedSyncResult] invited: list[InvitedSyncResult] knocked: list[KnockedSyncResult] @@ -2223,7 +2223,7 @@ class SyncHandler: ) # Serialise the profile updates into the sync response format. - profile_updates: dict[str, dict[str, JsonValue | None]] = {} + profile_updates: dict[str, dict[str, JsonValue | None] | None] = {} for other_user_id, fields in user_fields.items(): profile_data = profile_data_by_user.get(other_user_id) if profile_data is None: @@ -2324,7 +2324,7 @@ class SyncHandler: # Serialise the profile updates into the sync response format. # user ID -> {profile field -> value | null if unset } - profile_updates: dict[str, dict[str, JsonValue | None]] = {} + profile_updates: dict[str, dict[str, JsonValue | None] | None] = {} # Process field updates and users who have events in the sync response if users: @@ -2351,7 +2351,7 @@ class SyncHandler: # No profile data for this user, just return a blank dictionary # in incremental sync, telling the clients to remove all profile # information for this user. - profile_updates[other_user_id] = {} + profile_updates[other_user_id] = None continue per_user_updates: dict[str, JsonValue] = {} @@ -2401,7 +2401,7 @@ class SyncHandler: if left_room_user_ids: for other_user_id in left_room_user_ids: # Return an empty dictionary to the client - profile_updates[other_user_id] = {} + profile_updates[other_user_id] = None if profile_updates: sync_result_builder.profile_updates = profile_updates @@ -3441,7 +3441,7 @@ class SyncResultBuilder: presence: list[UserPresenceState] = attr.Factory(list) account_data: list[JsonDict] = attr.Factory(list) - profile_updates: dict[str, dict[str, JsonValue | None]] = attr.Factory(dict) + profile_updates: dict[str, dict[str, JsonValue | None] | None] = attr.Factory(dict) joined: list[JoinedSyncResult] = attr.Factory(list) invited: list[InvitedSyncResult] = attr.Factory(list) knocked: list[KnockedSyncResult] = attr.Factory(list) diff --git a/tests/handlers/test_sync.py b/tests/handlers/test_sync.py index 7e3d5c7aa5..fbec22ab9b 100644 --- a/tests/handlers/test_sync.py +++ b/tests/handlers/test_sync.py @@ -1245,6 +1245,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase): request_key=generate_request_key(), ) ) + assert initial_result.profile_updates["@other_user:test"] is not None self.assertEqual( initial_result.profile_updates["@other_user:test"]["m.status"], '{"text": "On holiday", "emoji": "\\ud83c\\udfd6"}', @@ -1379,6 +1380,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase): request_key=generate_request_key(), ) ) + assert incremental_result.profile_updates["@other_user:test"] is not None self.assertEqual( incremental_result.profile_updates["@other_user:test"]["m.status"], '{"text": "On holiday", "emoji": "\\ud83c\\udfd6"}', @@ -1472,7 +1474,8 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase): "@third_user:test", ], ) - # This is a field upfate, so should be here + assert incremental_result.profile_updates["@other_user:test"] is not None + # This is a field update, so should be here self.assertEqual( incremental_result.profile_updates["@other_user:test"]["m.status"], '{"text": "On holiday", "emoji": "\\ud83c\\udfd6"}', @@ -1482,6 +1485,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase): self.assertIsNone( incremental_result.profile_updates["@other_user:test"].get("displayname"), ) + assert incremental_result.profile_updates["@third_user:test"] is not None # This user has events in the timeline, thus their full profile is included self.assertEqual( incremental_result.profile_updates["@third_user:test"]["m.status"], @@ -1583,6 +1587,53 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase): [], ) + @override_config({"experimental_features": {"msc4429_enabled": True}}) + def test_incremental_sync_sends_down_null_profile_if_user_no_longer_sharing_rooms( + self, + ) -> None: + requester = create_requester(self.user) + initial_result = self.get_success( + self.sync_handler.wait_for_sync_for_user( + requester, + sync_config=generate_sync_config( + user_id=self.user, + filter_collection=FilterCollection( + hs=self.hs, + filter_json={ + "org.matrix.msc4429.profile_fields": { + "ids": ["m.status", "displayname", "avatar_url"] + } + }, + ), + ), + request_key=generate_request_key(), + ) + ) + self.helper.leave( + room=self.joined_room, user=self.other_user, tok=self.other_tok + ) + incremental_result = self.get_success( + self.sync_handler.wait_for_sync_for_user( + requester, + since_token=initial_result.next_batch, + sync_config=generate_sync_config( + user_id=self.user, + filter_collection=FilterCollection( + hs=self.hs, + filter_json={ + "org.matrix.msc4429.profile_fields": { + "ids": ["m.status", "displayname", "avatar_url"] + } + }, + ), + ), + request_key=generate_request_key(), + ) + ) + self.assertIsNone( + incremental_result.profile_updates["@other_user:test"], + ) + class SyncStateAfterTestCase(tests.unittest.HomeserverTestCase): """Tests Sync Handler state behavior when using `use_state_after."""