mirror of
https://github.com/element-hq/synapse.git
synced 2026-08-28 05:04:38 +00:00
Adjust MSC4429 profile updates over legacy removed fields in response
Due to MSC4429 being in flux and clients having implemented field removals early, the previously merged implementation pushed field removals by setting the value to `null`. The MSC will require delivering the fields in a separate `removed_profile_fields` key as `None` is a valid field value, see https://github.com/matrix-org/matrix-spec-proposals/pull/4429#discussion_r3512513534 Thus, as we moved storing field deletes to `ProfileUpdateAction.DELETE`, we need to adjust the legacy sync to properly identify deletes. At the same time have implemented the new `removed_profile_fields` key, so we have it ready for simplifying the legacy sync response in the future.
This commit is contained in:
@@ -1,2 +1,6 @@
|
||||
Add optional support for [MSC4262: Profile Updates for Sliding Sync](https://github.com/matrix-org/matrix-spec-proposals/pull/4262).
|
||||
Currently defaults to not enabled, and is limited to local users only for the sync results.
|
||||
Currently defaults to not enabled, and is limited to local users only for the sync results.
|
||||
|
||||
Additionally, optional support for [MSC4429: Profile Updates for Legacy Sync](https://github.com/matrix-org/matrix-spec-proposals/pull/4262)
|
||||
now includes removed fields in the `removed_profile_fields` response key, in addition to setting the field to a `null`
|
||||
value. The latter behaviour will be removed in a future Synapse release.
|
||||
+110
-25
@@ -29,6 +29,7 @@ from typing import (
|
||||
Any,
|
||||
Mapping,
|
||||
Sequence,
|
||||
cast,
|
||||
)
|
||||
|
||||
import attr
|
||||
@@ -40,6 +41,7 @@ from synapse.api.constants import (
|
||||
EventContentFields,
|
||||
EventTypes,
|
||||
Membership,
|
||||
ProfileFields,
|
||||
ProfileUpdateAction,
|
||||
StickyEvent,
|
||||
)
|
||||
@@ -237,6 +239,37 @@ class _RoomChanges:
|
||||
newly_left_rooms: list[str]
|
||||
|
||||
|
||||
@attr.s(slots=True, auto_attribs=True)
|
||||
class ProfilesResult:
|
||||
"""
|
||||
Updates to profiles to add to the sync response. Includes per user updated or
|
||||
existing fields in `profile_updates`, and field removals in
|
||||
`removed_profile_fields`.
|
||||
"""
|
||||
|
||||
# user ID -> {profile field -> value | null if unset }
|
||||
profile_updates: dict[str, dict[str, JsonValue | dict[str, JsonValue]] | None] = (
|
||||
attr.ib(factory=dict)
|
||||
)
|
||||
# user ID -> [field name]
|
||||
removed_profile_fields: dict[str, list[str]] = attr.ib(factory=dict)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.profile_updates.keys()) or bool(
|
||||
self.removed_profile_fields.keys()
|
||||
)
|
||||
|
||||
def to_response(self) -> dict[str, dict[str, JsonValue | dict[str, JsonValue]]]:
|
||||
response: dict[str, JsonDict] = {}
|
||||
for user_id, updates in self.profile_updates.items():
|
||||
response[user_id] = {"profile_updates": updates}
|
||||
for user_id, removals in self.removed_profile_fields.items():
|
||||
if response.get(user_id) is None:
|
||||
response[user_id] = {}
|
||||
response[user_id]["removed_profile_fields"] = removals
|
||||
return response
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
||||
class SyncResult:
|
||||
"""
|
||||
@@ -244,7 +277,7 @@ class SyncResult:
|
||||
next_batch: Token for the next sync
|
||||
presence: List of presence events for the user.
|
||||
account_data: List of account_data events for the user.
|
||||
profile_updates: Map of user_id to profile field updates for that user.
|
||||
profiles: The ProfilesResult object containing updates and removals.
|
||||
joined: JoinedSyncResult for each joined room.
|
||||
invited: InvitedSyncResult for each invited room.
|
||||
knocked: KnockedSyncResult for each knocked on room.
|
||||
@@ -260,8 +293,7 @@ class SyncResult:
|
||||
next_batch: StreamToken
|
||||
presence: list[UserPresenceState]
|
||||
account_data: list[JsonDict]
|
||||
# user ID -> {profile field -> value | null if unset }
|
||||
profile_updates: dict[str, dict[str, JsonValue | dict[str, JsonValue]] | None]
|
||||
profiles: ProfilesResult
|
||||
joined: list[JoinedSyncResult]
|
||||
invited: list[InvitedSyncResult]
|
||||
knocked: list[KnockedSyncResult]
|
||||
@@ -283,7 +315,7 @@ class SyncResult:
|
||||
or self.knocked
|
||||
or self.archived
|
||||
or self.account_data
|
||||
or self.profile_updates
|
||||
or self.profiles
|
||||
or self.to_device
|
||||
or self.device_lists
|
||||
)
|
||||
@@ -299,7 +331,7 @@ class SyncResult:
|
||||
next_batch=next_batch,
|
||||
presence=[],
|
||||
account_data=[],
|
||||
profile_updates={},
|
||||
profiles=ProfilesResult(),
|
||||
joined=[],
|
||||
invited=[],
|
||||
knocked=[],
|
||||
@@ -1954,7 +1986,7 @@ class SyncHandler:
|
||||
return SyncResult(
|
||||
presence=sync_result_builder.presence,
|
||||
account_data=sync_result_builder.account_data,
|
||||
profile_updates=sync_result_builder.profile_updates,
|
||||
profiles=sync_result_builder.profiles,
|
||||
joined=sync_result_builder.joined,
|
||||
invited=sync_result_builder.invited,
|
||||
knocked=sync_result_builder.knocked,
|
||||
@@ -2274,7 +2306,9 @@ class SyncHandler:
|
||||
profile_updates[other_user_id] = per_user_updates
|
||||
|
||||
if profile_updates:
|
||||
sync_result_builder.profile_updates = profile_updates
|
||||
sync_result_builder.profiles = ProfilesResult(
|
||||
profile_updates=profile_updates,
|
||||
)
|
||||
|
||||
async def _generate_sync_entry_for_profile_updates(
|
||||
self, sync_result_builder: "SyncResultBuilder"
|
||||
@@ -2350,6 +2384,11 @@ class SyncHandler:
|
||||
for update in updates
|
||||
if update.action == ProfileUpdateAction.JOINED_ROOM.value
|
||||
}
|
||||
delete_field_user_ids = {
|
||||
update.user_id
|
||||
for update in updates
|
||||
if update.action == ProfileUpdateAction.DELETE.value
|
||||
}
|
||||
users = set()
|
||||
updated_users = {
|
||||
update.user_id
|
||||
@@ -2364,7 +2403,7 @@ class SyncHandler:
|
||||
# Add any newly joined users
|
||||
users.update(joined_room_user_ids)
|
||||
|
||||
if not users and not left_room_user_ids:
|
||||
if not users and not left_room_user_ids and not delete_field_user_ids:
|
||||
return
|
||||
|
||||
# Serialise the profile updates into the sync response format.
|
||||
@@ -2373,6 +2412,19 @@ class SyncHandler:
|
||||
str, dict[str, JsonValue | dict[str, JsonValue]] | None
|
||||
] = {}
|
||||
|
||||
# Note: there's a small race condition here where a profile update may
|
||||
# occur between fetching `now_token` above and reaching this step. In
|
||||
# that case, the profile information will be newer than `now_token`.
|
||||
# This is fine, as users will generally always want the latest profile
|
||||
# information. However, it does mean that on the next sync, the same
|
||||
# profile update will come down a second time.
|
||||
#
|
||||
# Hopefully clients can just filter these out.
|
||||
profile_data_by_user = await self.store.get_profile_data_for_users(
|
||||
# Get profiles for both updates and deletes in one go
|
||||
users.union(delete_field_user_ids)
|
||||
)
|
||||
|
||||
# Process field updates and users who have events in the sync response
|
||||
if users:
|
||||
updated_user_fields: dict[str, set[str]] = {}
|
||||
@@ -2393,16 +2445,6 @@ class SyncHandler:
|
||||
update.affected_fields & profile_fields
|
||||
)
|
||||
|
||||
# Note: there's a small race condition here where a profile update may
|
||||
# occur between fetching `now_token` above and reaching this step. In
|
||||
# that case, the profile information will be newer than `now_token`.
|
||||
# This is fine, as users will generally always want the latest profile
|
||||
# information. However, it does mean that on the next sync, the same
|
||||
# profile update will come down a second time.
|
||||
#
|
||||
# Hopefully clients can just filter these out.
|
||||
profile_data_by_user = await self.store.get_profile_data_for_users(users)
|
||||
|
||||
# Note, we've already collected field updates above via `updates`,
|
||||
# outside of events in the timeline when lazy loading. When lazy loading,
|
||||
# we're already always sending the fields that have changed, regardless
|
||||
@@ -2476,16 +2518,61 @@ class SyncHandler:
|
||||
if per_user_updates:
|
||||
profile_updates[other_user_id] = per_user_updates
|
||||
|
||||
# Process deleted fields
|
||||
removed_profile_fields: dict[str, list[str]] = {}
|
||||
for update in updates:
|
||||
if (
|
||||
update.action != ProfileUpdateAction.DELETE.value
|
||||
or not update.affected_fields
|
||||
or update.user_id in left_room_user_ids
|
||||
):
|
||||
continue
|
||||
profile_data = profile_data_by_user.get(update.user_id)
|
||||
for field_name in update.affected_fields:
|
||||
if field_name not in profile_fields:
|
||||
# Only include fields the client is interested in
|
||||
continue
|
||||
if profile_data is not None:
|
||||
if field_name in (
|
||||
ProfileFields.AVATAR_URL,
|
||||
ProfileFields.DISPLAYNAME,
|
||||
):
|
||||
if profile_data.get(field_name) is not None:
|
||||
# Displayname/avatar_url are not unset anymore
|
||||
continue
|
||||
elif field_name in profile_data.keys():
|
||||
# This field has re-appeared to the profile, skip
|
||||
continue
|
||||
if removed_profile_fields.get(update.user_id) is None:
|
||||
removed_profile_fields[update.user_id] = []
|
||||
|
||||
# Ensure we only add the field once
|
||||
if field_name in removed_profile_fields[update.user_id]:
|
||||
continue
|
||||
removed_profile_fields[update.user_id].append(field_name)
|
||||
|
||||
# FIXME: Initial MSC4429 behaviour of including removed profile fields
|
||||
# in the main `profile_updates` key. We need to keep this around for
|
||||
# a few releases so that clients can adapt.
|
||||
# See https://github.com/matrix-org/matrix-spec-proposals/pull/4429#discussion_r3512513534
|
||||
# Removing will be tracked via https://github.com/element-hq/synapse/issues/19981
|
||||
if profile_updates.get(update.user_id) is None:
|
||||
profile_updates[update.user_id] = {}
|
||||
# Typing fix as mypy thinks this may be None
|
||||
entry = cast(JsonDict, profile_updates[update.user_id])
|
||||
entry[field_name] = None
|
||||
|
||||
# Process left rooms
|
||||
if left_room_user_ids:
|
||||
for other_user_id in left_room_user_ids:
|
||||
# Return an empty dictionary to the client
|
||||
profile_updates[other_user_id] = None
|
||||
|
||||
# FIXME: handle profile field deletions like we do for sliding sync
|
||||
|
||||
if profile_updates:
|
||||
sync_result_builder.profile_updates = profile_updates
|
||||
sync_result_builder.profiles = ProfilesResult(
|
||||
profile_updates=profile_updates,
|
||||
removed_profile_fields=removed_profile_fields,
|
||||
)
|
||||
|
||||
async def _generate_sync_entry_for_presence(
|
||||
self,
|
||||
@@ -3503,7 +3590,7 @@ class SyncResultBuilder:
|
||||
# The following mirror the fields in a sync response
|
||||
presence
|
||||
account_data
|
||||
profile_updates
|
||||
profiles
|
||||
joined
|
||||
invited
|
||||
knocked
|
||||
@@ -3522,9 +3609,7 @@ class SyncResultBuilder:
|
||||
|
||||
presence: list[UserPresenceState] = attr.Factory(list)
|
||||
account_data: list[JsonDict] = attr.Factory(list)
|
||||
profile_updates: dict[str, dict[str, JsonValue | dict[str, JsonValue]] | None] = (
|
||||
attr.Factory(dict)
|
||||
)
|
||||
profiles: ProfilesResult = attr.Factory(ProfilesResult)
|
||||
joined: list[JoinedSyncResult] = attr.Factory(list)
|
||||
invited: list[InvitedSyncResult] = attr.Factory(list)
|
||||
knocked: list[KnockedSyncResult] = attr.Factory(list)
|
||||
|
||||
@@ -358,14 +358,11 @@ class SyncRestServlet(RestServlet):
|
||||
if sync_result.to_device:
|
||||
response["to_device"] = {"events": sync_result.to_device}
|
||||
|
||||
if self._msc4429_enabled and sync_result.profile_updates:
|
||||
if self._msc4429_enabled and sync_result.profiles:
|
||||
# FIXME: See issue https://github.com/element-hq/synapse/issues/19981
|
||||
# for concerns around the current implementation of the profile
|
||||
# updates stream.
|
||||
response["org.matrix.msc4429.users"] = {
|
||||
user_id: {"profile_updates": updates}
|
||||
for user_id, updates in sync_result.profile_updates.items()
|
||||
}
|
||||
response["org.matrix.msc4429.users"] = sync_result.profiles.to_response()
|
||||
|
||||
if sync_result.device_lists.changed:
|
||||
response["device_lists"]["changed"] = list(sync_result.device_lists.changed)
|
||||
|
||||
+172
-76
@@ -1208,7 +1208,8 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
self.assertEqual(initial_result.profile_updates, {})
|
||||
self.assertEqual(initial_result.profiles.profile_updates, {})
|
||||
self.assertEqual(initial_result.profiles.removed_profile_fields, {})
|
||||
|
||||
@override_config({"include_profile_updates_in_sync": True})
|
||||
def test_initial_sync_no_profile_updates_if_not_filtered_for(self) -> None:
|
||||
@@ -1234,7 +1235,11 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
initial_result.profile_updates,
|
||||
initial_result.profiles.profile_updates,
|
||||
{},
|
||||
)
|
||||
self.assertEqual(
|
||||
initial_result.profiles.removed_profile_fields,
|
||||
{},
|
||||
)
|
||||
|
||||
@@ -1277,17 +1282,18 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
assert initial_result.profile_updates[self.user] is not None
|
||||
assert initial_result.profile_updates["@other_user:test"] is not None
|
||||
profile_updates = initial_result.profiles.profile_updates
|
||||
assert profile_updates[self.user] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
initial_result.profile_updates["@other_user:test"]["m.status"],
|
||||
profile_updates["@other_user:test"]["m.status"],
|
||||
{"text": "On holiday", "emoji": "🏖"},
|
||||
)
|
||||
self.assertFalse(
|
||||
"displayname" in initial_result.profile_updates["@other_user:test"].keys(),
|
||||
"displayname" in profile_updates["@other_user:test"].keys(),
|
||||
)
|
||||
self.assertCountEqual(
|
||||
initial_result.profile_updates.keys(),
|
||||
profile_updates.keys(),
|
||||
[
|
||||
self.user,
|
||||
"@other_user:test",
|
||||
@@ -1341,7 +1347,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
self.assertIsNone(initial_result.profile_updates.get(third_user))
|
||||
self.assertIsNone(initial_result.profiles.profile_updates.get(third_user))
|
||||
|
||||
@override_config({"include_profile_updates_in_sync": True})
|
||||
def test_initial_sync_lazy_loading_responds_with_only_profiles_with_events(
|
||||
@@ -1414,7 +1420,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
# Only third_user is returned, as lazy loading filters out the events from
|
||||
# the other users
|
||||
self.assertCountEqual(
|
||||
initial_result.profile_updates.keys(),
|
||||
initial_result.profiles.profile_updates.keys(),
|
||||
[
|
||||
"@third_user:test",
|
||||
],
|
||||
@@ -1479,22 +1485,106 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@other_user:test"]["m.status"],
|
||||
profile_updates["@other_user:test"]["m.status"],
|
||||
{"text": "On holiday", "emoji": "🏖"},
|
||||
)
|
||||
# We only send diffs in incremental sync for profile field updates
|
||||
self.assertFalse(
|
||||
"displayname"
|
||||
in incremental_result.profile_updates["@other_user:test"].keys(),
|
||||
"displayname" in profile_updates["@other_user:test"].keys(),
|
||||
)
|
||||
# The client didn't ask for this field
|
||||
self.assertFalse(
|
||||
"uninterestingfield"
|
||||
in incremental_result.profile_updates["@other_user:test"].keys(),
|
||||
"uninterestingfield" in profile_updates["@other_user:test"].keys(),
|
||||
)
|
||||
|
||||
@override_config({"include_profile_updates_in_sync": True})
|
||||
def test_incremental_sync_sends_down_profile_field_removals(
|
||||
self,
|
||||
) -> None:
|
||||
"""Test that with MSC4429 enabled the incremental sync response correctly
|
||||
responds with field removals.
|
||||
|
||||
Note that initially MSC4429 defined field removals being in the
|
||||
`profile_updates` key, with the field having a null value if it was removed.
|
||||
This got implemented and released in Element Web, thus we kept the behaviour
|
||||
in the pr adding profile updates over legacy sync
|
||||
(https://github.com/element-hq/synapse/pull/19556).
|
||||
|
||||
The latest suggestion in MSC4429 is to do something similar that sliding sync
|
||||
MSC4262 has, ie have a dedicated key for field removals, see:
|
||||
https://github.com/matrix-org/matrix-spec-proposals/pull/4429#pullrequestreview-4616853235
|
||||
|
||||
To ensure clients have time to adapt, we should include field removals in both
|
||||
keys for a few Synapse releases.
|
||||
"""
|
||||
requester = create_requester(self.user)
|
||||
initial_result = self.get_success(
|
||||
self.sync_handler.wait_for_sync_for_user(
|
||||
requester,
|
||||
sync_config=generate_sync_config(
|
||||
user_id=self.user,
|
||||
filter_collection=FilterCollection(
|
||||
hs=self.hs,
|
||||
filter_json={
|
||||
"org.matrix.msc4429.profile_fields": {
|
||||
"ids": ["m.status", "displayname", "avatar_url"]
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
self.get_success(
|
||||
self.profile_handler.set_field(
|
||||
target_user=UserID.from_string(self.other_user),
|
||||
requester=create_requester(self.other_user),
|
||||
field_name="otherfield",
|
||||
new_value="value",
|
||||
)
|
||||
)
|
||||
self.get_success(
|
||||
self.profile_handler.delete_profile_field(
|
||||
target_user=UserID.from_string(self.other_user),
|
||||
requester=create_requester(self.other_user),
|
||||
field_name="m.status",
|
||||
)
|
||||
)
|
||||
incremental_result = self.get_success(
|
||||
self.sync_handler.wait_for_sync_for_user(
|
||||
requester,
|
||||
since_token=initial_result.next_batch,
|
||||
sync_config=generate_sync_config(
|
||||
user_id=self.user,
|
||||
filter_collection=FilterCollection(
|
||||
hs=self.hs,
|
||||
filter_json={
|
||||
"org.matrix.msc4429.profile_fields": {
|
||||
"ids": ["m.status", "displayname", "avatar_url"]
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
# Legacy behaviour - field is null
|
||||
self.assertIsNone(profile_updates["@other_user:test"]["m.status"])
|
||||
# Latest behaviour, in `removed_fields`
|
||||
field_removals = incremental_result.profiles.removed_profile_fields
|
||||
self.assertEqual(
|
||||
field_removals["@other_user:test"],
|
||||
["m.status"],
|
||||
)
|
||||
# Client didn't ask for this field
|
||||
self.assertFalse("otherfield" in field_removals["@other_user:test"])
|
||||
self.assertFalse("otherfield" in profile_updates["@other_user:test"].keys())
|
||||
|
||||
@override_config({"include_profile_updates_in_sync": True})
|
||||
def test_incremental_sync_does_not_filter_profile_updates_when_lazy_loading(
|
||||
self,
|
||||
@@ -1592,49 +1682,46 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
# Ensure our federated user is filtered out, even though they have an
|
||||
# event in the joined room timeline
|
||||
self.assertFalse(
|
||||
"@federateduser:federatedhs" in incremental_result.profile_updates.keys()
|
||||
)
|
||||
self.assertFalse("@federateduser:federatedhs" in profile_updates.keys())
|
||||
|
||||
# Lazy loading only filters initial sync profile updates. Incremental syncs
|
||||
# should include all tracked profile updates for the syncing user.
|
||||
self.assertCountEqual(
|
||||
incremental_result.profile_updates.keys(),
|
||||
profile_updates.keys(),
|
||||
[
|
||||
"@other_user:test",
|
||||
"@third_user:test",
|
||||
],
|
||||
)
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
|
||||
# This is a field update, so should be here
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@other_user:test"]["m.status"],
|
||||
profile_updates["@other_user:test"]["m.status"],
|
||||
{"text": "On holiday", "emoji": "🏖"},
|
||||
)
|
||||
|
||||
# We don't have events for this user in this response, so their full profile
|
||||
# is not included
|
||||
self.assertFalse(
|
||||
"displayname"
|
||||
in incremental_result.profile_updates["@other_user:test"].keys(),
|
||||
"displayname" in profile_updates["@other_user:test"].keys(),
|
||||
)
|
||||
assert incremental_result.profile_updates["@third_user:test"] is not None
|
||||
assert profile_updates["@third_user:test"] is not None
|
||||
|
||||
# This user has events in the timeline, thus the fields the client asked for
|
||||
# are included
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@third_user:test"]["m.status"],
|
||||
profile_updates["@third_user:test"]["m.status"],
|
||||
{"text": "On fire", "emoji": "🔥"},
|
||||
)
|
||||
self.assertFalse(
|
||||
"uninterestingfield"
|
||||
in incremental_result.profile_updates["@third_user:test"].keys(),
|
||||
"uninterestingfield" in profile_updates["@third_user:test"].keys(),
|
||||
)
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@third_user:test"]["displayname"],
|
||||
profile_updates["@third_user:test"]["displayname"],
|
||||
"third_user",
|
||||
)
|
||||
|
||||
@@ -1693,7 +1780,8 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
# Ensure our federated user is filtered out, even though they have an
|
||||
# event in the joined room timeline
|
||||
self.assertFalse(
|
||||
"@federateduser1:federatedhs" in initial_result.profile_updates.keys()
|
||||
"@federateduser1:federatedhs"
|
||||
in initial_result.profiles.profile_updates.keys()
|
||||
)
|
||||
if not is_initial:
|
||||
# Join another federated user to the room, causing a membership event into
|
||||
@@ -1725,7 +1813,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
# event in the joined room timeline
|
||||
self.assertFalse(
|
||||
"@federateduser2:federatedhs"
|
||||
in incremental_result.profile_updates.keys()
|
||||
in incremental_result.profiles.profile_updates.keys()
|
||||
)
|
||||
|
||||
@parameterized.expand(
|
||||
@@ -1782,9 +1870,10 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
)
|
||||
if is_initial:
|
||||
assert initial_result.profile_updates["@third_user:test"] is not None
|
||||
profile_updates = initial_result.profiles.profile_updates
|
||||
assert profile_updates["@third_user:test"] is not None
|
||||
self.assertEqual(
|
||||
initial_result.profile_updates["@third_user:test"]["field"],
|
||||
profile_updates["@third_user:test"]["field"],
|
||||
"Content",
|
||||
)
|
||||
else:
|
||||
@@ -1810,9 +1899,10 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
assert incremental_result.profile_updates["@third_user:test"] is not None
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
assert profile_updates["@third_user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@third_user:test"]["field"],
|
||||
profile_updates["@third_user:test"]["field"],
|
||||
"Content",
|
||||
)
|
||||
|
||||
@@ -1867,11 +1957,10 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
)
|
||||
if is_initial:
|
||||
assert initial_result.profile_updates["@other_user:test"] is not None
|
||||
profile_updates = initial_result.profiles.profile_updates
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
initial_result.profile_updates["@other_user:test"][
|
||||
"falseyvaluefield"
|
||||
],
|
||||
profile_updates["@other_user:test"]["falseyvaluefield"],
|
||||
value,
|
||||
)
|
||||
else:
|
||||
@@ -1897,13 +1986,10 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
assert (
|
||||
incremental_result.profile_updates["@other_user:test"] is not None
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@other_user:test"][
|
||||
"falseyvaluefield"
|
||||
],
|
||||
profile_updates["@other_user:test"]["falseyvaluefield"],
|
||||
value,
|
||||
)
|
||||
|
||||
@@ -1968,16 +2054,17 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
# Lazy loading incremental sync should include profiles from events
|
||||
self.assertCountEqual(
|
||||
incremental_result.profile_updates.keys(),
|
||||
profile_updates.keys(),
|
||||
[
|
||||
"@other_user:test",
|
||||
],
|
||||
)
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
set(incremental_result.profile_updates["@other_user:test"].keys()),
|
||||
set(profile_updates["@other_user:test"].keys()),
|
||||
{"avatar_url", "displayname"},
|
||||
)
|
||||
|
||||
@@ -2009,8 +2096,9 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
self.assertCountEqual(
|
||||
incremental_result.profile_updates.keys(),
|
||||
profile_updates.keys(),
|
||||
[],
|
||||
)
|
||||
# However, if we again add an event, we do expect any fields the client didn't
|
||||
@@ -2046,15 +2134,16 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
self.assertCountEqual(
|
||||
incremental_result.profile_updates.keys(),
|
||||
profile_updates.keys(),
|
||||
[
|
||||
"@other_user:test",
|
||||
],
|
||||
)
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
set(incremental_result.profile_updates["@other_user:test"].keys()),
|
||||
set(profile_updates["@other_user:test"].keys()),
|
||||
{"sooninterestingfield"},
|
||||
)
|
||||
|
||||
@@ -2105,7 +2194,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
)
|
||||
self.assertIsNone(
|
||||
incremental_result.profile_updates["@other_user:test"],
|
||||
incremental_result.profiles.profile_updates["@other_user:test"],
|
||||
)
|
||||
|
||||
@override_config({"include_profile_updates_in_sync": True})
|
||||
@@ -2187,20 +2276,21 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
assert incremental_result.profile_updates["@third_user:test"] is not None
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
assert profile_updates["@third_user:test"] is not None
|
||||
self.assertCountEqual(
|
||||
incremental_result.profile_updates.keys(),
|
||||
profile_updates.keys(),
|
||||
[third_user],
|
||||
)
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@third_user:test"]["displayname"],
|
||||
profile_updates["@third_user:test"]["displayname"],
|
||||
"third_user",
|
||||
)
|
||||
self.assertIsNone(
|
||||
incremental_result.profile_updates["@third_user:test"]["avatar_url"],
|
||||
profile_updates["@third_user:test"]["avatar_url"],
|
||||
)
|
||||
self.assertFalse(
|
||||
"m.status" in incremental_result.profile_updates["@third_user:test"].keys(),
|
||||
"m.status" in profile_updates["@third_user:test"].keys(),
|
||||
)
|
||||
|
||||
@parameterized.expand(
|
||||
@@ -2258,14 +2348,15 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
assert incremental_result.profile_updates["@user:test"] is not None
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
assert profile_updates["@user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@user:test"]["m.status"],
|
||||
profile_updates["@user:test"]["m.status"],
|
||||
{"text": "On holiday", "emoji": "🏖"},
|
||||
)
|
||||
# We didn't ask for displayname
|
||||
self.assertFalse(
|
||||
"displayname" in incremental_result.profile_updates["@user:test"].keys(),
|
||||
"displayname" in profile_updates["@user:test"].keys(),
|
||||
)
|
||||
|
||||
@parameterized.expand([[True, False], [True, True], [False, False], [False, True]])
|
||||
@@ -2337,7 +2428,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
# We expect there to be the users profile
|
||||
self.assertIsNotNone(
|
||||
incremental_result.profile_updates["@third_user:test"],
|
||||
incremental_result.profiles.profile_updates["@third_user:test"],
|
||||
)
|
||||
next_token = incremental_result.next_batch
|
||||
# Leave the room
|
||||
@@ -2365,7 +2456,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
# We expect there to be a null profile
|
||||
self.assertIsNone(
|
||||
incremental_result.profile_updates["@third_user:test"],
|
||||
incremental_result.profiles.profile_updates["@third_user:test"],
|
||||
)
|
||||
next_token = incremental_result.next_batch
|
||||
# Join the room
|
||||
@@ -2393,7 +2484,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
# We expect there to be the users profile
|
||||
self.assertIsNotNone(
|
||||
incremental_result.profile_updates["@third_user:test"],
|
||||
incremental_result.profiles.profile_updates["@third_user:test"],
|
||||
)
|
||||
next_token = incremental_result.next_batch
|
||||
# Leave the room
|
||||
@@ -2420,7 +2511,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
# The end result should be null profile
|
||||
self.assertIsNone(
|
||||
incremental_result.profile_updates["@third_user:test"],
|
||||
incremental_result.profiles.profile_updates["@third_user:test"],
|
||||
)
|
||||
|
||||
@parameterized.expand(
|
||||
@@ -2469,7 +2560,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
"@other_user:test" in initial_result.profile_updates,
|
||||
"@other_user:test" in initial_result.profiles.profile_updates,
|
||||
)
|
||||
|
||||
# Update the field
|
||||
@@ -2505,12 +2596,13 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
# We should have the field change in our sync response.
|
||||
# It will also be added to the lazy loading cache, so the same field value
|
||||
# isn't sent again immediately.
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@other_user:test"]["field"],
|
||||
profile_updates["@other_user:test"]["field"],
|
||||
value,
|
||||
)
|
||||
|
||||
@@ -2547,11 +2639,12 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
# Even though the field was added to the lazy loading members cache,
|
||||
# it should come through as an update, as the field value changed.
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@other_user:test"]["field"],
|
||||
profile_updates["@other_user:test"]["field"],
|
||||
new_value,
|
||||
)
|
||||
|
||||
@@ -2586,7 +2679,7 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
"@other_user:test" in initial_result.profile_updates,
|
||||
"@other_user:test" in initial_result.profiles.profile_updates,
|
||||
)
|
||||
|
||||
# Update the field
|
||||
@@ -2615,12 +2708,13 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
# We should have the field change in our sync response.
|
||||
# It will also be added to the lazy loading cache, so the same field value
|
||||
# isn't sent again immediately.
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@other_user:test"]["field"],
|
||||
profile_updates["@other_user:test"]["field"],
|
||||
"value",
|
||||
)
|
||||
|
||||
@@ -2650,11 +2744,12 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
# Even though the field was added to the lazy loading members cache,
|
||||
# it should come through as an update, as the field value changed.
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@other_user:test"]["field"],
|
||||
profile_updates["@other_user:test"]["field"],
|
||||
"new value",
|
||||
)
|
||||
|
||||
@@ -2684,11 +2779,12 @@ class SyncProfileUpdatesTestCase(tests.unittest.HomeserverTestCase):
|
||||
request_key=generate_request_key(),
|
||||
)
|
||||
)
|
||||
profile_updates = incremental_result.profiles.profile_updates
|
||||
# Even though we've quite recently sent down this value, we should still
|
||||
# see it again as it is a change
|
||||
assert incremental_result.profile_updates["@other_user:test"] is not None
|
||||
assert profile_updates["@other_user:test"] is not None
|
||||
self.assertEqual(
|
||||
incremental_result.profile_updates["@other_user:test"]["field"],
|
||||
profile_updates["@other_user:test"]["field"],
|
||||
"value",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user