mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-24 18:45:52 +03:00
Sliding Sync: Reset forgotten
status when membership changes (like rejoining a room) (#17835)
Reset `sliding_sync_membership_snapshots` -> `forgotten` status when membership changes (like rejoining a room). Fix https://github.com/element-hq/synapse/issues/17781 ### What was the problem before? Previously, if someone used `/forget` on one of their rooms, it would update `sliding_sync_membership_snapshots` as expected but when someone rejoined the room (or had any membership change), the upsert didn't overwrite and reset the `forgotten` status so it remained `forgotten` and invisible down the Sliding Sync endpoint.
This commit is contained in:
parent
80ad02e10e
commit
a5e16a4ab5
10 changed files with 433 additions and 4 deletions
1
changelog.d/17835.bugfix
Normal file
1
changelog.d/17835.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix a bug in [MSC4186](https://github.com/matrix-org/matrix-spec-proposals/pull/4186) Sliding Sync that would cause rooms to stay forgotten and hidden even after rejoining.
|
|
@ -1863,10 +1863,10 @@ class PersistEventsStore:
|
||||||
txn.execute_batch(
|
txn.execute_batch(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO sliding_sync_membership_snapshots
|
INSERT INTO sliding_sync_membership_snapshots
|
||||||
(room_id, user_id, sender, membership_event_id, membership, event_stream_ordering, event_instance_name
|
(room_id, user_id, sender, membership_event_id, membership, forgotten, event_stream_ordering, event_instance_name
|
||||||
{("," + ", ".join(sliding_sync_snapshot_keys)) if sliding_sync_snapshot_keys else ""})
|
{("," + ", ".join(sliding_sync_snapshot_keys)) if sliding_sync_snapshot_keys else ""})
|
||||||
VALUES (
|
VALUES (
|
||||||
?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?, ?,
|
||||||
(SELECT stream_ordering FROM events WHERE event_id = ?),
|
(SELECT stream_ordering FROM events WHERE event_id = ?),
|
||||||
(SELECT COALESCE(instance_name, 'master') FROM events WHERE event_id = ?)
|
(SELECT COALESCE(instance_name, 'master') FROM events WHERE event_id = ?)
|
||||||
{("," + ", ".join("?" for _ in sliding_sync_snapshot_values)) if sliding_sync_snapshot_values else ""}
|
{("," + ", ".join("?" for _ in sliding_sync_snapshot_values)) if sliding_sync_snapshot_values else ""}
|
||||||
|
@ -1876,6 +1876,7 @@ class PersistEventsStore:
|
||||||
sender = EXCLUDED.sender,
|
sender = EXCLUDED.sender,
|
||||||
membership_event_id = EXCLUDED.membership_event_id,
|
membership_event_id = EXCLUDED.membership_event_id,
|
||||||
membership = EXCLUDED.membership,
|
membership = EXCLUDED.membership,
|
||||||
|
forgotten = EXCLUDED.forgotten,
|
||||||
event_stream_ordering = EXCLUDED.event_stream_ordering
|
event_stream_ordering = EXCLUDED.event_stream_ordering
|
||||||
{("," + ", ".join(f"{key} = EXCLUDED.{key}" for key in sliding_sync_snapshot_keys)) if sliding_sync_snapshot_keys else ""}
|
{("," + ", ".join(f"{key} = EXCLUDED.{key}" for key in sliding_sync_snapshot_keys)) if sliding_sync_snapshot_keys else ""}
|
||||||
""",
|
""",
|
||||||
|
@ -1886,6 +1887,9 @@ class PersistEventsStore:
|
||||||
membership_info.sender,
|
membership_info.sender,
|
||||||
membership_info.membership_event_id,
|
membership_info.membership_event_id,
|
||||||
membership_info.membership,
|
membership_info.membership,
|
||||||
|
# Since this is a new membership, it isn't forgotten anymore (which
|
||||||
|
# matches how Synapse currently thinks about the forgotten status)
|
||||||
|
0,
|
||||||
# XXX: We do not use `membership_info.membership_event_stream_ordering` here
|
# XXX: We do not use `membership_info.membership_event_stream_ordering` here
|
||||||
# because it is an unreliable value. See XXX note above.
|
# because it is an unreliable value. See XXX note above.
|
||||||
membership_info.membership_event_id,
|
membership_info.membership_event_id,
|
||||||
|
@ -2901,6 +2905,9 @@ class PersistEventsStore:
|
||||||
"sender": event.sender,
|
"sender": event.sender,
|
||||||
"membership_event_id": event.event_id,
|
"membership_event_id": event.event_id,
|
||||||
"membership": event.membership,
|
"membership": event.membership,
|
||||||
|
# Since this is a new membership, it isn't forgotten anymore (which
|
||||||
|
# matches how Synapse currently thinks about the forgotten status)
|
||||||
|
"forgotten": 0,
|
||||||
"event_stream_ordering": event.internal_metadata.stream_ordering,
|
"event_stream_ordering": event.internal_metadata.stream_ordering,
|
||||||
"event_instance_name": event.internal_metadata.instance_name,
|
"event_instance_name": event.internal_metadata.instance_name,
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,6 +304,12 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||||
_BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
|
_BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
|
||||||
self._sliding_sync_membership_snapshots_bg_update,
|
self._sliding_sync_membership_snapshots_bg_update,
|
||||||
)
|
)
|
||||||
|
# Add a background update to fix data integrity issue in the
|
||||||
|
# `sliding_sync_membership_snapshots` -> `forgotten` column
|
||||||
|
self.db_pool.updates.register_background_update_handler(
|
||||||
|
_BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_FIX_FORGOTTEN_COLUMN_BG_UPDATE,
|
||||||
|
self._sliding_sync_membership_snapshots_fix_forgotten_column_bg_update,
|
||||||
|
)
|
||||||
|
|
||||||
# We want this to run on the main database at startup before we start processing
|
# We want this to run on the main database at startup before we start processing
|
||||||
# events.
|
# events.
|
||||||
|
@ -2429,6 +2435,118 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||||
|
|
||||||
return len(memberships_to_update_rows)
|
return len(memberships_to_update_rows)
|
||||||
|
|
||||||
|
async def _sliding_sync_membership_snapshots_fix_forgotten_column_bg_update(
|
||||||
|
self, progress: JsonDict, batch_size: int
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Background update to update the `sliding_sync_membership_snapshots` ->
|
||||||
|
`forgotten` column to be in sync with the `room_memberships` table.
|
||||||
|
|
||||||
|
Because of previously flawed code (now fixed); any room that someone has
|
||||||
|
forgotten and subsequently re-joined or had any new membership on, we need to go
|
||||||
|
and update the column to match the `room_memberships` table as it has fallen out
|
||||||
|
of sync.
|
||||||
|
"""
|
||||||
|
last_event_stream_ordering = progress.get(
|
||||||
|
"last_event_stream_ordering", -(1 << 31)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _txn(
|
||||||
|
txn: LoggingTransaction,
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
The number of rows updated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# To simplify things, we can just recheck any row in
|
||||||
|
# `sliding_sync_membership_snapshots` with `forgotten=1`
|
||||||
|
txn.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
s.room_id,
|
||||||
|
s.user_id,
|
||||||
|
s.membership_event_id,
|
||||||
|
s.event_stream_ordering,
|
||||||
|
m.forgotten
|
||||||
|
FROM sliding_sync_membership_snapshots AS s
|
||||||
|
INNER JOIN room_memberships AS m ON (s.membership_event_id = m.event_id)
|
||||||
|
WHERE s.event_stream_ordering > ?
|
||||||
|
AND s.forgotten = 1
|
||||||
|
ORDER BY s.event_stream_ordering ASC
|
||||||
|
LIMIT ?
|
||||||
|
""",
|
||||||
|
(last_event_stream_ordering, batch_size),
|
||||||
|
)
|
||||||
|
|
||||||
|
memberships_to_update_rows = cast(
|
||||||
|
List[Tuple[str, str, str, int, int]],
|
||||||
|
txn.fetchall(),
|
||||||
|
)
|
||||||
|
if not memberships_to_update_rows:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Assemble the values to update
|
||||||
|
#
|
||||||
|
# (room_id, user_id)
|
||||||
|
key_values: List[Tuple[str, str]] = []
|
||||||
|
# (forgotten,)
|
||||||
|
value_values: List[Tuple[int]] = []
|
||||||
|
for (
|
||||||
|
room_id,
|
||||||
|
user_id,
|
||||||
|
_membership_event_id,
|
||||||
|
_event_stream_ordering,
|
||||||
|
forgotten,
|
||||||
|
) in memberships_to_update_rows:
|
||||||
|
key_values.append(
|
||||||
|
(
|
||||||
|
room_id,
|
||||||
|
user_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
value_values.append((forgotten,))
|
||||||
|
|
||||||
|
# Update all of the rows in one go
|
||||||
|
self.db_pool.simple_update_many_txn(
|
||||||
|
txn,
|
||||||
|
table="sliding_sync_membership_snapshots",
|
||||||
|
key_names=("room_id", "user_id"),
|
||||||
|
key_values=key_values,
|
||||||
|
value_names=("forgotten",),
|
||||||
|
value_values=value_values,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update the progress
|
||||||
|
(
|
||||||
|
_room_id,
|
||||||
|
_user_id,
|
||||||
|
_membership_event_id,
|
||||||
|
event_stream_ordering,
|
||||||
|
_forgotten,
|
||||||
|
) = memberships_to_update_rows[-1]
|
||||||
|
self.db_pool.updates._background_update_progress_txn(
|
||||||
|
txn,
|
||||||
|
_BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_FIX_FORGOTTEN_COLUMN_BG_UPDATE,
|
||||||
|
{
|
||||||
|
"last_event_stream_ordering": event_stream_ordering,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return len(memberships_to_update_rows)
|
||||||
|
|
||||||
|
num_rows = await self.db_pool.runInteraction(
|
||||||
|
"_sliding_sync_membership_snapshots_fix_forgotten_column_bg_update",
|
||||||
|
_txn,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not num_rows:
|
||||||
|
await self.db_pool.updates._end_background_update(
|
||||||
|
_BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_FIX_FORGOTTEN_COLUMN_BG_UPDATE
|
||||||
|
)
|
||||||
|
|
||||||
|
return num_rows
|
||||||
|
|
||||||
|
|
||||||
def _resolve_stale_data_in_sliding_sync_tables(
|
def _resolve_stale_data_in_sliding_sync_tables(
|
||||||
txn: LoggingTransaction,
|
txn: LoggingTransaction,
|
||||||
|
|
|
@ -1375,6 +1375,7 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore):
|
||||||
keyvalues={"user_id": user_id, "room_id": room_id},
|
keyvalues={"user_id": user_id, "room_id": room_id},
|
||||||
updatevalues={"forgotten": 1},
|
updatevalues={"forgotten": 1},
|
||||||
)
|
)
|
||||||
|
# Handle updating the `sliding_sync_membership_snapshots` table
|
||||||
self.db_pool.simple_update_txn(
|
self.db_pool.simple_update_txn(
|
||||||
txn,
|
txn,
|
||||||
table="sliding_sync_membership_snapshots",
|
table="sliding_sync_membership_snapshots",
|
||||||
|
|
|
@ -153,6 +153,8 @@ Changes in SCHEMA_VERSION = 87
|
||||||
Changes in SCHEMA_VERSION = 88
|
Changes in SCHEMA_VERSION = 88
|
||||||
- MSC4140: Add `delayed_events` table that keeps track of events that are to
|
- MSC4140: Add `delayed_events` table that keeps track of events that are to
|
||||||
be posted in response to a resettable timeout or an on-demand action.
|
be posted in response to a resettable timeout or an on-demand action.
|
||||||
|
- Add background update to fix data integrity issue in the
|
||||||
|
`sliding_sync_membership_snapshots` -> `forgotten` column
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
--
|
||||||
|
-- This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
--
|
||||||
|
-- Copyright (C) 2024 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:
|
||||||
|
-- <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||||
|
|
||||||
|
-- Add a background update to update the `sliding_sync_membership_snapshots` ->
|
||||||
|
-- `forgotten` column to be in sync with the `room_memberships` table.
|
||||||
|
--
|
||||||
|
-- For any room that someone has forgotten and subsequently re-joined or had any new
|
||||||
|
-- membership on, we need to go and update the column to match the `room_memberships`
|
||||||
|
-- table as it has fallen out of sync.
|
||||||
|
INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
|
||||||
|
(8802, 'sliding_sync_membership_snapshots_fix_forgotten_column_bg_update', '{}');
|
|
@ -45,3 +45,6 @@ class _BackgroundUpdates:
|
||||||
SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE = (
|
SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE = (
|
||||||
"sliding_sync_membership_snapshots_bg_update"
|
"sliding_sync_membership_snapshots_bg_update"
|
||||||
)
|
)
|
||||||
|
SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_FIX_FORGOTTEN_COLUMN_BG_UPDATE = (
|
||||||
|
"sliding_sync_membership_snapshots_fix_forgotten_column_bg_update"
|
||||||
|
)
|
||||||
|
|
|
@ -240,6 +240,7 @@ class SlidingSyncBase(unittest.HomeserverTestCase):
|
||||||
self,
|
self,
|
||||||
invitee_user_id: str,
|
invitee_user_id: str,
|
||||||
unsigned_invite_room_state: Optional[List[StrippedStateEvent]],
|
unsigned_invite_room_state: Optional[List[StrippedStateEvent]],
|
||||||
|
invite_room_id: Optional[str] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Create a fake invite for a remote room and persist it.
|
Create a fake invite for a remote room and persist it.
|
||||||
|
@ -252,19 +253,23 @@ class SlidingSyncBase(unittest.HomeserverTestCase):
|
||||||
invitee_user_id: The person being invited
|
invitee_user_id: The person being invited
|
||||||
unsigned_invite_room_state: List of stripped state events to assist the
|
unsigned_invite_room_state: List of stripped state events to assist the
|
||||||
receiver in identifying the room.
|
receiver in identifying the room.
|
||||||
|
invite_room_id: Optional remote room ID to be invited to. When unset, we
|
||||||
|
will generate one.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The room ID of the remote invite room
|
The room ID of the remote invite room
|
||||||
"""
|
"""
|
||||||
store = self.hs.get_datastores().main
|
store = self.hs.get_datastores().main
|
||||||
|
|
||||||
invite_room_id = f"!test_room{self._remote_invite_count}:remote_server"
|
if invite_room_id is None:
|
||||||
|
invite_room_id = f"!test_room{self._remote_invite_count}:remote_server"
|
||||||
|
|
||||||
invite_event_dict = {
|
invite_event_dict = {
|
||||||
"room_id": invite_room_id,
|
"room_id": invite_room_id,
|
||||||
"sender": "@inviter:remote_server",
|
"sender": "@inviter:remote_server",
|
||||||
"state_key": invitee_user_id,
|
"state_key": invitee_user_id,
|
||||||
"depth": 1,
|
# Just keep advancing the depth
|
||||||
|
"depth": self._remote_invite_count,
|
||||||
"origin_server_ts": 1,
|
"origin_server_ts": 1,
|
||||||
"type": EventTypes.Member,
|
"type": EventTypes.Member,
|
||||||
"content": {"membership": Membership.INVITE},
|
"content": {"membership": Membership.INVITE},
|
||||||
|
@ -679,6 +684,112 @@ class SlidingSyncTestCase(SlidingSyncBase):
|
||||||
exact=True,
|
exact=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_rejoin_forgotten_room(self) -> None:
|
||||||
|
"""
|
||||||
|
Make sure we can see a forgotten room again if we rejoin (or any new membership
|
||||||
|
like an invite) (no longer forgotten)
|
||||||
|
"""
|
||||||
|
user1_id = self.register_user("user1", "pass")
|
||||||
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
user2_id = self.register_user("user2", "pass")
|
||||||
|
user2_tok = self.login(user2_id, "pass")
|
||||||
|
|
||||||
|
room_id = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True)
|
||||||
|
# User1 joins the room
|
||||||
|
self.helper.join(room_id, user1_id, tok=user1_tok)
|
||||||
|
|
||||||
|
# Make the Sliding Sync request
|
||||||
|
sync_body = {
|
||||||
|
"lists": {
|
||||||
|
"foo-list": {
|
||||||
|
"ranges": [[0, 99]],
|
||||||
|
"required_state": [],
|
||||||
|
"timeline_limit": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
|
||||||
|
# We should see the room (like normal)
|
||||||
|
self.assertIncludes(
|
||||||
|
set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
|
||||||
|
{room_id},
|
||||||
|
exact=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Leave and forget the room
|
||||||
|
self.helper.leave(room_id, user1_id, tok=user1_tok)
|
||||||
|
# User1 forgets the room
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
f"/_matrix/client/r0/rooms/{room_id}/forget",
|
||||||
|
content={},
|
||||||
|
access_token=user1_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200, channel.result)
|
||||||
|
|
||||||
|
# Re-join the room
|
||||||
|
self.helper.join(room_id, user1_id, tok=user1_tok)
|
||||||
|
|
||||||
|
# We should see the room again after re-joining
|
||||||
|
response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok)
|
||||||
|
self.assertIncludes(
|
||||||
|
set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
|
||||||
|
{room_id},
|
||||||
|
exact=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invited_to_forgotten_remote_room(self) -> None:
|
||||||
|
"""
|
||||||
|
Make sure we can see a forgotten room again if we are invited again
|
||||||
|
(remote/federated out-of-band memberships)
|
||||||
|
"""
|
||||||
|
user1_id = self.register_user("user1", "pass")
|
||||||
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
|
||||||
|
# Create a remote room invite (out-of-band membership)
|
||||||
|
room_id = self._create_remote_invite_room_for_user(user1_id, None)
|
||||||
|
|
||||||
|
# Make the Sliding Sync request
|
||||||
|
sync_body = {
|
||||||
|
"lists": {
|
||||||
|
"foo-list": {
|
||||||
|
"ranges": [[0, 99]],
|
||||||
|
"required_state": [],
|
||||||
|
"timeline_limit": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
|
||||||
|
# We should see the room (like normal)
|
||||||
|
self.assertIncludes(
|
||||||
|
set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
|
||||||
|
{room_id},
|
||||||
|
exact=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Leave and forget the room
|
||||||
|
self.helper.leave(room_id, user1_id, tok=user1_tok)
|
||||||
|
# User1 forgets the room
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
f"/_matrix/client/r0/rooms/{room_id}/forget",
|
||||||
|
content={},
|
||||||
|
access_token=user1_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200, channel.result)
|
||||||
|
|
||||||
|
# Get invited to the room again
|
||||||
|
# self.helper.join(room_id, user1_id, tok=user1_tok)
|
||||||
|
self._create_remote_invite_room_for_user(user1_id, None, invite_room_id=room_id)
|
||||||
|
|
||||||
|
# We should see the room again after re-joining
|
||||||
|
response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok)
|
||||||
|
self.assertIncludes(
|
||||||
|
set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
|
||||||
|
{room_id},
|
||||||
|
exact=True,
|
||||||
|
)
|
||||||
|
|
||||||
def test_ignored_user_invites_initial_sync(self) -> None:
|
def test_ignored_user_invites_initial_sync(self) -> None:
|
||||||
"""
|
"""
|
||||||
Make sure we ignore invites if they are from one of the `m.ignored_user_list` on
|
Make sure we ignore invites if they are from one of the `m.ignored_user_list` on
|
||||||
|
|
|
@ -2894,6 +2894,68 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
|
||||||
self.assertEqual(event_content.get("reason"), reason, channel.result)
|
self.assertEqual(event_content.get("reason"), reason, channel.result)
|
||||||
|
|
||||||
|
|
||||||
|
class RoomForgottenTestCase(unittest.HomeserverTestCase):
|
||||||
|
"""
|
||||||
|
Test forget/forgotten rooms
|
||||||
|
"""
|
||||||
|
|
||||||
|
servlets = [
|
||||||
|
synapse.rest.admin.register_servlets,
|
||||||
|
room.register_servlets,
|
||||||
|
login.register_servlets,
|
||||||
|
]
|
||||||
|
|
||||||
|
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||||
|
self.store = hs.get_datastores().main
|
||||||
|
|
||||||
|
def test_room_not_forgotten_after_unban(self) -> None:
|
||||||
|
"""
|
||||||
|
Test what happens when someone is banned from a room, they forget the room, and
|
||||||
|
some time later are unbanned.
|
||||||
|
|
||||||
|
Currently, when they are unbanned, the room isn't forgotten anymore which may or
|
||||||
|
may not be expected.
|
||||||
|
"""
|
||||||
|
user1_id = self.register_user("user1", "pass")
|
||||||
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
user2_id = self.register_user("user2", "pass")
|
||||||
|
user2_tok = self.login(user2_id, "pass")
|
||||||
|
|
||||||
|
room_id = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True)
|
||||||
|
self.helper.join(room_id, user1_id, tok=user1_tok)
|
||||||
|
|
||||||
|
# User1 is banned and forgets the room
|
||||||
|
self.helper.ban(room_id, src=user2_id, targ=user1_id, tok=user2_tok)
|
||||||
|
# User1 forgets the room
|
||||||
|
self.get_success(self.store.forget(user1_id, room_id))
|
||||||
|
|
||||||
|
# The room should show up as forgotten
|
||||||
|
forgotten_room_ids = self.get_success(
|
||||||
|
self.store.get_forgotten_rooms_for_user(user1_id)
|
||||||
|
)
|
||||||
|
self.assertIncludes(forgotten_room_ids, {room_id}, exact=True)
|
||||||
|
|
||||||
|
# Unban user1
|
||||||
|
self.helper.change_membership(
|
||||||
|
room=room_id,
|
||||||
|
src=user2_id,
|
||||||
|
targ=user1_id,
|
||||||
|
membership=Membership.LEAVE,
|
||||||
|
tok=user2_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Room is no longer forgotten because it's a new membership
|
||||||
|
#
|
||||||
|
# XXX: Is this how we actually want it to behave? It seems like ideally, the
|
||||||
|
# room forgotten status should only be reset when the user decides to join again
|
||||||
|
# (or is invited/knocks). This way the room remains forgotten for any ban/leave
|
||||||
|
# transitions.
|
||||||
|
forgotten_room_ids = self.get_success(
|
||||||
|
self.store.get_forgotten_rooms_for_user(user1_id)
|
||||||
|
)
|
||||||
|
self.assertIncludes(forgotten_room_ids, set(), exact=True)
|
||||||
|
|
||||||
|
|
||||||
class LabelsTestCase(unittest.HomeserverTestCase):
|
class LabelsTestCase(unittest.HomeserverTestCase):
|
||||||
servlets = [
|
servlets = [
|
||||||
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||||
|
|
|
@ -5014,3 +5014,106 @@ class SlidingSyncTablesCatchUpBackgroundUpdatesTestCase(SlidingSyncTablesTestCas
|
||||||
},
|
},
|
||||||
exact=True,
|
exact=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SlidingSyncMembershipSnapshotsTableFixForgottenColumnBackgroundUpdatesTestCase(
|
||||||
|
SlidingSyncTablesTestCaseBase
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test the background updates that fixes `sliding_sync_membership_snapshots` ->
|
||||||
|
`forgotten` column.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_membership_snapshots_fix_forgotten_column_background_update(self) -> None:
|
||||||
|
"""
|
||||||
|
Test that the background update, updates the `sliding_sync_membership_snapshots`
|
||||||
|
-> `forgotten` column to be in sync with the `room_memberships` table.
|
||||||
|
"""
|
||||||
|
user1_id = self.register_user("user1", "pass")
|
||||||
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
user2_id = self.register_user("user2", "pass")
|
||||||
|
user2_tok = self.login(user2_id, "pass")
|
||||||
|
|
||||||
|
room_id = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True)
|
||||||
|
# User1 joins the room
|
||||||
|
self.helper.join(room_id, user1_id, tok=user1_tok)
|
||||||
|
|
||||||
|
# Leave and forget the room
|
||||||
|
self.helper.leave(room_id, user1_id, tok=user1_tok)
|
||||||
|
# User1 forgets the room
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
f"/_matrix/client/r0/rooms/{room_id}/forget",
|
||||||
|
content={},
|
||||||
|
access_token=user1_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200, channel.result)
|
||||||
|
|
||||||
|
# Re-join the room
|
||||||
|
self.helper.join(room_id, user1_id, tok=user1_tok)
|
||||||
|
|
||||||
|
# Reset `sliding_sync_membership_snapshots` table as if the `forgotten` column
|
||||||
|
# got out of sync from the `room_memberships` table from the previous flawed
|
||||||
|
# code.
|
||||||
|
self.get_success(
|
||||||
|
self.store.db_pool.simple_update_one(
|
||||||
|
table="sliding_sync_membership_snapshots",
|
||||||
|
keyvalues={"room_id": room_id, "user_id": user1_id},
|
||||||
|
updatevalues={"forgotten": 1},
|
||||||
|
desc="sliding_sync_membership_snapshots.test_membership_snapshots_fix_forgotten_column_background_update",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Insert and run the background update.
|
||||||
|
self.get_success(
|
||||||
|
self.store.db_pool.simple_insert(
|
||||||
|
"background_updates",
|
||||||
|
{
|
||||||
|
"update_name": _BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_FIX_FORGOTTEN_COLUMN_BG_UPDATE,
|
||||||
|
"progress_json": "{}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.store.db_pool.updates._all_done = False
|
||||||
|
self.wait_for_background_updates()
|
||||||
|
|
||||||
|
# Make sure the table is populated
|
||||||
|
|
||||||
|
sliding_sync_membership_snapshots_results = (
|
||||||
|
self._get_sliding_sync_membership_snapshots()
|
||||||
|
)
|
||||||
|
self.assertIncludes(
|
||||||
|
set(sliding_sync_membership_snapshots_results.keys()),
|
||||||
|
{
|
||||||
|
(room_id, user1_id),
|
||||||
|
(room_id, user2_id),
|
||||||
|
},
|
||||||
|
exact=True,
|
||||||
|
)
|
||||||
|
state_map = self.get_success(
|
||||||
|
self.storage_controllers.state.get_current_state(room_id)
|
||||||
|
)
|
||||||
|
# Holds the info according to the current state when the user joined.
|
||||||
|
#
|
||||||
|
# We only care about checking on user1 as that's what we reset and expect to be
|
||||||
|
# correct now
|
||||||
|
self.assertEqual(
|
||||||
|
sliding_sync_membership_snapshots_results.get((room_id, user1_id)),
|
||||||
|
_SlidingSyncMembershipSnapshotResult(
|
||||||
|
room_id=room_id,
|
||||||
|
user_id=user1_id,
|
||||||
|
sender=user1_id,
|
||||||
|
membership_event_id=state_map[(EventTypes.Member, user1_id)].event_id,
|
||||||
|
membership=Membership.JOIN,
|
||||||
|
event_stream_ordering=state_map[
|
||||||
|
(EventTypes.Member, user1_id)
|
||||||
|
].internal_metadata.stream_ordering,
|
||||||
|
has_known_state=True,
|
||||||
|
room_type=None,
|
||||||
|
room_name=None,
|
||||||
|
is_encrypted=False,
|
||||||
|
tombstone_successor_room_id=None,
|
||||||
|
# We should see the room as no longer forgotten
|
||||||
|
forgotten=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue