mirror of
https://mau.dev/maunium/synapse.git
synced 2024-11-16 23:11:34 +01:00
Add stream_ordering
sort to Sliding Sync /sync
(#17293)
Sort is no longer configurable and we always sort rooms by the `stream_ordering` of the last event in the room or the point where the user can see up to in cases of leave/ban/invite/knock.
This commit is contained in:
parent
e88332b5f4
commit
e5b8a3e37f
9 changed files with 459 additions and 121 deletions
1
changelog.d/17293.feature
Normal file
1
changelog.d/17293.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add `stream_ordering` sort to experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint.
|
|
@ -201,7 +201,7 @@ class MessageHandler:
|
||||||
|
|
||||||
if at_token:
|
if at_token:
|
||||||
last_event_id = (
|
last_event_id = (
|
||||||
await self.store.get_last_event_in_room_before_stream_ordering(
|
await self.store.get_last_event_id_in_room_before_stream_ordering(
|
||||||
room_id,
|
room_id,
|
||||||
end_token=at_token.room_key,
|
end_token=at_token.room_key,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,13 +18,20 @@
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, AbstractSet, Dict, List, Optional
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from immutabledict import immutabledict
|
from immutabledict import immutabledict
|
||||||
|
|
||||||
from synapse.api.constants import AccountDataTypes, Membership
|
from synapse.api.constants import AccountDataTypes, Membership
|
||||||
from synapse.events import EventBase
|
from synapse.events import EventBase
|
||||||
from synapse.types import Requester, RoomStreamToken, StreamToken, UserID
|
from synapse.storage.roommember import RoomsForUser
|
||||||
|
from synapse.types import (
|
||||||
|
PersistedEventPosition,
|
||||||
|
Requester,
|
||||||
|
RoomStreamToken,
|
||||||
|
StreamToken,
|
||||||
|
UserID,
|
||||||
|
)
|
||||||
from synapse.types.handlers import OperationType, SlidingSyncConfig, SlidingSyncResult
|
from synapse.types.handlers import OperationType, SlidingSyncConfig, SlidingSyncResult
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -33,6 +40,27 @@ if TYPE_CHECKING:
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_event_to_rooms_for_user(event: EventBase) -> RoomsForUser:
|
||||||
|
"""
|
||||||
|
Quick helper to convert an event to a `RoomsForUser` object.
|
||||||
|
"""
|
||||||
|
# These fields should be present for all persisted events
|
||||||
|
assert event.internal_metadata.stream_ordering is not None
|
||||||
|
assert event.internal_metadata.instance_name is not None
|
||||||
|
|
||||||
|
return RoomsForUser(
|
||||||
|
room_id=event.room_id,
|
||||||
|
sender=event.sender,
|
||||||
|
membership=event.membership,
|
||||||
|
event_id=event.event_id,
|
||||||
|
event_pos=PersistedEventPosition(
|
||||||
|
event.internal_metadata.instance_name,
|
||||||
|
event.internal_metadata.stream_ordering,
|
||||||
|
),
|
||||||
|
room_version_id=event.room_version.identifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def filter_membership_for_sync(*, membership: str, user_id: str, sender: str) -> bool:
|
def filter_membership_for_sync(*, membership: str, user_id: str, sender: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns True if the membership event should be included in the sync response,
|
Returns True if the membership event should be included in the sync response,
|
||||||
|
@ -169,26 +197,28 @@ class SlidingSyncHandler:
|
||||||
# See https://github.com/matrix-org/matrix-doc/issues/1144
|
# See https://github.com/matrix-org/matrix-doc/issues/1144
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
# Get all of the room IDs that the user should be able to see in the sync
|
|
||||||
# response
|
|
||||||
room_id_set = await self.get_sync_room_ids_for_user(
|
|
||||||
sync_config.user,
|
|
||||||
from_token=from_token,
|
|
||||||
to_token=to_token,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Assemble sliding window lists
|
# Assemble sliding window lists
|
||||||
lists: Dict[str, SlidingSyncResult.SlidingWindowList] = {}
|
lists: Dict[str, SlidingSyncResult.SlidingWindowList] = {}
|
||||||
if sync_config.lists:
|
if sync_config.lists:
|
||||||
|
# Get all of the room IDs that the user should be able to see in the sync
|
||||||
|
# response
|
||||||
|
sync_room_map = await self.get_sync_room_ids_for_user(
|
||||||
|
sync_config.user,
|
||||||
|
from_token=from_token,
|
||||||
|
to_token=to_token,
|
||||||
|
)
|
||||||
|
|
||||||
for list_key, list_config in sync_config.lists.items():
|
for list_key, list_config in sync_config.lists.items():
|
||||||
# Apply filters
|
# Apply filters
|
||||||
filtered_room_ids = room_id_set
|
filtered_sync_room_map = sync_room_map
|
||||||
if list_config.filters is not None:
|
if list_config.filters is not None:
|
||||||
filtered_room_ids = await self.filter_rooms(
|
filtered_sync_room_map = await self.filter_rooms(
|
||||||
sync_config.user, room_id_set, list_config.filters, to_token
|
sync_config.user, sync_room_map, list_config.filters, to_token
|
||||||
)
|
)
|
||||||
# TODO: Apply sorts
|
|
||||||
sorted_room_ids = sorted(filtered_room_ids)
|
sorted_room_info = await self.sort_rooms(
|
||||||
|
filtered_sync_room_map, to_token
|
||||||
|
)
|
||||||
|
|
||||||
ops: List[SlidingSyncResult.SlidingWindowList.Operation] = []
|
ops: List[SlidingSyncResult.SlidingWindowList.Operation] = []
|
||||||
if list_config.ranges:
|
if list_config.ranges:
|
||||||
|
@ -197,12 +227,17 @@ class SlidingSyncHandler:
|
||||||
SlidingSyncResult.SlidingWindowList.Operation(
|
SlidingSyncResult.SlidingWindowList.Operation(
|
||||||
op=OperationType.SYNC,
|
op=OperationType.SYNC,
|
||||||
range=range,
|
range=range,
|
||||||
room_ids=sorted_room_ids[range[0] : range[1]],
|
room_ids=[
|
||||||
|
room_id
|
||||||
|
for room_id, _ in sorted_room_info[
|
||||||
|
range[0] : range[1]
|
||||||
|
]
|
||||||
|
],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lists[list_key] = SlidingSyncResult.SlidingWindowList(
|
lists[list_key] = SlidingSyncResult.SlidingWindowList(
|
||||||
count=len(sorted_room_ids),
|
count=len(sorted_room_info),
|
||||||
ops=ops,
|
ops=ops,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -219,7 +254,7 @@ class SlidingSyncHandler:
|
||||||
user: UserID,
|
user: UserID,
|
||||||
to_token: StreamToken,
|
to_token: StreamToken,
|
||||||
from_token: Optional[StreamToken] = None,
|
from_token: Optional[StreamToken] = None,
|
||||||
) -> AbstractSet[str]:
|
) -> Dict[str, RoomsForUser]:
|
||||||
"""
|
"""
|
||||||
Fetch room IDs that should be listed for this user in the sync response (the
|
Fetch room IDs that should be listed for this user in the sync response (the
|
||||||
full room list that will be filtered, sorted, and sliced).
|
full room list that will be filtered, sorted, and sliced).
|
||||||
|
@ -237,11 +272,14 @@ class SlidingSyncHandler:
|
||||||
to tell when a room was forgotten at the moment so we can't factor it into the
|
to tell when a room was forgotten at the moment so we can't factor it into the
|
||||||
from/to range.
|
from/to range.
|
||||||
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user: User to fetch rooms for
|
user: User to fetch rooms for
|
||||||
to_token: The token to fetch rooms up to.
|
to_token: The token to fetch rooms up to.
|
||||||
from_token: The point in the stream to sync from.
|
from_token: The point in the stream to sync from.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dictionary of room IDs that should be listed in the sync response along
|
||||||
|
with membership information in that room at the time of `to_token`.
|
||||||
"""
|
"""
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
|
|
||||||
|
@ -261,11 +299,11 @@ class SlidingSyncHandler:
|
||||||
|
|
||||||
# If the user has never joined any rooms before, we can just return an empty list
|
# If the user has never joined any rooms before, we can just return an empty list
|
||||||
if not room_for_user_list:
|
if not room_for_user_list:
|
||||||
return set()
|
return {}
|
||||||
|
|
||||||
# Our working list of rooms that can show up in the sync response
|
# Our working list of rooms that can show up in the sync response
|
||||||
sync_room_id_set = {
|
sync_room_id_set = {
|
||||||
room_for_user.room_id
|
room_for_user.room_id: room_for_user
|
||||||
for room_for_user in room_for_user_list
|
for room_for_user in room_for_user_list
|
||||||
if filter_membership_for_sync(
|
if filter_membership_for_sync(
|
||||||
membership=room_for_user.membership,
|
membership=room_for_user.membership,
|
||||||
|
@ -415,7 +453,9 @@ class SlidingSyncHandler:
|
||||||
not was_last_membership_already_included
|
not was_last_membership_already_included
|
||||||
and should_prev_membership_be_included
|
and should_prev_membership_be_included
|
||||||
):
|
):
|
||||||
sync_room_id_set.add(room_id)
|
sync_room_id_set[room_id] = convert_event_to_rooms_for_user(
|
||||||
|
last_membership_change_after_to_token
|
||||||
|
)
|
||||||
# 1b) Remove rooms that the user joined (hasn't left) after the `to_token`
|
# 1b) Remove rooms that the user joined (hasn't left) after the `to_token`
|
||||||
#
|
#
|
||||||
# For example, if the last membership event after the `to_token` is a "join"
|
# For example, if the last membership event after the `to_token` is a "join"
|
||||||
|
@ -426,7 +466,7 @@ class SlidingSyncHandler:
|
||||||
was_last_membership_already_included
|
was_last_membership_already_included
|
||||||
and not should_prev_membership_be_included
|
and not should_prev_membership_be_included
|
||||||
):
|
):
|
||||||
sync_room_id_set.discard(room_id)
|
del sync_room_id_set[room_id]
|
||||||
|
|
||||||
# 2) -----------------------------------------------------
|
# 2) -----------------------------------------------------
|
||||||
# We fix-up newly_left rooms after the first fixup because it may have removed
|
# We fix-up newly_left rooms after the first fixup because it may have removed
|
||||||
|
@ -461,25 +501,32 @@ class SlidingSyncHandler:
|
||||||
# include newly_left rooms because the last event that the user should see
|
# include newly_left rooms because the last event that the user should see
|
||||||
# is their own leave event
|
# is their own leave event
|
||||||
if last_membership_change_in_from_to_range.membership == Membership.LEAVE:
|
if last_membership_change_in_from_to_range.membership == Membership.LEAVE:
|
||||||
sync_room_id_set.add(room_id)
|
sync_room_id_set[room_id] = convert_event_to_rooms_for_user(
|
||||||
|
last_membership_change_in_from_to_range
|
||||||
|
)
|
||||||
|
|
||||||
return sync_room_id_set
|
return sync_room_id_set
|
||||||
|
|
||||||
async def filter_rooms(
|
async def filter_rooms(
|
||||||
self,
|
self,
|
||||||
user: UserID,
|
user: UserID,
|
||||||
room_id_set: AbstractSet[str],
|
sync_room_map: Dict[str, RoomsForUser],
|
||||||
filters: SlidingSyncConfig.SlidingSyncList.Filters,
|
filters: SlidingSyncConfig.SlidingSyncList.Filters,
|
||||||
to_token: StreamToken,
|
to_token: StreamToken,
|
||||||
) -> AbstractSet[str]:
|
) -> Dict[str, RoomsForUser]:
|
||||||
"""
|
"""
|
||||||
Filter rooms based on the sync request.
|
Filter rooms based on the sync request.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user: User to filter rooms for
|
user: User to filter rooms for
|
||||||
room_id_set: Set of room IDs to filter down
|
sync_room_map: Dictionary of room IDs to sort along with membership
|
||||||
|
information in the room at the time of `to_token`.
|
||||||
filters: Filters to apply
|
filters: Filters to apply
|
||||||
to_token: We filter based on the state of the room at this token
|
to_token: We filter based on the state of the room at this token
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A filtered dictionary of room IDs along with membership information in the
|
||||||
|
room at the time of `to_token`.
|
||||||
"""
|
"""
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
|
|
||||||
|
@ -488,7 +535,7 @@ class SlidingSyncHandler:
|
||||||
# TODO: Exclude partially stated rooms unless the `required_state` has
|
# TODO: Exclude partially stated rooms unless the `required_state` has
|
||||||
# `["m.room.member", "$LAZY"]`
|
# `["m.room.member", "$LAZY"]`
|
||||||
|
|
||||||
filtered_room_id_set = set(room_id_set)
|
filtered_room_id_set = set(sync_room_map.keys())
|
||||||
|
|
||||||
# Filter for Direct-Message (DM) rooms
|
# Filter for Direct-Message (DM) rooms
|
||||||
if filters.is_dm is not None:
|
if filters.is_dm is not None:
|
||||||
|
@ -544,4 +591,57 @@ class SlidingSyncHandler:
|
||||||
if filters.not_tags:
|
if filters.not_tags:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
return filtered_room_id_set
|
# Assemble a new sync room map but only with the `filtered_room_id_set`
|
||||||
|
return {room_id: sync_room_map[room_id] for room_id in filtered_room_id_set}
|
||||||
|
|
||||||
|
async def sort_rooms(
|
||||||
|
self,
|
||||||
|
sync_room_map: Dict[str, RoomsForUser],
|
||||||
|
to_token: StreamToken,
|
||||||
|
) -> List[Tuple[str, RoomsForUser]]:
|
||||||
|
"""
|
||||||
|
Sort by `stream_ordering` of the last event that the user should see in the
|
||||||
|
room. `stream_ordering` is unique so we get a stable sort.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sync_room_map: Dictionary of room IDs to sort along with membership
|
||||||
|
information in the room at the time of `to_token`.
|
||||||
|
to_token: We sort based on the events in the room at this token (<= `to_token`)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A sorted list of room IDs by `stream_ordering` along with membership information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Assemble a map of room ID to the `stream_ordering` of the last activity that the
|
||||||
|
# user should see in the room (<= `to_token`)
|
||||||
|
last_activity_in_room_map: Dict[str, int] = {}
|
||||||
|
for room_id, room_for_user in sync_room_map.items():
|
||||||
|
# If they are fully-joined to the room, let's find the latest activity
|
||||||
|
# at/before the `to_token`.
|
||||||
|
if room_for_user.membership == Membership.JOIN:
|
||||||
|
last_event_result = (
|
||||||
|
await self.store.get_last_event_pos_in_room_before_stream_ordering(
|
||||||
|
room_id, to_token.room_key
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the room has no events at/before the `to_token`, this is probably a
|
||||||
|
# mistake in the code that generates the `sync_room_map` since that should
|
||||||
|
# only give us rooms that the user had membership in during the token range.
|
||||||
|
assert last_event_result is not None
|
||||||
|
|
||||||
|
_, event_pos = last_event_result
|
||||||
|
|
||||||
|
last_activity_in_room_map[room_id] = event_pos.stream
|
||||||
|
else:
|
||||||
|
# Otherwise, if the user has left/been invited/knocked/been banned from
|
||||||
|
# a room, they shouldn't see anything past that point.
|
||||||
|
last_activity_in_room_map[room_id] = room_for_user.event_pos.stream
|
||||||
|
|
||||||
|
return sorted(
|
||||||
|
sync_room_map.items(),
|
||||||
|
# Sort by the last activity (stream_ordering) in the room
|
||||||
|
key=lambda room_info: last_activity_in_room_map[room_info[0]],
|
||||||
|
# We want descending order
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
|
|
@ -1036,9 +1036,11 @@ class SyncHandler:
|
||||||
# FIXME: This gets the state at the latest event before the stream ordering,
|
# FIXME: This gets the state at the latest event before the stream ordering,
|
||||||
# which might not be the same as the "current state" of the room at the time
|
# which might not be the same as the "current state" of the room at the time
|
||||||
# of the stream token if there were multiple forward extremities at the time.
|
# of the stream token if there were multiple forward extremities at the time.
|
||||||
last_event_id = await self.store.get_last_event_in_room_before_stream_ordering(
|
last_event_id = (
|
||||||
room_id,
|
await self.store.get_last_event_id_in_room_before_stream_ordering(
|
||||||
end_token=stream_position.room_key,
|
room_id,
|
||||||
|
end_token=stream_position.room_key,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if last_event_id:
|
if last_event_id:
|
||||||
|
@ -1519,7 +1521,7 @@ class SyncHandler:
|
||||||
# We need to make sure the first event in our batch points to the
|
# We need to make sure the first event in our batch points to the
|
||||||
# last event in the previous batch.
|
# last event in the previous batch.
|
||||||
last_event_id_prev_batch = (
|
last_event_id_prev_batch = (
|
||||||
await self.store.get_last_event_in_room_before_stream_ordering(
|
await self.store.get_last_event_id_in_room_before_stream_ordering(
|
||||||
room_id,
|
room_id,
|
||||||
end_token=since_token.room_key,
|
end_token=since_token.room_key,
|
||||||
)
|
)
|
||||||
|
|
|
@ -895,7 +895,7 @@ class StreamWorkerStore(EventsWorkerStore, SQLBaseStore):
|
||||||
"get_room_event_before_stream_ordering", _f
|
"get_room_event_before_stream_ordering", _f
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_last_event_in_room_before_stream_ordering(
|
async def get_last_event_id_in_room_before_stream_ordering(
|
||||||
self,
|
self,
|
||||||
room_id: str,
|
room_id: str,
|
||||||
end_token: RoomStreamToken,
|
end_token: RoomStreamToken,
|
||||||
|
@ -910,10 +910,38 @@ class StreamWorkerStore(EventsWorkerStore, SQLBaseStore):
|
||||||
The ID of the most recent event, or None if there are no events in the room
|
The ID of the most recent event, or None if there are no events in the room
|
||||||
before this stream ordering.
|
before this stream ordering.
|
||||||
"""
|
"""
|
||||||
|
last_event_result = (
|
||||||
|
await self.get_last_event_pos_in_room_before_stream_ordering(
|
||||||
|
room_id, end_token
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def get_last_event_in_room_before_stream_ordering_txn(
|
if last_event_result:
|
||||||
|
return last_event_result[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_last_event_pos_in_room_before_stream_ordering(
|
||||||
|
self,
|
||||||
|
room_id: str,
|
||||||
|
end_token: RoomStreamToken,
|
||||||
|
) -> Optional[Tuple[str, PersistedEventPosition]]:
|
||||||
|
"""
|
||||||
|
Returns the ID and event position of the last event in a room at or before a
|
||||||
|
stream ordering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id
|
||||||
|
end_token: The token used to stream from
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The ID of the most recent event and it's position, or None if there are no
|
||||||
|
events in the room before this stream ordering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_last_event_pos_in_room_before_stream_ordering_txn(
|
||||||
txn: LoggingTransaction,
|
txn: LoggingTransaction,
|
||||||
) -> Optional[str]:
|
) -> Optional[Tuple[str, PersistedEventPosition]]:
|
||||||
# We're looking for the closest event at or before the token. We need to
|
# We're looking for the closest event at or before the token. We need to
|
||||||
# handle the fact that the stream token can be a vector clock (with an
|
# handle the fact that the stream token can be a vector clock (with an
|
||||||
# `instance_map`) and events can be persisted on different instances
|
# `instance_map`) and events can be persisted on different instances
|
||||||
|
@ -975,13 +1003,15 @@ class StreamWorkerStore(EventsWorkerStore, SQLBaseStore):
|
||||||
topological_ordering=topological_ordering,
|
topological_ordering=topological_ordering,
|
||||||
stream_ordering=stream_ordering,
|
stream_ordering=stream_ordering,
|
||||||
):
|
):
|
||||||
return event_id
|
return event_id, PersistedEventPosition(
|
||||||
|
instance_name, stream_ordering
|
||||||
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return await self.db_pool.runInteraction(
|
return await self.db_pool.runInteraction(
|
||||||
"get_last_event_in_room_before_stream_ordering",
|
"get_last_event_pos_in_room_before_stream_ordering",
|
||||||
get_last_event_in_room_before_stream_ordering_txn,
|
get_last_event_pos_in_room_before_stream_ordering_txn,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_current_room_stream_token_for_room_id(
|
async def get_current_room_stream_token_for_room_id(
|
||||||
|
|
|
@ -175,22 +175,8 @@ class SlidingSyncBody(RequestBodyModel):
|
||||||
ranges: Sliding window ranges. If this field is missing, no sliding window
|
ranges: Sliding window ranges. If this field is missing, no sliding window
|
||||||
is used and all rooms are returned in this list. Integers are
|
is used and all rooms are returned in this list. Integers are
|
||||||
*inclusive*.
|
*inclusive*.
|
||||||
sort: How the list should be sorted on the server. The first value is
|
|
||||||
applied first, then tiebreaks are performed with each subsequent sort
|
|
||||||
listed.
|
|
||||||
|
|
||||||
FIXME: Furthermore, it's not currently defined how servers should behave
|
|
||||||
if they encounter a filter or sort operation they do not recognise. If
|
|
||||||
the server rejects the request with an HTTP 400 then that will break
|
|
||||||
backwards compatibility with new clients vs old servers. However, the
|
|
||||||
client would be otherwise unaware that only some of the sort/filter
|
|
||||||
operations have taken effect. We may need to include a "warnings"
|
|
||||||
section to indicate which sort/filter operations are unrecognised,
|
|
||||||
allowing for some form of graceful degradation of service.
|
|
||||||
-- https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/sync-v3/proposals/3575-sync.md#filter-and-sort-extensions
|
|
||||||
|
|
||||||
slow_get_all_rooms: Just get all rooms (for clients that don't want to deal with
|
slow_get_all_rooms: Just get all rooms (for clients that don't want to deal with
|
||||||
sliding windows). When true, the `ranges` and `sort` fields are ignored.
|
sliding windows). When true, the `ranges` field is ignored.
|
||||||
required_state: Required state for each room returned. An array of event
|
required_state: Required state for each room returned. An array of event
|
||||||
type and state key tuples. Elements in this array are ORd together to
|
type and state key tuples. Elements in this array are ORd together to
|
||||||
produce the final set of state events to return.
|
produce the final set of state events to return.
|
||||||
|
@ -229,12 +215,6 @@ class SlidingSyncBody(RequestBodyModel):
|
||||||
`user_id` and optionally `avatar_url` and `displayname`) for the users used
|
`user_id` and optionally `avatar_url` and `displayname`) for the users used
|
||||||
to calculate the room name.
|
to calculate the room name.
|
||||||
filters: Filters to apply to the list before sorting.
|
filters: Filters to apply to the list before sorting.
|
||||||
bump_event_types: Allowlist of event types which should be considered recent activity
|
|
||||||
when sorting `by_recency`. By omitting event types from this field,
|
|
||||||
clients can ensure that uninteresting events (e.g. a profile rename) do
|
|
||||||
not cause a room to jump to the top of its list(s). Empty or omitted
|
|
||||||
`bump_event_types` have no effect—all events in a room will be
|
|
||||||
considered recent activity.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Filters(RequestBodyModel):
|
class Filters(RequestBodyModel):
|
||||||
|
@ -300,11 +280,9 @@ class SlidingSyncBody(RequestBodyModel):
|
||||||
ranges: Optional[List[Tuple[int, int]]] = None
|
ranges: Optional[List[Tuple[int, int]]] = None
|
||||||
else:
|
else:
|
||||||
ranges: Optional[List[Tuple[conint(ge=0, strict=True), conint(ge=0, strict=True)]]] = None # type: ignore[valid-type]
|
ranges: Optional[List[Tuple[conint(ge=0, strict=True), conint(ge=0, strict=True)]]] = None # type: ignore[valid-type]
|
||||||
sort: Optional[List[StrictStr]] = None
|
|
||||||
slow_get_all_rooms: Optional[StrictBool] = False
|
slow_get_all_rooms: Optional[StrictBool] = False
|
||||||
include_heroes: Optional[StrictBool] = False
|
include_heroes: Optional[StrictBool] = False
|
||||||
filters: Optional[Filters] = None
|
filters: Optional[Filters] = None
|
||||||
bump_event_types: Optional[List[StrictStr]] = None
|
|
||||||
|
|
||||||
class RoomSubscription(CommonRoomParameters):
|
class RoomSubscription(CommonRoomParameters):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
import logging
|
import logging
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from parameterized import parameterized
|
||||||
|
|
||||||
from twisted.test.proto_helpers import MemoryReactor
|
from twisted.test.proto_helpers import MemoryReactor
|
||||||
|
|
||||||
from synapse.api.constants import AccountDataTypes, EventTypes, JoinRules, Membership
|
from synapse.api.constants import AccountDataTypes, EventTypes, JoinRules, Membership
|
||||||
|
@ -79,7 +81,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(room_id_results, set())
|
self.assertEqual(room_id_results.keys(), set())
|
||||||
|
|
||||||
def test_get_newly_joined_room(self) -> None:
|
def test_get_newly_joined_room(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -103,7 +105,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(room_id_results, {room_id})
|
self.assertEqual(room_id_results.keys(), {room_id})
|
||||||
|
|
||||||
def test_get_already_joined_room(self) -> None:
|
def test_get_already_joined_room(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -124,7 +126,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(room_id_results, {room_id})
|
self.assertEqual(room_id_results.keys(), {room_id})
|
||||||
|
|
||||||
def test_get_invited_banned_knocked_room(self) -> None:
|
def test_get_invited_banned_knocked_room(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -180,7 +182,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
|
|
||||||
# Ensure that the invited, ban, and knock rooms show up
|
# Ensure that the invited, ban, and knock rooms show up
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
room_id_results,
|
room_id_results.keys(),
|
||||||
{
|
{
|
||||||
invited_room_id,
|
invited_room_id,
|
||||||
ban_room_id,
|
ban_room_id,
|
||||||
|
@ -226,7 +228,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# The kicked room should show up
|
# The kicked room should show up
|
||||||
self.assertEqual(room_id_results, {kick_room_id})
|
self.assertEqual(room_id_results.keys(), {kick_room_id})
|
||||||
|
|
||||||
def test_forgotten_rooms(self) -> None:
|
def test_forgotten_rooms(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -308,7 +310,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# We shouldn't see the room because it was forgotten
|
# We shouldn't see the room because it was forgotten
|
||||||
self.assertEqual(room_id_results, set())
|
self.assertEqual(room_id_results.keys(), set())
|
||||||
|
|
||||||
def test_only_newly_left_rooms_show_up(self) -> None:
|
def test_only_newly_left_rooms_show_up(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -340,7 +342,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only the newly_left room should show up
|
# Only the newly_left room should show up
|
||||||
self.assertEqual(room_id_results, {room_id2})
|
self.assertEqual(room_id_results.keys(), {room_id2})
|
||||||
|
|
||||||
def test_no_joins_after_to_token(self) -> None:
|
def test_no_joins_after_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -368,7 +370,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_join_during_range_and_left_room_after_to_token(self) -> None:
|
def test_join_during_range_and_left_room_after_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -398,7 +400,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
|
|
||||||
# We should still see the room because we were joined during the
|
# We should still see the room because we were joined during the
|
||||||
# from_token/to_token time period.
|
# from_token/to_token time period.
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_join_before_range_and_left_room_after_to_token(self) -> None:
|
def test_join_before_range_and_left_room_after_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -425,7 +427,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# We should still see the room because we were joined before the `from_token`
|
# We should still see the room because we were joined before the `from_token`
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_kicked_before_range_and_left_after_to_token(self) -> None:
|
def test_kicked_before_range_and_left_after_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -473,7 +475,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# We shouldn't see the room because it was forgotten
|
# We shouldn't see the room because it was forgotten
|
||||||
self.assertEqual(room_id_results, {kick_room_id})
|
self.assertEqual(room_id_results.keys(), {kick_room_id})
|
||||||
|
|
||||||
def test_newly_left_during_range_and_join_leave_after_to_token(self) -> None:
|
def test_newly_left_during_range_and_join_leave_after_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -510,7 +512,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Room should still show up because it's newly_left during the from/to range
|
# Room should still show up because it's newly_left during the from/to range
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_newly_left_during_range_and_join_after_to_token(self) -> None:
|
def test_newly_left_during_range_and_join_after_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -546,7 +548,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Room should still show up because it's newly_left during the from/to range
|
# Room should still show up because it's newly_left during the from/to range
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_no_from_token(self) -> None:
|
def test_no_from_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -587,7 +589,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only rooms we were joined to before the `to_token` should show up
|
# Only rooms we were joined to before the `to_token` should show up
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_from_token_ahead_of_to_token(self) -> None:
|
def test_from_token_ahead_of_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -648,7 +650,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
#
|
#
|
||||||
# There won't be any newly_left rooms because the `from_token` is ahead of the
|
# There won't be any newly_left rooms because the `from_token` is ahead of the
|
||||||
# `to_token` and that range will give no membership changes to check.
|
# `to_token` and that range will give no membership changes to check.
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_leave_before_range_and_join_leave_after_to_token(self) -> None:
|
def test_leave_before_range_and_join_leave_after_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -683,7 +685,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Room shouldn't show up because it was left before the `from_token`
|
# Room shouldn't show up because it was left before the `from_token`
|
||||||
self.assertEqual(room_id_results, set())
|
self.assertEqual(room_id_results.keys(), set())
|
||||||
|
|
||||||
def test_leave_before_range_and_join_after_to_token(self) -> None:
|
def test_leave_before_range_and_join_after_to_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -717,7 +719,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Room shouldn't show up because it was left before the `from_token`
|
# Room shouldn't show up because it was left before the `from_token`
|
||||||
self.assertEqual(room_id_results, set())
|
self.assertEqual(room_id_results.keys(), set())
|
||||||
|
|
||||||
def test_join_leave_multiple_times_during_range_and_after_to_token(
|
def test_join_leave_multiple_times_during_range_and_after_to_token(
|
||||||
self,
|
self,
|
||||||
|
@ -759,7 +761,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Room should show up because it was newly_left and joined during the from/to range
|
# Room should show up because it was newly_left and joined during the from/to range
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_join_leave_multiple_times_before_range_and_after_to_token(
|
def test_join_leave_multiple_times_before_range_and_after_to_token(
|
||||||
self,
|
self,
|
||||||
|
@ -799,7 +801,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Room should show up because we were joined before the from/to range
|
# Room should show up because we were joined before the from/to range
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_invite_before_range_and_join_leave_after_to_token(
|
def test_invite_before_range_and_join_leave_after_to_token(
|
||||||
self,
|
self,
|
||||||
|
@ -836,7 +838,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Room should show up because we were invited before the from/to range
|
# Room should show up because we were invited before the from/to range
|
||||||
self.assertEqual(room_id_results, {room_id1})
|
self.assertEqual(room_id_results.keys(), {room_id1})
|
||||||
|
|
||||||
def test_multiple_rooms_are_not_confused(
|
def test_multiple_rooms_are_not_confused(
|
||||||
self,
|
self,
|
||||||
|
@ -889,7 +891,7 @@ class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
room_id_results,
|
room_id_results.keys(),
|
||||||
{
|
{
|
||||||
# `room_id1` shouldn't show up because we left before the from/to range
|
# `room_id1` shouldn't show up because we left before the from/to range
|
||||||
#
|
#
|
||||||
|
@ -1048,7 +1050,6 @@ class GetSyncRoomIdsForUserEventShardTestCase(BaseMultiWorkerStreamTestCase):
|
||||||
|
|
||||||
# Get a token while things are stuck after our activity
|
# Get a token while things are stuck after our activity
|
||||||
stuck_activity_token = self.event_sources.get_current_token()
|
stuck_activity_token = self.event_sources.get_current_token()
|
||||||
logger.info("stuck_activity_token %s", stuck_activity_token)
|
|
||||||
# Let's make sure we're working with a token that has an `instance_map`
|
# Let's make sure we're working with a token that has an `instance_map`
|
||||||
self.assertNotEqual(len(stuck_activity_token.room_key.instance_map), 0)
|
self.assertNotEqual(len(stuck_activity_token.room_key.instance_map), 0)
|
||||||
|
|
||||||
|
@ -1058,7 +1059,6 @@ class GetSyncRoomIdsForUserEventShardTestCase(BaseMultiWorkerStreamTestCase):
|
||||||
join_on_worker2_pos = self.get_success(
|
join_on_worker2_pos = self.get_success(
|
||||||
self.store.get_position_for_event(join_on_worker2_response["event_id"])
|
self.store.get_position_for_event(join_on_worker2_response["event_id"])
|
||||||
)
|
)
|
||||||
logger.info("join_on_worker2_pos %s", join_on_worker2_pos)
|
|
||||||
# Ensure the join technially came after our token
|
# Ensure the join technially came after our token
|
||||||
self.assertGreater(
|
self.assertGreater(
|
||||||
join_on_worker2_pos.stream,
|
join_on_worker2_pos.stream,
|
||||||
|
@ -1077,7 +1077,6 @@ class GetSyncRoomIdsForUserEventShardTestCase(BaseMultiWorkerStreamTestCase):
|
||||||
join_on_worker3_pos = self.get_success(
|
join_on_worker3_pos = self.get_success(
|
||||||
self.store.get_position_for_event(join_on_worker3_response["event_id"])
|
self.store.get_position_for_event(join_on_worker3_response["event_id"])
|
||||||
)
|
)
|
||||||
logger.info("join_on_worker3_pos %s", join_on_worker3_pos)
|
|
||||||
# Ensure the join came after the min but still encapsulated by the token
|
# Ensure the join came after the min but still encapsulated by the token
|
||||||
self.assertGreaterEqual(
|
self.assertGreaterEqual(
|
||||||
join_on_worker3_pos.stream,
|
join_on_worker3_pos.stream,
|
||||||
|
@ -1103,7 +1102,7 @@ class GetSyncRoomIdsForUserEventShardTestCase(BaseMultiWorkerStreamTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
room_id_results,
|
room_id_results.keys(),
|
||||||
{
|
{
|
||||||
room_id1,
|
room_id1,
|
||||||
# room_id2 shouldn't show up because we left before the from/to range
|
# room_id2 shouldn't show up because we left before the from/to range
|
||||||
|
@ -1217,11 +1216,20 @@ class FilterRoomsTestCase(HomeserverTestCase):
|
||||||
|
|
||||||
after_rooms_token = self.event_sources.get_current_token()
|
after_rooms_token = self.event_sources.get_current_token()
|
||||||
|
|
||||||
|
# Get the rooms the user should be syncing with
|
||||||
|
sync_room_map = self.get_success(
|
||||||
|
self.sliding_sync_handler.get_sync_room_ids_for_user(
|
||||||
|
UserID.from_string(user1_id),
|
||||||
|
from_token=None,
|
||||||
|
to_token=after_rooms_token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Try with `is_dm=True`
|
# Try with `is_dm=True`
|
||||||
truthy_filtered_room_ids = self.get_success(
|
truthy_filtered_room_map = self.get_success(
|
||||||
self.sliding_sync_handler.filter_rooms(
|
self.sliding_sync_handler.filter_rooms(
|
||||||
UserID.from_string(user1_id),
|
UserID.from_string(user1_id),
|
||||||
{room_id, dm_room_id},
|
sync_room_map,
|
||||||
SlidingSyncConfig.SlidingSyncList.Filters(
|
SlidingSyncConfig.SlidingSyncList.Filters(
|
||||||
is_dm=True,
|
is_dm=True,
|
||||||
),
|
),
|
||||||
|
@ -1229,13 +1237,13 @@ class FilterRoomsTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(truthy_filtered_room_ids, {dm_room_id})
|
self.assertEqual(truthy_filtered_room_map.keys(), {dm_room_id})
|
||||||
|
|
||||||
# Try with `is_dm=False`
|
# Try with `is_dm=False`
|
||||||
falsy_filtered_room_ids = self.get_success(
|
falsy_filtered_room_map = self.get_success(
|
||||||
self.sliding_sync_handler.filter_rooms(
|
self.sliding_sync_handler.filter_rooms(
|
||||||
UserID.from_string(user1_id),
|
UserID.from_string(user1_id),
|
||||||
{room_id, dm_room_id},
|
sync_room_map,
|
||||||
SlidingSyncConfig.SlidingSyncList.Filters(
|
SlidingSyncConfig.SlidingSyncList.Filters(
|
||||||
is_dm=False,
|
is_dm=False,
|
||||||
),
|
),
|
||||||
|
@ -1243,4 +1251,160 @@ class FilterRoomsTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(falsy_filtered_room_ids, {room_id})
|
self.assertEqual(falsy_filtered_room_map.keys(), {room_id})
|
||||||
|
|
||||||
|
|
||||||
|
class SortRoomsTestCase(HomeserverTestCase):
|
||||||
|
"""
|
||||||
|
Tests Sliding Sync handler `sort_rooms()` to make sure it sorts/orders rooms
|
||||||
|
correctly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
servlets = [
|
||||||
|
admin.register_servlets,
|
||||||
|
knock.register_servlets,
|
||||||
|
login.register_servlets,
|
||||||
|
room.register_servlets,
|
||||||
|
]
|
||||||
|
|
||||||
|
def default_config(self) -> JsonDict:
|
||||||
|
config = super().default_config()
|
||||||
|
# Enable sliding sync
|
||||||
|
config["experimental_features"] = {"msc3575_enabled": True}
|
||||||
|
return config
|
||||||
|
|
||||||
|
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||||
|
self.sliding_sync_handler = self.hs.get_sliding_sync_handler()
|
||||||
|
self.store = self.hs.get_datastores().main
|
||||||
|
self.event_sources = hs.get_event_sources()
|
||||||
|
|
||||||
|
def test_sort_activity_basic(self) -> None:
|
||||||
|
"""
|
||||||
|
Rooms with newer activity are sorted first.
|
||||||
|
"""
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
room_id2 = self.helper.create_room_as(
|
||||||
|
user1_id,
|
||||||
|
tok=user1_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
after_rooms_token = self.event_sources.get_current_token()
|
||||||
|
|
||||||
|
# Get the rooms the user should be syncing with
|
||||||
|
sync_room_map = self.get_success(
|
||||||
|
self.sliding_sync_handler.get_sync_room_ids_for_user(
|
||||||
|
UserID.from_string(user1_id),
|
||||||
|
from_token=None,
|
||||||
|
to_token=after_rooms_token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sort the rooms (what we're testing)
|
||||||
|
sorted_room_info = self.get_success(
|
||||||
|
self.sliding_sync_handler.sort_rooms(
|
||||||
|
sync_room_map=sync_room_map,
|
||||||
|
to_token=after_rooms_token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[room_id for room_id, _ in sorted_room_info],
|
||||||
|
[room_id2, room_id1],
|
||||||
|
)
|
||||||
|
|
||||||
|
@parameterized.expand(
|
||||||
|
[
|
||||||
|
(Membership.LEAVE,),
|
||||||
|
(Membership.INVITE,),
|
||||||
|
(Membership.KNOCK,),
|
||||||
|
(Membership.BAN,),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_activity_after_xxx(self, room1_membership: str) -> None:
|
||||||
|
"""
|
||||||
|
When someone has left/been invited/knocked/been banned from a room, they
|
||||||
|
shouldn't take anything into account after that membership event.
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
|
||||||
|
before_rooms_token = self.event_sources.get_current_token()
|
||||||
|
|
||||||
|
# Create the rooms as user2 so we can have user1 with a clean slate to work from
|
||||||
|
# and join in whatever order we need for the tests.
|
||||||
|
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True)
|
||||||
|
# If we're testing knocks, set the room to knock
|
||||||
|
if room1_membership == Membership.KNOCK:
|
||||||
|
self.helper.send_state(
|
||||||
|
room_id1,
|
||||||
|
EventTypes.JoinRules,
|
||||||
|
{"join_rule": JoinRules.KNOCK},
|
||||||
|
tok=user2_tok,
|
||||||
|
)
|
||||||
|
room_id2 = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True)
|
||||||
|
room_id3 = self.helper.create_room_as(user2_id, tok=user2_tok, is_public=True)
|
||||||
|
|
||||||
|
# Here is the activity with user1 that will determine the sort of the rooms
|
||||||
|
# (room2, room1, room3)
|
||||||
|
self.helper.join(room_id3, user1_id, tok=user1_tok)
|
||||||
|
if room1_membership == Membership.LEAVE:
|
||||||
|
self.helper.join(room_id1, user1_id, tok=user1_tok)
|
||||||
|
self.helper.leave(room_id1, user1_id, tok=user1_tok)
|
||||||
|
elif room1_membership == Membership.INVITE:
|
||||||
|
self.helper.invite(room_id1, src=user2_id, targ=user1_id, tok=user2_tok)
|
||||||
|
elif room1_membership == Membership.KNOCK:
|
||||||
|
self.helper.knock(room_id1, user1_id, tok=user1_tok)
|
||||||
|
elif room1_membership == Membership.BAN:
|
||||||
|
self.helper.ban(room_id1, src=user2_id, targ=user1_id, tok=user2_tok)
|
||||||
|
self.helper.join(room_id2, user1_id, tok=user1_tok)
|
||||||
|
|
||||||
|
# Activity before the token but the user is only been xxx to this room so it
|
||||||
|
# shouldn't be taken into account
|
||||||
|
self.helper.send(room_id1, "activity in room1", tok=user2_tok)
|
||||||
|
|
||||||
|
after_rooms_token = self.event_sources.get_current_token()
|
||||||
|
|
||||||
|
# Activity after the token. Just make it in a different order than what we
|
||||||
|
# expect to make sure we're not taking the activity after the token into
|
||||||
|
# account.
|
||||||
|
self.helper.send(room_id1, "activity in room1", tok=user2_tok)
|
||||||
|
self.helper.send(room_id2, "activity in room2", tok=user2_tok)
|
||||||
|
self.helper.send(room_id3, "activity in room3", tok=user2_tok)
|
||||||
|
|
||||||
|
# Get the rooms the user should be syncing with
|
||||||
|
sync_room_map = self.get_success(
|
||||||
|
self.sliding_sync_handler.get_sync_room_ids_for_user(
|
||||||
|
UserID.from_string(user1_id),
|
||||||
|
from_token=before_rooms_token,
|
||||||
|
to_token=after_rooms_token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sort the rooms (what we're testing)
|
||||||
|
sorted_room_info = self.get_success(
|
||||||
|
self.sliding_sync_handler.sort_rooms(
|
||||||
|
sync_room_map=sync_room_map,
|
||||||
|
to_token=after_rooms_token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[room_id for room_id, _ in sorted_room_info],
|
||||||
|
[room_id2, room_id1, room_id3],
|
||||||
|
"Corresponding map to disambiguate the opaque room IDs: "
|
||||||
|
+ str(
|
||||||
|
{
|
||||||
|
"room_id1": room_id1,
|
||||||
|
"room_id2": room_id2,
|
||||||
|
"room_id3": room_id3,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
|
@ -1299,7 +1299,6 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
"lists": {
|
"lists": {
|
||||||
"foo-list": {
|
"foo-list": {
|
||||||
"ranges": [[0, 99]],
|
"ranges": [[0, 99]],
|
||||||
"sort": ["by_notification_level", "by_recency", "by_name"],
|
|
||||||
"required_state": [
|
"required_state": [
|
||||||
["m.room.join_rules", ""],
|
["m.room.join_rules", ""],
|
||||||
["m.room.history_visibility", ""],
|
["m.room.history_visibility", ""],
|
||||||
|
@ -1361,7 +1360,6 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
"lists": {
|
"lists": {
|
||||||
"foo-list": {
|
"foo-list": {
|
||||||
"ranges": [[0, 99]],
|
"ranges": [[0, 99]],
|
||||||
"sort": ["by_notification_level", "by_recency", "by_name"],
|
|
||||||
"required_state": [
|
"required_state": [
|
||||||
["m.room.join_rules", ""],
|
["m.room.join_rules", ""],
|
||||||
["m.room.history_visibility", ""],
|
["m.room.history_visibility", ""],
|
||||||
|
@ -1415,14 +1413,12 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
"lists": {
|
"lists": {
|
||||||
"dms": {
|
"dms": {
|
||||||
"ranges": [[0, 99]],
|
"ranges": [[0, 99]],
|
||||||
"sort": ["by_recency"],
|
|
||||||
"required_state": [],
|
"required_state": [],
|
||||||
"timeline_limit": 1,
|
"timeline_limit": 1,
|
||||||
"filters": {"is_dm": True},
|
"filters": {"is_dm": True},
|
||||||
},
|
},
|
||||||
"foo-list": {
|
"foo-list": {
|
||||||
"ranges": [[0, 99]],
|
"ranges": [[0, 99]],
|
||||||
"sort": ["by_recency"],
|
|
||||||
"required_state": [],
|
"required_state": [],
|
||||||
"timeline_limit": 1,
|
"timeline_limit": 1,
|
||||||
"filters": {"is_dm": False},
|
"filters": {"is_dm": False},
|
||||||
|
@ -1463,3 +1459,60 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
],
|
],
|
||||||
list(channel.json_body["lists"]["foo-list"]),
|
list(channel.json_body["lists"]["foo-list"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_sort_list(self) -> None:
|
||||||
|
"""
|
||||||
|
Test that the lists are sorted by `stream_ordering`
|
||||||
|
"""
|
||||||
|
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, is_public=True)
|
||||||
|
room_id2 = self.helper.create_room_as(user1_id, tok=user1_tok, is_public=True)
|
||||||
|
room_id3 = self.helper.create_room_as(user1_id, tok=user1_tok, is_public=True)
|
||||||
|
|
||||||
|
# Activity that will order the rooms
|
||||||
|
self.helper.send(room_id3, "activity in room3", tok=user1_tok)
|
||||||
|
self.helper.send(room_id1, "activity in room1", tok=user1_tok)
|
||||||
|
self.helper.send(room_id2, "activity in room2", tok=user1_tok)
|
||||||
|
|
||||||
|
# Make the Sliding Sync request
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
self.sync_endpoint,
|
||||||
|
{
|
||||||
|
"lists": {
|
||||||
|
"foo-list": {
|
||||||
|
"ranges": [[0, 99]],
|
||||||
|
"required_state": [
|
||||||
|
["m.room.join_rules", ""],
|
||||||
|
["m.room.history_visibility", ""],
|
||||||
|
["m.space.child", "*"],
|
||||||
|
],
|
||||||
|
"timeline_limit": 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
access_token=user1_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200, channel.json_body)
|
||||||
|
|
||||||
|
# Make sure it has the foo-list we requested
|
||||||
|
self.assertListEqual(
|
||||||
|
list(channel.json_body["lists"].keys()),
|
||||||
|
["foo-list"],
|
||||||
|
channel.json_body["lists"].keys(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the list is sorted in the way we expect
|
||||||
|
self.assertListEqual(
|
||||||
|
list(channel.json_body["lists"]["foo-list"]["ops"]),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "SYNC",
|
||||||
|
"range": [0, 99],
|
||||||
|
"room_ids": [room_id2, room_id1, room_id3],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
channel.json_body["lists"]["foo-list"],
|
||||||
|
)
|
||||||
|
|
|
@ -277,7 +277,7 @@ class PaginationTestCase(HomeserverTestCase):
|
||||||
|
|
||||||
class GetLastEventInRoomBeforeStreamOrderingTestCase(HomeserverTestCase):
|
class GetLastEventInRoomBeforeStreamOrderingTestCase(HomeserverTestCase):
|
||||||
"""
|
"""
|
||||||
Test `get_last_event_in_room_before_stream_ordering(...)`
|
Test `get_last_event_pos_in_room_before_stream_ordering(...)`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
servlets = [
|
servlets = [
|
||||||
|
@ -336,14 +336,14 @@ class GetLastEventInRoomBeforeStreamOrderingTestCase(HomeserverTestCase):
|
||||||
|
|
||||||
room_id = self.helper.create_room_as(user1_id, tok=user1_tok, is_public=True)
|
room_id = self.helper.create_room_as(user1_id, tok=user1_tok, is_public=True)
|
||||||
|
|
||||||
last_event = self.get_success(
|
last_event_result = self.get_success(
|
||||||
self.store.get_last_event_in_room_before_stream_ordering(
|
self.store.get_last_event_pos_in_room_before_stream_ordering(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
end_token=before_room_token.room_key,
|
end_token=before_room_token.room_key,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertIsNone(last_event)
|
self.assertIsNone(last_event_result)
|
||||||
|
|
||||||
def test_after_room_created(self) -> None:
|
def test_after_room_created(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -356,14 +356,16 @@ class GetLastEventInRoomBeforeStreamOrderingTestCase(HomeserverTestCase):
|
||||||
|
|
||||||
after_room_token = self.event_sources.get_current_token()
|
after_room_token = self.event_sources.get_current_token()
|
||||||
|
|
||||||
last_event = self.get_success(
|
last_event_result = self.get_success(
|
||||||
self.store.get_last_event_in_room_before_stream_ordering(
|
self.store.get_last_event_pos_in_room_before_stream_ordering(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
end_token=after_room_token.room_key,
|
end_token=after_room_token.room_key,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
assert last_event_result is not None
|
||||||
|
last_event_id, _ = last_event_result
|
||||||
|
|
||||||
self.assertIsNotNone(last_event)
|
self.assertIsNotNone(last_event_id)
|
||||||
|
|
||||||
def test_activity_in_other_rooms(self) -> None:
|
def test_activity_in_other_rooms(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -380,16 +382,18 @@ class GetLastEventInRoomBeforeStreamOrderingTestCase(HomeserverTestCase):
|
||||||
|
|
||||||
after_room_token = self.event_sources.get_current_token()
|
after_room_token = self.event_sources.get_current_token()
|
||||||
|
|
||||||
last_event = self.get_success(
|
last_event_result = self.get_success(
|
||||||
self.store.get_last_event_in_room_before_stream_ordering(
|
self.store.get_last_event_pos_in_room_before_stream_ordering(
|
||||||
room_id=room_id1,
|
room_id=room_id1,
|
||||||
end_token=after_room_token.room_key,
|
end_token=after_room_token.room_key,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
assert last_event_result is not None
|
||||||
|
last_event_id, _ = last_event_result
|
||||||
|
|
||||||
# Make sure it's the event we expect (which also means we know it's from the
|
# Make sure it's the event we expect (which also means we know it's from the
|
||||||
# correct room)
|
# correct room)
|
||||||
self.assertEqual(last_event, event_response["event_id"])
|
self.assertEqual(last_event_id, event_response["event_id"])
|
||||||
|
|
||||||
def test_activity_after_token_has_no_effect(self) -> None:
|
def test_activity_after_token_has_no_effect(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -408,15 +412,17 @@ class GetLastEventInRoomBeforeStreamOrderingTestCase(HomeserverTestCase):
|
||||||
self.helper.send(room_id1, "after1", tok=user1_tok)
|
self.helper.send(room_id1, "after1", tok=user1_tok)
|
||||||
self.helper.send(room_id1, "after2", tok=user1_tok)
|
self.helper.send(room_id1, "after2", tok=user1_tok)
|
||||||
|
|
||||||
last_event = self.get_success(
|
last_event_result = self.get_success(
|
||||||
self.store.get_last_event_in_room_before_stream_ordering(
|
self.store.get_last_event_pos_in_room_before_stream_ordering(
|
||||||
room_id=room_id1,
|
room_id=room_id1,
|
||||||
end_token=after_room_token.room_key,
|
end_token=after_room_token.room_key,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
assert last_event_result is not None
|
||||||
|
last_event_id, _ = last_event_result
|
||||||
|
|
||||||
# Make sure it's the last event before the token
|
# Make sure it's the last event before the token
|
||||||
self.assertEqual(last_event, event_response["event_id"])
|
self.assertEqual(last_event_id, event_response["event_id"])
|
||||||
|
|
||||||
def test_last_event_within_sharded_token(self) -> None:
|
def test_last_event_within_sharded_token(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -457,18 +463,20 @@ class GetLastEventInRoomBeforeStreamOrderingTestCase(HomeserverTestCase):
|
||||||
self.helper.send(room_id1, "after1", tok=user1_tok)
|
self.helper.send(room_id1, "after1", tok=user1_tok)
|
||||||
self.helper.send(room_id1, "after2", tok=user1_tok)
|
self.helper.send(room_id1, "after2", tok=user1_tok)
|
||||||
|
|
||||||
last_event = self.get_success(
|
last_event_result = self.get_success(
|
||||||
self.store.get_last_event_in_room_before_stream_ordering(
|
self.store.get_last_event_pos_in_room_before_stream_ordering(
|
||||||
room_id=room_id1,
|
room_id=room_id1,
|
||||||
end_token=end_token,
|
end_token=end_token,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
assert last_event_result is not None
|
||||||
|
last_event_id, _ = last_event_result
|
||||||
|
|
||||||
# Should find closest event at/before the token in room1
|
# Should find closest event before the token in room1
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
last_event,
|
last_event_id,
|
||||||
event_response3["event_id"],
|
event_response3["event_id"],
|
||||||
f"We expected {event_response3['event_id']} but saw {last_event} which corresponds to "
|
f"We expected {event_response3['event_id']} but saw {last_event_id} which corresponds to "
|
||||||
+ str(
|
+ str(
|
||||||
{
|
{
|
||||||
"event1": event_response1["event_id"],
|
"event1": event_response1["event_id"],
|
||||||
|
@ -514,18 +522,20 @@ class GetLastEventInRoomBeforeStreamOrderingTestCase(HomeserverTestCase):
|
||||||
self.helper.send(room_id1, "after1", tok=user1_tok)
|
self.helper.send(room_id1, "after1", tok=user1_tok)
|
||||||
self.helper.send(room_id1, "after2", tok=user1_tok)
|
self.helper.send(room_id1, "after2", tok=user1_tok)
|
||||||
|
|
||||||
last_event = self.get_success(
|
last_event_result = self.get_success(
|
||||||
self.store.get_last_event_in_room_before_stream_ordering(
|
self.store.get_last_event_pos_in_room_before_stream_ordering(
|
||||||
room_id=room_id1,
|
room_id=room_id1,
|
||||||
end_token=end_token,
|
end_token=end_token,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
assert last_event_result is not None
|
||||||
|
last_event_id, _ = last_event_result
|
||||||
|
|
||||||
# Should find closest event at/before the token in room1
|
# Should find closest event before the token in room1
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
last_event,
|
last_event_id,
|
||||||
event_response2["event_id"],
|
event_response2["event_id"],
|
||||||
f"We expected {event_response2['event_id']} but saw {last_event} which corresponds to "
|
f"We expected {event_response2['event_id']} but saw {last_event_id} which corresponds to "
|
||||||
+ str(
|
+ str(
|
||||||
{
|
{
|
||||||
"event1": event_response1["event_id"],
|
"event1": event_response1["event_id"],
|
||||||
|
|
Loading…
Reference in a new issue