mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-21 17:15:38 +03:00
Add is_dm
filtering to Sliding Sync /sync
(#17277)
Based on [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575): Sliding Sync
This commit is contained in:
parent
8aaff851b1
commit
c12ee0d5ba
5 changed files with 416 additions and 7 deletions
1
changelog.d/17277.feature
Normal file
1
changelog.d/17277.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add `is_dm` filtering to experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint.
|
|
@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, AbstractSet, Dict, List, Optional
|
||||||
|
|
||||||
from immutabledict import immutabledict
|
from immutabledict import immutabledict
|
||||||
|
|
||||||
from synapse.api.constants import 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.types import Requester, RoomStreamToken, StreamToken, UserID
|
||||||
from synapse.types.handlers import OperationType, SlidingSyncConfig, SlidingSyncResult
|
from synapse.types.handlers import OperationType, SlidingSyncConfig, SlidingSyncResult
|
||||||
|
@ -69,9 +69,19 @@ class SlidingSyncHandler:
|
||||||
from_token: Optional[StreamToken] = None,
|
from_token: Optional[StreamToken] = None,
|
||||||
timeout_ms: int = 0,
|
timeout_ms: int = 0,
|
||||||
) -> SlidingSyncResult:
|
) -> SlidingSyncResult:
|
||||||
"""Get the sync for a client if we have new data for it now. Otherwise
|
"""
|
||||||
|
Get the sync for a client if we have new data for it now. Otherwise
|
||||||
wait for new data to arrive on the server. If the timeout expires, then
|
wait for new data to arrive on the server. If the timeout expires, then
|
||||||
return an empty sync result.
|
return an empty sync result.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester: The user making the request
|
||||||
|
sync_config: Sync configuration
|
||||||
|
from_token: The point in the stream to sync from. Token of the end of the
|
||||||
|
previous batch. May be `None` if this is the initial sync request.
|
||||||
|
timeout_ms: The time in milliseconds to wait for new data to arrive. If 0,
|
||||||
|
we will immediately but there might not be any new data so we just return an
|
||||||
|
empty response.
|
||||||
"""
|
"""
|
||||||
# If the user is not part of the mau group, then check that limits have
|
# If the user is not part of the mau group, then check that limits have
|
||||||
# not been exceeded (if not part of the group by this point, almost certain
|
# not been exceeded (if not part of the group by this point, almost certain
|
||||||
|
@ -143,6 +153,14 @@ class SlidingSyncHandler:
|
||||||
"""
|
"""
|
||||||
Generates the response body of a Sliding Sync result, represented as a
|
Generates the response body of a Sliding Sync result, represented as a
|
||||||
`SlidingSyncResult`.
|
`SlidingSyncResult`.
|
||||||
|
|
||||||
|
We fetch data according to the token range (> `from_token` and <= `to_token`).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sync_config: Sync configuration
|
||||||
|
to_token: The point in the stream to sync up to.
|
||||||
|
from_token: The point in the stream to sync from. Token of the end of the
|
||||||
|
previous batch. May be `None` if this is the initial sync request.
|
||||||
"""
|
"""
|
||||||
user_id = sync_config.user.to_string()
|
user_id = sync_config.user.to_string()
|
||||||
app_service = self.store.get_app_service_by_user_id(user_id)
|
app_service = self.store.get_app_service_by_user_id(user_id)
|
||||||
|
@ -163,11 +181,12 @@ class SlidingSyncHandler:
|
||||||
lists: Dict[str, SlidingSyncResult.SlidingWindowList] = {}
|
lists: Dict[str, SlidingSyncResult.SlidingWindowList] = {}
|
||||||
if sync_config.lists:
|
if sync_config.lists:
|
||||||
for list_key, list_config in sync_config.lists.items():
|
for list_key, list_config in sync_config.lists.items():
|
||||||
# TODO: Apply filters
|
# Apply filters
|
||||||
#
|
|
||||||
# TODO: Exclude partially stated rooms unless the `required_state` has
|
|
||||||
# `["m.room.member", "$LAZY"]`
|
|
||||||
filtered_room_ids = room_id_set
|
filtered_room_ids = room_id_set
|
||||||
|
if list_config.filters is not None:
|
||||||
|
filtered_room_ids = await self.filter_rooms(
|
||||||
|
sync_config.user, room_id_set, list_config.filters, to_token
|
||||||
|
)
|
||||||
# TODO: Apply sorts
|
# TODO: Apply sorts
|
||||||
sorted_room_ids = sorted(filtered_room_ids)
|
sorted_room_ids = sorted(filtered_room_ids)
|
||||||
|
|
||||||
|
@ -217,6 +236,12 @@ class SlidingSyncHandler:
|
||||||
`forgotten` flag to the `room_memberships` table in Synapse. There isn't a way
|
`forgotten` flag to the `room_memberships` table in Synapse. There isn't a way
|
||||||
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:
|
||||||
|
user: User to fetch rooms for
|
||||||
|
to_token: The token to fetch rooms up to.
|
||||||
|
from_token: The point in the stream to sync from.
|
||||||
"""
|
"""
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
|
|
||||||
|
@ -439,3 +464,84 @@ class SlidingSyncHandler:
|
||||||
sync_room_id_set.add(room_id)
|
sync_room_id_set.add(room_id)
|
||||||
|
|
||||||
return sync_room_id_set
|
return sync_room_id_set
|
||||||
|
|
||||||
|
async def filter_rooms(
|
||||||
|
self,
|
||||||
|
user: UserID,
|
||||||
|
room_id_set: AbstractSet[str],
|
||||||
|
filters: SlidingSyncConfig.SlidingSyncList.Filters,
|
||||||
|
to_token: StreamToken,
|
||||||
|
) -> AbstractSet[str]:
|
||||||
|
"""
|
||||||
|
Filter rooms based on the sync request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user: User to filter rooms for
|
||||||
|
room_id_set: Set of room IDs to filter down
|
||||||
|
filters: Filters to apply
|
||||||
|
to_token: We filter based on the state of the room at this token
|
||||||
|
"""
|
||||||
|
user_id = user.to_string()
|
||||||
|
|
||||||
|
# TODO: Apply filters
|
||||||
|
#
|
||||||
|
# TODO: Exclude partially stated rooms unless the `required_state` has
|
||||||
|
# `["m.room.member", "$LAZY"]`
|
||||||
|
|
||||||
|
filtered_room_id_set = set(room_id_set)
|
||||||
|
|
||||||
|
# Filter for Direct-Message (DM) rooms
|
||||||
|
if filters.is_dm is not None:
|
||||||
|
# We're using global account data (`m.direct`) instead of checking for
|
||||||
|
# `is_direct` on membership events because that property only appears for
|
||||||
|
# the invitee membership event (doesn't show up for the inviter). Account
|
||||||
|
# data is set by the client so it needs to be scrutinized.
|
||||||
|
#
|
||||||
|
# We're unable to take `to_token` into account for global account data since
|
||||||
|
# we only keep track of the latest account data for the user.
|
||||||
|
dm_map = await self.store.get_global_account_data_by_type_for_user(
|
||||||
|
user_id, AccountDataTypes.DIRECT
|
||||||
|
)
|
||||||
|
|
||||||
|
# Flatten out the map
|
||||||
|
dm_room_id_set = set()
|
||||||
|
if dm_map:
|
||||||
|
for room_ids in dm_map.values():
|
||||||
|
# Account data should be a list of room IDs. Ignore anything else
|
||||||
|
if isinstance(room_ids, list):
|
||||||
|
for room_id in room_ids:
|
||||||
|
if isinstance(room_id, str):
|
||||||
|
dm_room_id_set.add(room_id)
|
||||||
|
|
||||||
|
if filters.is_dm:
|
||||||
|
# Only DM rooms please
|
||||||
|
filtered_room_id_set = filtered_room_id_set.intersection(dm_room_id_set)
|
||||||
|
else:
|
||||||
|
# Only non-DM rooms please
|
||||||
|
filtered_room_id_set = filtered_room_id_set.difference(dm_room_id_set)
|
||||||
|
|
||||||
|
if filters.spaces:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
if filters.is_encrypted:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
if filters.is_invite:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
if filters.room_types:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
if filters.not_room_types:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
if filters.room_name_like:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
if filters.tags:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
if filters.not_tags:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
return filtered_room_id_set
|
||||||
|
|
|
@ -238,6 +238,53 @@ class SlidingSyncBody(RequestBodyModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Filters(RequestBodyModel):
|
class Filters(RequestBodyModel):
|
||||||
|
"""
|
||||||
|
All fields are applied with AND operators, hence if `is_dm: True` and
|
||||||
|
`is_encrypted: True` then only Encrypted DM rooms will be returned. The
|
||||||
|
absence of fields implies no filter on that criteria: it does NOT imply
|
||||||
|
`False`. These fields may be expanded through use of extensions.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
is_dm: Flag which only returns rooms present (or not) in the DM section
|
||||||
|
of account data. If unset, both DM rooms and non-DM rooms are returned.
|
||||||
|
If False, only non-DM rooms are returned. If True, only DM rooms are
|
||||||
|
returned.
|
||||||
|
spaces: Filter the room based on the space they belong to according to
|
||||||
|
`m.space.child` state events. If multiple spaces are present, a room can
|
||||||
|
be part of any one of the listed spaces (OR'd). The server will inspect
|
||||||
|
the `m.space.child` state events for the JOINED space room IDs given.
|
||||||
|
Servers MUST NOT navigate subspaces. It is up to the client to give a
|
||||||
|
complete list of spaces to navigate. Only rooms directly mentioned as
|
||||||
|
`m.space.child` events in these spaces will be returned. Unknown spaces
|
||||||
|
or spaces the user is not joined to will be ignored.
|
||||||
|
is_encrypted: Flag which only returns rooms which have an
|
||||||
|
`m.room.encryption` state event. If unset, both encrypted and
|
||||||
|
unencrypted rooms are returned. If `False`, only unencrypted rooms are
|
||||||
|
returned. If `True`, only encrypted rooms are returned.
|
||||||
|
is_invite: Flag which only returns rooms the user is currently invited
|
||||||
|
to. If unset, both invited and joined rooms are returned. If `False`, no
|
||||||
|
invited rooms are returned. If `True`, only invited rooms are returned.
|
||||||
|
room_types: If specified, only rooms where the `m.room.create` event has
|
||||||
|
a `type` matching one of the strings in this array will be returned. If
|
||||||
|
this field is unset, all rooms are returned regardless of type. This can
|
||||||
|
be used to get the initial set of spaces for an account. For rooms which
|
||||||
|
do not have a room type, use `null`/`None` to include them.
|
||||||
|
not_room_types: Same as `room_types` but inverted. This can be used to
|
||||||
|
filter out spaces from the room list. If a type is in both `room_types`
|
||||||
|
and `not_room_types`, then `not_room_types` wins and they are not included
|
||||||
|
in the result.
|
||||||
|
room_name_like: Filter the room name. Case-insensitive partial matching
|
||||||
|
e.g 'foo' matches 'abFooab'. The term 'like' is inspired by SQL 'LIKE',
|
||||||
|
and the text here is similar to '%foo%'.
|
||||||
|
tags: Filter the room based on its room tags. If multiple tags are
|
||||||
|
present, a room can have any one of the listed tags (OR'd).
|
||||||
|
not_tags: Filter the room based on its room tags. Takes priority over
|
||||||
|
`tags`. For example, a room with tags A and B with filters `tags: [A]`
|
||||||
|
`not_tags: [B]` would NOT be included because `not_tags` takes priority over
|
||||||
|
`tags`. This filter is useful if your rooms list does NOT include the
|
||||||
|
list of favourite rooms again.
|
||||||
|
"""
|
||||||
|
|
||||||
is_dm: Optional[StrictBool] = None
|
is_dm: Optional[StrictBool] = None
|
||||||
spaces: Optional[List[StrictStr]] = None
|
spaces: Optional[List[StrictStr]] = None
|
||||||
is_encrypted: Optional[StrictBool] = None
|
is_encrypted: Optional[StrictBool] = None
|
||||||
|
|
|
@ -22,8 +22,9 @@ from unittest.mock import patch
|
||||||
|
|
||||||
from twisted.test.proto_helpers import MemoryReactor
|
from twisted.test.proto_helpers import MemoryReactor
|
||||||
|
|
||||||
from synapse.api.constants import EventTypes, JoinRules, Membership
|
from synapse.api.constants import AccountDataTypes, EventTypes, JoinRules, Membership
|
||||||
from synapse.api.room_versions import RoomVersions
|
from synapse.api.room_versions import RoomVersions
|
||||||
|
from synapse.handlers.sliding_sync import SlidingSyncConfig
|
||||||
from synapse.rest import admin
|
from synapse.rest import admin
|
||||||
from synapse.rest.client import knock, login, room
|
from synapse.rest.client import knock, login, room
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
@ -1116,3 +1117,130 @@ class GetSyncRoomIdsForUserEventShardTestCase(BaseMultiWorkerStreamTestCase):
|
||||||
room_id3,
|
room_id3,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FilterRoomsTestCase(HomeserverTestCase):
|
||||||
|
"""
|
||||||
|
Tests Sliding Sync handler `filter_rooms()` to make sure it includes/excludes 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 _create_dm_room(
|
||||||
|
self,
|
||||||
|
inviter_user_id: str,
|
||||||
|
inviter_tok: str,
|
||||||
|
invitee_user_id: str,
|
||||||
|
invitee_tok: str,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Helper to create a DM room as the "inviter" and invite the "invitee" user to the room. The
|
||||||
|
"invitee" user also will join the room. The `m.direct` account data will be set
|
||||||
|
for both users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a room and send an invite the other user
|
||||||
|
room_id = self.helper.create_room_as(
|
||||||
|
inviter_user_id,
|
||||||
|
is_public=False,
|
||||||
|
tok=inviter_tok,
|
||||||
|
)
|
||||||
|
self.helper.invite(
|
||||||
|
room_id,
|
||||||
|
src=inviter_user_id,
|
||||||
|
targ=invitee_user_id,
|
||||||
|
tok=inviter_tok,
|
||||||
|
extra_data={"is_direct": True},
|
||||||
|
)
|
||||||
|
# Person that was invited joins the room
|
||||||
|
self.helper.join(room_id, invitee_user_id, tok=invitee_tok)
|
||||||
|
|
||||||
|
# Mimic the client setting the room as a direct message in the global account
|
||||||
|
# data
|
||||||
|
self.get_success(
|
||||||
|
self.store.add_account_data_for_user(
|
||||||
|
invitee_user_id,
|
||||||
|
AccountDataTypes.DIRECT,
|
||||||
|
{inviter_user_id: [room_id]},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.get_success(
|
||||||
|
self.store.add_account_data_for_user(
|
||||||
|
inviter_user_id,
|
||||||
|
AccountDataTypes.DIRECT,
|
||||||
|
{invitee_user_id: [room_id]},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return room_id
|
||||||
|
|
||||||
|
def test_filter_dm_rooms(self) -> None:
|
||||||
|
"""
|
||||||
|
Test `filter.is_dm` for DM rooms
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Create a normal room
|
||||||
|
room_id = self.helper.create_room_as(
|
||||||
|
user1_id,
|
||||||
|
is_public=False,
|
||||||
|
tok=user1_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a DM room
|
||||||
|
dm_room_id = self._create_dm_room(
|
||||||
|
inviter_user_id=user1_id,
|
||||||
|
inviter_tok=user1_tok,
|
||||||
|
invitee_user_id=user2_id,
|
||||||
|
invitee_tok=user2_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
after_rooms_token = self.event_sources.get_current_token()
|
||||||
|
|
||||||
|
# Try with `is_dm=True`
|
||||||
|
truthy_filtered_room_ids = self.get_success(
|
||||||
|
self.sliding_sync_handler.filter_rooms(
|
||||||
|
UserID.from_string(user1_id),
|
||||||
|
{room_id, dm_room_id},
|
||||||
|
SlidingSyncConfig.SlidingSyncList.Filters(
|
||||||
|
is_dm=True,
|
||||||
|
),
|
||||||
|
after_rooms_token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(truthy_filtered_room_ids, {dm_room_id})
|
||||||
|
|
||||||
|
# Try with `is_dm=False`
|
||||||
|
falsy_filtered_room_ids = self.get_success(
|
||||||
|
self.sliding_sync_handler.filter_rooms(
|
||||||
|
UserID.from_string(user1_id),
|
||||||
|
{room_id, dm_room_id},
|
||||||
|
SlidingSyncConfig.SlidingSyncList.Filters(
|
||||||
|
is_dm=False,
|
||||||
|
),
|
||||||
|
after_rooms_token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(falsy_filtered_room_ids, {room_id})
|
||||||
|
|
|
@ -27,6 +27,7 @@ from twisted.test.proto_helpers import MemoryReactor
|
||||||
|
|
||||||
import synapse.rest.admin
|
import synapse.rest.admin
|
||||||
from synapse.api.constants import (
|
from synapse.api.constants import (
|
||||||
|
AccountDataTypes,
|
||||||
EventContentFields,
|
EventContentFields,
|
||||||
EventTypes,
|
EventTypes,
|
||||||
ReceiptTypes,
|
ReceiptTypes,
|
||||||
|
@ -1226,10 +1227,59 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||||
|
self.store = hs.get_datastores().main
|
||||||
self.sync_endpoint = "/_matrix/client/unstable/org.matrix.msc3575/sync"
|
self.sync_endpoint = "/_matrix/client/unstable/org.matrix.msc3575/sync"
|
||||||
self.store = hs.get_datastores().main
|
self.store = hs.get_datastores().main
|
||||||
self.event_sources = hs.get_event_sources()
|
self.event_sources = hs.get_event_sources()
|
||||||
|
|
||||||
|
def _create_dm_room(
|
||||||
|
self,
|
||||||
|
inviter_user_id: str,
|
||||||
|
inviter_tok: str,
|
||||||
|
invitee_user_id: str,
|
||||||
|
invitee_tok: str,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Helper to create a DM room as the "inviter" and invite the "invitee" user to the
|
||||||
|
room. The "invitee" user also will join the room. The `m.direct` account data
|
||||||
|
will be set for both users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a room and send an invite the other user
|
||||||
|
room_id = self.helper.create_room_as(
|
||||||
|
inviter_user_id,
|
||||||
|
is_public=False,
|
||||||
|
tok=inviter_tok,
|
||||||
|
)
|
||||||
|
self.helper.invite(
|
||||||
|
room_id,
|
||||||
|
src=inviter_user_id,
|
||||||
|
targ=invitee_user_id,
|
||||||
|
tok=inviter_tok,
|
||||||
|
extra_data={"is_direct": True},
|
||||||
|
)
|
||||||
|
# Person that was invited joins the room
|
||||||
|
self.helper.join(room_id, invitee_user_id, tok=invitee_tok)
|
||||||
|
|
||||||
|
# Mimic the client setting the room as a direct message in the global account
|
||||||
|
# data
|
||||||
|
self.get_success(
|
||||||
|
self.store.add_account_data_for_user(
|
||||||
|
invitee_user_id,
|
||||||
|
AccountDataTypes.DIRECT,
|
||||||
|
{inviter_user_id: [room_id]},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.get_success(
|
||||||
|
self.store.add_account_data_for_user(
|
||||||
|
inviter_user_id,
|
||||||
|
AccountDataTypes.DIRECT,
|
||||||
|
{invitee_user_id: [room_id]},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return room_id
|
||||||
|
|
||||||
def test_sync_list(self) -> None:
|
def test_sync_list(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test that room IDs show up in the Sliding Sync lists
|
Test that room IDs show up in the Sliding Sync lists
|
||||||
|
@ -1336,3 +1386,80 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
channel.json_body["next_pos"], future_position_token_serialized
|
channel.json_body["next_pos"], future_position_token_serialized
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_filter_list(self) -> None:
|
||||||
|
"""
|
||||||
|
Test that filters apply to lists
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Create a DM room
|
||||||
|
dm_room_id = self._create_dm_room(
|
||||||
|
inviter_user_id=user1_id,
|
||||||
|
inviter_tok=user1_tok,
|
||||||
|
invitee_user_id=user2_id,
|
||||||
|
invitee_tok=user2_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a normal room
|
||||||
|
room_id = self.helper.create_room_as(user1_id, tok=user1_tok, is_public=True)
|
||||||
|
|
||||||
|
# Make the Sliding Sync request
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
self.sync_endpoint,
|
||||||
|
{
|
||||||
|
"lists": {
|
||||||
|
"dms": {
|
||||||
|
"ranges": [[0, 99]],
|
||||||
|
"sort": ["by_recency"],
|
||||||
|
"required_state": [],
|
||||||
|
"timeline_limit": 1,
|
||||||
|
"filters": {"is_dm": True},
|
||||||
|
},
|
||||||
|
"foo-list": {
|
||||||
|
"ranges": [[0, 99]],
|
||||||
|
"sort": ["by_recency"],
|
||||||
|
"required_state": [],
|
||||||
|
"timeline_limit": 1,
|
||||||
|
"filters": {"is_dm": False},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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()),
|
||||||
|
["dms", "foo-list"],
|
||||||
|
channel.json_body["lists"].keys(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the list includes the room we are joined to
|
||||||
|
self.assertListEqual(
|
||||||
|
list(channel.json_body["lists"]["dms"]["ops"]),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "SYNC",
|
||||||
|
"range": [0, 99],
|
||||||
|
"room_ids": [dm_room_id],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
list(channel.json_body["lists"]["dms"]),
|
||||||
|
)
|
||||||
|
self.assertListEqual(
|
||||||
|
list(channel.json_body["lists"]["foo-list"]["ops"]),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "SYNC",
|
||||||
|
"range": [0, 99],
|
||||||
|
"room_ids": [room_id],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
list(channel.json_body["lists"]["foo-list"]),
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue