test_presentable_names.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. # Copyright 2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from typing import Iterable, List, Optional, Tuple, cast
  15. from synapse.api.constants import EventTypes, Membership
  16. from synapse.api.room_versions import RoomVersions
  17. from synapse.events import EventBase, FrozenEvent
  18. from synapse.push.presentable_names import calculate_room_name
  19. from synapse.types import StateKey, StateMap
  20. from tests import unittest
  21. class MockDataStore:
  22. """
  23. A fake data store which stores a mapping of state key to event content.
  24. (I.e. the state key is used as the event ID.)
  25. """
  26. def __init__(self, events: Iterable[Tuple[StateKey, dict]]):
  27. """
  28. Args:
  29. events: A state map to event contents.
  30. """
  31. self._events = {}
  32. for i, (event_id, content) in enumerate(events):
  33. self._events[event_id] = FrozenEvent(
  34. {
  35. "event_id": "$event_id",
  36. "type": event_id[0],
  37. "sender": "@user:test",
  38. "state_key": event_id[1],
  39. "room_id": "#room:test",
  40. "content": content,
  41. "origin_server_ts": i,
  42. },
  43. RoomVersions.V1,
  44. )
  45. async def get_event(
  46. self, event_id: str, allow_none: bool = False
  47. ) -> Optional[FrozenEvent]:
  48. assert allow_none, "Mock not configured for allow_none = False"
  49. # Decode the state key from the event ID.
  50. state_key = cast(Tuple[str, str], tuple(event_id.split("|", 1)))
  51. return self._events.get(state_key)
  52. async def get_events(self, event_ids: Iterable[StateKey]) -> StateMap[EventBase]:
  53. # This is cheating since it just returns all events.
  54. return self._events
  55. class PresentableNamesTestCase(unittest.HomeserverTestCase):
  56. USER_ID = "@test:test"
  57. OTHER_USER_ID = "@user:test"
  58. def _calculate_room_name(
  59. self,
  60. events: Iterable[Tuple[Tuple[str, str], dict]],
  61. user_id: str = "",
  62. fallback_to_members: bool = True,
  63. fallback_to_single_member: bool = True,
  64. ) -> Optional[str]:
  65. # Encode the state key into the event ID.
  66. room_state_ids = {k[0]: "|".join(k[0]) for k in events}
  67. return self.get_success(
  68. calculate_room_name(
  69. MockDataStore(events), # type: ignore[arg-type]
  70. room_state_ids,
  71. user_id or self.USER_ID,
  72. fallback_to_members,
  73. fallback_to_single_member,
  74. )
  75. )
  76. def test_name(self) -> None:
  77. """A room name event should be used."""
  78. events: List[Tuple[Tuple[str, str], dict]] = [
  79. ((EventTypes.Name, ""), {"name": "test-name"}),
  80. ]
  81. self.assertEqual("test-name", self._calculate_room_name(events))
  82. # Check if the event content has garbage.
  83. events = [((EventTypes.Name, ""), {"foo": 1})]
  84. self.assertEqual("Empty Room", self._calculate_room_name(events))
  85. events = [((EventTypes.Name, ""), {"name": 1})]
  86. self.assertEqual(1, self._calculate_room_name(events))
  87. def test_canonical_alias(self) -> None:
  88. """An canonical alias should be used."""
  89. events: List[Tuple[Tuple[str, str], dict]] = [
  90. ((EventTypes.CanonicalAlias, ""), {"alias": "#test-name:test"}),
  91. ]
  92. self.assertEqual("#test-name:test", self._calculate_room_name(events))
  93. # Check if the event content has garbage.
  94. events = [((EventTypes.CanonicalAlias, ""), {"foo": 1})]
  95. self.assertEqual("Empty Room", self._calculate_room_name(events))
  96. events = [((EventTypes.CanonicalAlias, ""), {"alias": "test-name"})]
  97. self.assertEqual("Empty Room", self._calculate_room_name(events))
  98. def test_invite(self) -> None:
  99. """An invite has special behaviour."""
  100. events: List[Tuple[Tuple[str, str], dict]] = [
  101. ((EventTypes.Member, self.USER_ID), {"membership": Membership.INVITE}),
  102. ((EventTypes.Member, self.OTHER_USER_ID), {"displayname": "Other User"}),
  103. ]
  104. self.assertEqual("Invite from Other User", self._calculate_room_name(events))
  105. self.assertIsNone(
  106. self._calculate_room_name(events, fallback_to_single_member=False)
  107. )
  108. # Ensure this logic is skipped if we don't fallback to members.
  109. self.assertIsNone(self._calculate_room_name(events, fallback_to_members=False))
  110. # Check if the event content has garbage.
  111. events = [
  112. ((EventTypes.Member, self.USER_ID), {"membership": Membership.INVITE}),
  113. ((EventTypes.Member, self.OTHER_USER_ID), {"foo": 1}),
  114. ]
  115. self.assertEqual("Invite from @user:test", self._calculate_room_name(events))
  116. # No member event for sender.
  117. events = [
  118. ((EventTypes.Member, self.USER_ID), {"membership": Membership.INVITE}),
  119. ]
  120. self.assertEqual("Room Invite", self._calculate_room_name(events))
  121. def test_no_members(self) -> None:
  122. """Behaviour of an empty room."""
  123. events: List[Tuple[Tuple[str, str], dict]] = []
  124. self.assertEqual("Empty Room", self._calculate_room_name(events))
  125. # Note that events with invalid (or missing) membership are ignored.
  126. events = [
  127. ((EventTypes.Member, self.OTHER_USER_ID), {"foo": 1}),
  128. ((EventTypes.Member, "@foo:test"), {"membership": "foo"}),
  129. ]
  130. self.assertEqual("Empty Room", self._calculate_room_name(events))
  131. def test_no_other_members(self) -> None:
  132. """Behaviour of a room with no other members in it."""
  133. events = [
  134. (
  135. (EventTypes.Member, self.USER_ID),
  136. {"membership": Membership.JOIN, "displayname": "Me"},
  137. ),
  138. ]
  139. self.assertEqual("Me", self._calculate_room_name(events))
  140. # Check if the event content has no displayname.
  141. events = [
  142. ((EventTypes.Member, self.USER_ID), {"membership": Membership.JOIN}),
  143. ]
  144. self.assertEqual("@test:test", self._calculate_room_name(events))
  145. # 3pid invite, use the other user (who is set as the sender).
  146. events = [
  147. ((EventTypes.Member, self.OTHER_USER_ID), {"membership": Membership.JOIN}),
  148. ]
  149. self.assertEqual(
  150. "nobody", self._calculate_room_name(events, user_id=self.OTHER_USER_ID)
  151. )
  152. events = [
  153. ((EventTypes.Member, self.OTHER_USER_ID), {"membership": Membership.JOIN}),
  154. ((EventTypes.ThirdPartyInvite, self.OTHER_USER_ID), {}),
  155. ]
  156. self.assertEqual(
  157. "Inviting email address",
  158. self._calculate_room_name(events, user_id=self.OTHER_USER_ID),
  159. )
  160. def test_one_other_member(self) -> None:
  161. """Behaviour of a room with a single other member."""
  162. events = [
  163. ((EventTypes.Member, self.USER_ID), {"membership": Membership.JOIN}),
  164. (
  165. (EventTypes.Member, self.OTHER_USER_ID),
  166. {"membership": Membership.JOIN, "displayname": "Other User"},
  167. ),
  168. ]
  169. self.assertEqual("Other User", self._calculate_room_name(events))
  170. self.assertIsNone(
  171. self._calculate_room_name(events, fallback_to_single_member=False)
  172. )
  173. # Check if the event content has no displayname and is an invite.
  174. events = [
  175. ((EventTypes.Member, self.USER_ID), {"membership": Membership.JOIN}),
  176. (
  177. (EventTypes.Member, self.OTHER_USER_ID),
  178. {"membership": Membership.INVITE},
  179. ),
  180. ]
  181. self.assertEqual("@user:test", self._calculate_room_name(events))
  182. def test_other_members(self) -> None:
  183. """Behaviour of a room with multiple other members."""
  184. # Two other members.
  185. events = [
  186. ((EventTypes.Member, self.USER_ID), {"membership": Membership.JOIN}),
  187. (
  188. (EventTypes.Member, self.OTHER_USER_ID),
  189. {"membership": Membership.JOIN, "displayname": "Other User"},
  190. ),
  191. ((EventTypes.Member, "@foo:test"), {"membership": Membership.JOIN}),
  192. ]
  193. self.assertEqual("Other User and @foo:test", self._calculate_room_name(events))
  194. # Three or more other members.
  195. events.append(
  196. ((EventTypes.Member, "@fourth:test"), {"membership": Membership.INVITE})
  197. )
  198. self.assertEqual("Other User and 2 others", self._calculate_room_name(events))