test_typing.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from tests import unittest
  16. from twisted.internet import defer
  17. from mock import Mock, call, ANY
  18. import json
  19. from ..utils import (
  20. MockHttpResource, MockClock, DeferredMockCallable, setup_test_homeserver
  21. )
  22. from synapse.api.errors import AuthError
  23. from synapse.handlers.typing import TypingNotificationHandler
  24. from synapse.types import UserID
  25. def _expect_edu(destination, edu_type, content, origin="test"):
  26. return {
  27. "origin": origin,
  28. "origin_server_ts": 1000000,
  29. "pdus": [],
  30. "edus": [
  31. {
  32. "edu_type": edu_type,
  33. "content": content,
  34. }
  35. ],
  36. "pdu_failures": [],
  37. }
  38. def _make_edu_json(origin, edu_type, content):
  39. return json.dumps(_expect_edu("test", edu_type, content, origin=origin))
  40. class JustTypingNotificationHandlers(object):
  41. def __init__(self, hs):
  42. self.typing_notification_handler = TypingNotificationHandler(hs)
  43. class TypingNotificationsTestCase(unittest.TestCase):
  44. """Tests typing notifications to rooms."""
  45. @defer.inlineCallbacks
  46. def setUp(self):
  47. self.clock = MockClock()
  48. self.mock_http_client = Mock(spec=[])
  49. self.mock_http_client.put_json = DeferredMockCallable()
  50. self.mock_federation_resource = MockHttpResource()
  51. mock_notifier = Mock(spec=["on_new_event"])
  52. self.on_new_event = mock_notifier.on_new_event
  53. self.auth = Mock(spec=[])
  54. hs = yield setup_test_homeserver(
  55. auth=self.auth,
  56. clock=self.clock,
  57. datastore=Mock(spec=[
  58. # Bits that Federation needs
  59. "prep_send_transaction",
  60. "delivered_txn",
  61. "get_received_txn_response",
  62. "set_received_txn_response",
  63. "get_destination_retry_timings",
  64. ]),
  65. handlers=None,
  66. notifier=mock_notifier,
  67. resource_for_client=Mock(),
  68. resource_for_federation=self.mock_federation_resource,
  69. http_client=self.mock_http_client,
  70. keyring=Mock(),
  71. )
  72. hs.handlers = JustTypingNotificationHandlers(hs)
  73. self.handler = hs.get_handlers().typing_notification_handler
  74. self.event_source = hs.get_event_sources().sources["typing"]
  75. self.datastore = hs.get_datastore()
  76. retry_timings_res = {
  77. "destination": "",
  78. "retry_last_ts": 0,
  79. "retry_interval": 0,
  80. }
  81. self.datastore.get_destination_retry_timings.return_value = (
  82. defer.succeed(retry_timings_res)
  83. )
  84. def get_received_txn_response(*args):
  85. return defer.succeed(None)
  86. self.datastore.get_received_txn_response = get_received_txn_response
  87. self.room_id = "a-room"
  88. # Mock the RoomMemberHandler
  89. hs.handlers.room_member_handler = Mock(spec=[])
  90. self.room_member_handler = hs.handlers.room_member_handler
  91. self.room_members = []
  92. def get_rooms_for_user(user):
  93. if user in self.room_members:
  94. return defer.succeed([self.room_id])
  95. else:
  96. return defer.succeed([])
  97. self.room_member_handler.get_rooms_for_user = get_rooms_for_user
  98. def get_room_members(room_id):
  99. if room_id == self.room_id:
  100. return defer.succeed(self.room_members)
  101. else:
  102. return defer.succeed([])
  103. self.room_member_handler.get_room_members = get_room_members
  104. def get_joined_rooms_for_user(user):
  105. if user in self.room_members:
  106. return defer.succeed([self.room_id])
  107. else:
  108. return defer.succeed([])
  109. self.room_member_handler.get_joined_rooms_for_user = get_joined_rooms_for_user
  110. @defer.inlineCallbacks
  111. def fetch_room_distributions_into(
  112. room_id, localusers=None, remotedomains=None, ignore_user=None
  113. ):
  114. members = yield get_room_members(room_id)
  115. for member in members:
  116. if ignore_user is not None and member == ignore_user:
  117. continue
  118. if hs.is_mine(member):
  119. if localusers is not None:
  120. localusers.add(member)
  121. else:
  122. if remotedomains is not None:
  123. remotedomains.add(member.domain)
  124. self.room_member_handler.fetch_room_distributions_into = (
  125. fetch_room_distributions_into
  126. )
  127. def check_joined_room(room_id, user_id):
  128. if user_id not in [u.to_string() for u in self.room_members]:
  129. raise AuthError(401, "User is not in the room")
  130. self.auth.check_joined_room = check_joined_room
  131. # Some local users to test with
  132. self.u_apple = UserID.from_string("@apple:test")
  133. self.u_banana = UserID.from_string("@banana:test")
  134. # Remote user
  135. self.u_onion = UserID.from_string("@onion:farm")
  136. @defer.inlineCallbacks
  137. def test_started_typing_local(self):
  138. self.room_members = [self.u_apple, self.u_banana]
  139. self.assertEquals(self.event_source.get_current_key(), 0)
  140. yield self.handler.started_typing(
  141. target_user=self.u_apple,
  142. auth_user=self.u_apple,
  143. room_id=self.room_id,
  144. timeout=20000,
  145. )
  146. self.on_new_event.assert_has_calls([
  147. call('typing_key', 1, rooms=[self.room_id]),
  148. ])
  149. self.assertEquals(self.event_source.get_current_key(), 1)
  150. events = yield self.event_source.get_new_events(
  151. room_ids=[self.room_id],
  152. from_key=0,
  153. )
  154. self.assertEquals(
  155. events[0],
  156. [
  157. {"type": "m.typing",
  158. "room_id": self.room_id,
  159. "content": {
  160. "user_ids": [self.u_apple.to_string()],
  161. }},
  162. ]
  163. )
  164. @defer.inlineCallbacks
  165. def test_started_typing_remote_send(self):
  166. self.room_members = [self.u_apple, self.u_onion]
  167. put_json = self.mock_http_client.put_json
  168. put_json.expect_call_and_return(
  169. call(
  170. "farm",
  171. path="/_matrix/federation/v1/send/1000000/",
  172. data=_expect_edu(
  173. "farm",
  174. "m.typing",
  175. content={
  176. "room_id": self.room_id,
  177. "user_id": self.u_apple.to_string(),
  178. "typing": True,
  179. }
  180. ),
  181. json_data_callback=ANY,
  182. long_retries=True,
  183. ),
  184. defer.succeed((200, "OK"))
  185. )
  186. yield self.handler.started_typing(
  187. target_user=self.u_apple,
  188. auth_user=self.u_apple,
  189. room_id=self.room_id,
  190. timeout=20000,
  191. )
  192. yield put_json.await_calls()
  193. @defer.inlineCallbacks
  194. def test_started_typing_remote_recv(self):
  195. self.room_members = [self.u_apple, self.u_onion]
  196. self.assertEquals(self.event_source.get_current_key(), 0)
  197. yield self.mock_federation_resource.trigger(
  198. "PUT",
  199. "/_matrix/federation/v1/send/1000000/",
  200. _make_edu_json(
  201. "farm",
  202. "m.typing",
  203. content={
  204. "room_id": self.room_id,
  205. "user_id": self.u_onion.to_string(),
  206. "typing": True,
  207. }
  208. )
  209. )
  210. self.on_new_event.assert_has_calls([
  211. call('typing_key', 1, rooms=[self.room_id]),
  212. ])
  213. self.assertEquals(self.event_source.get_current_key(), 1)
  214. events = yield self.event_source.get_new_events(
  215. room_ids=[self.room_id],
  216. from_key=0
  217. )
  218. self.assertEquals(events[0], [{
  219. "type": "m.typing",
  220. "room_id": self.room_id,
  221. "content": {
  222. "user_ids": [self.u_onion.to_string()],
  223. },
  224. }])
  225. @defer.inlineCallbacks
  226. def test_stopped_typing(self):
  227. self.room_members = [self.u_apple, self.u_banana, self.u_onion]
  228. put_json = self.mock_http_client.put_json
  229. put_json.expect_call_and_return(
  230. call(
  231. "farm",
  232. path="/_matrix/federation/v1/send/1000000/",
  233. data=_expect_edu(
  234. "farm",
  235. "m.typing",
  236. content={
  237. "room_id": self.room_id,
  238. "user_id": self.u_apple.to_string(),
  239. "typing": False,
  240. }
  241. ),
  242. json_data_callback=ANY,
  243. long_retries=True,
  244. ),
  245. defer.succeed((200, "OK"))
  246. )
  247. # Gut-wrenching
  248. from synapse.handlers.typing import RoomMember
  249. member = RoomMember(self.room_id, self.u_apple)
  250. self.handler._member_typing_until[member] = 1002000
  251. self.handler._member_typing_timer[member] = (
  252. self.clock.call_later(1002, lambda: 0)
  253. )
  254. self.handler._room_typing[self.room_id] = set((self.u_apple,))
  255. self.assertEquals(self.event_source.get_current_key(), 0)
  256. yield self.handler.stopped_typing(
  257. target_user=self.u_apple,
  258. auth_user=self.u_apple,
  259. room_id=self.room_id,
  260. )
  261. self.on_new_event.assert_has_calls([
  262. call('typing_key', 1, rooms=[self.room_id]),
  263. ])
  264. yield put_json.await_calls()
  265. self.assertEquals(self.event_source.get_current_key(), 1)
  266. events = yield self.event_source.get_new_events(
  267. room_ids=[self.room_id],
  268. from_key=0,
  269. )
  270. self.assertEquals(events[0], [{
  271. "type": "m.typing",
  272. "room_id": self.room_id,
  273. "content": {
  274. "user_ids": [],
  275. },
  276. }])
  277. @defer.inlineCallbacks
  278. def test_typing_timeout(self):
  279. self.room_members = [self.u_apple, self.u_banana]
  280. self.assertEquals(self.event_source.get_current_key(), 0)
  281. yield self.handler.started_typing(
  282. target_user=self.u_apple,
  283. auth_user=self.u_apple,
  284. room_id=self.room_id,
  285. timeout=10000,
  286. )
  287. self.on_new_event.assert_has_calls([
  288. call('typing_key', 1, rooms=[self.room_id]),
  289. ])
  290. self.on_new_event.reset_mock()
  291. self.assertEquals(self.event_source.get_current_key(), 1)
  292. events = yield self.event_source.get_new_events(
  293. room_ids=[self.room_id],
  294. from_key=0,
  295. )
  296. self.assertEquals(events[0], [{
  297. "type": "m.typing",
  298. "room_id": self.room_id,
  299. "content": {
  300. "user_ids": [self.u_apple.to_string()],
  301. },
  302. }])
  303. self.clock.advance_time(11)
  304. self.on_new_event.assert_has_calls([
  305. call('typing_key', 2, rooms=[self.room_id]),
  306. ])
  307. self.assertEquals(self.event_source.get_current_key(), 2)
  308. events = yield self.event_source.get_new_events(
  309. room_ids=[self.room_id],
  310. from_key=1,
  311. )
  312. self.assertEquals(events[0], [{
  313. "type": "m.typing",
  314. "room_id": self.room_id,
  315. "content": {
  316. "user_ids": [],
  317. },
  318. }])
  319. # SYN-230 - see if we can still set after timeout
  320. yield self.handler.started_typing(
  321. target_user=self.u_apple,
  322. auth_user=self.u_apple,
  323. room_id=self.room_id,
  324. timeout=10000,
  325. )
  326. self.on_new_event.assert_has_calls([
  327. call('typing_key', 3, rooms=[self.room_id]),
  328. ])
  329. self.on_new_event.reset_mock()
  330. self.assertEquals(self.event_source.get_current_key(), 3)
  331. events = yield self.event_source.get_new_events(
  332. room_ids=[self.room_id],
  333. from_key=0,
  334. )
  335. self.assertEquals(events[0], [{
  336. "type": "m.typing",
  337. "room_id": self.room_id,
  338. "content": {
  339. "user_ids": [self.u_apple.to_string()],
  340. },
  341. }])