Deliver announcement rooms in the sync that carries the user's leave

This commit is contained in:
Paul Chobert committed 2026-10-28 10:42:18 +01:00
1 parent 0b6a20fa1e
commit 29eb4b5f44
2 files changed
+38 -5

No files matched your search

+13 -2
View File
@@ -3096,6 +3096,7 @@ class SyncHandler:
sync_result_builder.forced_newly_joined_room_ids
)
newly_left_rooms: list[str] = []
left_announcement_room_ids: set[str] = set()
room_entries: list[RoomSyncResultBuilder] = []
invited: list[InvitedSyncResult] = []
knocked: list[KnockedSyncResult] = []
@@ -3200,6 +3201,7 @@ class SyncHandler:
knocked.append(knock_room_sync)
if room_id in sync_result_builder.announcement_room_ids:
left_announcement_room_ids.add(room_id)
continue
# Always include leave/ban events. Just take the last one.
@@ -3311,7 +3313,9 @@ class SyncHandler:
room_entries.extend(
await self._get_announcement_room_entries(
sync_result_builder, sync_result_builder.announcement_room_ids
sync_result_builder,
sync_result_builder.announcement_room_ids,
left_room_ids=left_announcement_room_ids,
)
)
@@ -3327,6 +3331,7 @@ class SyncHandler:
self,
sync_result_builder: "SyncResultBuilder",
room_ids: AbstractSet[str],
left_room_ids: AbstractSet[str] = frozenset(),
) -> list["RoomSyncResultBuilder"]:
"""Returns entries for the given announcement rooms that have an
announcement to show, i.e. a non-state event.
@@ -3336,9 +3341,15 @@ class SyncHandler:
most recent events hold an announcement. On an initial sync, a room is
included if its most recent events hold an announcement.
Rooms in `left_room_ids` are included even without an announcement, so
that the client replaces the user's real membership with the synthetic
join and gets the room's current state.
Args:
sync_result_builder
room_ids: The announcement rooms to consider.
left_room_ids: Rooms among `room_ids` that the user left or was
removed from since the `since_token`.
"""
since_token = sync_result_builder.since_token
now_token = sync_result_builder.now_token
@@ -3357,7 +3368,7 @@ class SyncHandler:
room_id
for room_id, (events, _, _) in room_to_events.items()
if not all(event.is_state() for event in events)
}
} | (left_room_ids & room_ids)
if since_token and sync_result_builder.full_state:
recent_room_to_events = await self.store.get_room_events_stream_for_rooms(
+25 -3
View File
@@ -2242,6 +2242,9 @@ class ServerAnnouncementsSyncTestCase(unittest.HomeserverTestCase):
def _timeline_ids(self, room: JsonDict) -> list[str]:
return [event["event_id"] for event in room["timeline"]["events"]]
def _timeline_types(self, room: JsonDict) -> list[str]:
return [event["type"] for event in room["timeline"]["events"]]
def _state_types(self, room: JsonDict) -> set[str]:
return {event["type"] for event in room["state"]["events"]}
@@ -2342,8 +2345,9 @@ class ServerAnnouncementsSyncTestCase(unittest.HomeserverTestCase):
)
def test_removed_member(self) -> None:
"""A member removed from the room by the config does not get their leave,
then gets later announcements as a non-member, on initial sync too.
"""A member removed from the room by the config switches straight to the
synthetic join and the room's current power levels, then gets later
announcements as a non-member, on initial sync too.
"""
since = self._sync(self.former_tok)["next_batch"]
@@ -2352,7 +2356,14 @@ class ServerAnnouncementsSyncTestCase(unittest.HomeserverTestCase):
]
self.get_success(self.handler.reconcile_rooms())
response = self._sync(self.former_tok, since)
self._assert_not_delivered(response)
room = self._assert_delivered(response, self.former)
self.assertNotIn(EventTypes.Message, self._timeline_types(room))
power_levels = [
event
for event in room["state"]["events"] + room["timeline"]["events"]
if event["type"] == EventTypes.PowerLevels
][-1]
self.assertNotIn(self.former, power_levels["content"]["users"])
event_id = self._announce("hello")
room = self._assert_delivered(
@@ -2380,6 +2391,17 @@ class ServerAnnouncementsSyncTestCase(unittest.HomeserverTestCase):
room = self._assert_delivered(response, self.former)
self.assertEqual(self._timeline_ids(room)[-2:], event_ids)
def test_member_leaving(self) -> None:
"""A member who leaves the room on their own switches straight to the
synthetic join, even when no announcement is sent.
"""
since = self._sync(self.former_tok)["next_batch"]
self.helper.leave(self.room_id, self.former, tok=self.former_tok)
room = self._assert_delivered(self._sync(self.former_tok, since), self.former)
self.assertEqual(self._timeline_ids(room), [])
def test_initial_sync_after_leaving(self) -> None:
"""A former member who left on their own gets a single, synthetic join
on initial sync, even when their real leave is among the recent events.