test_typing.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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.types import UserID
  24. def _expect_edu(destination, edu_type, content, origin="test"):
  25. return {
  26. "origin": origin,
  27. "origin_server_ts": 1000000,
  28. "pdus": [],
  29. "edus": [
  30. {
  31. "edu_type": edu_type,
  32. "content": content,
  33. }
  34. ],
  35. "pdu_failures": [],
  36. }
  37. def _make_edu_json(origin, edu_type, content):
  38. return json.dumps(_expect_edu("test", edu_type, content, origin=origin))
  39. class TypingNotificationsTestCase(unittest.TestCase):
  40. """Tests typing notifications to rooms."""
  41. @defer.inlineCallbacks
  42. def setUp(self):
  43. self.clock = MockClock()
  44. self.mock_http_client = Mock(spec=[])
  45. self.mock_http_client.put_json = DeferredMockCallable()
  46. self.mock_federation_resource = MockHttpResource()
  47. mock_notifier = Mock()
  48. self.on_new_event = mock_notifier.on_new_event
  49. self.auth = Mock(spec=[])
  50. self.state_handler = Mock()
  51. hs = yield setup_test_homeserver(
  52. "test",
  53. auth=self.auth,
  54. clock=self.clock,
  55. datastore=Mock(spec=[
  56. # Bits that Federation needs
  57. "prep_send_transaction",
  58. "delivered_txn",
  59. "get_received_txn_response",
  60. "set_received_txn_response",
  61. "get_destination_retry_timings",
  62. "get_devices_by_remote",
  63. # Bits that user_directory needs
  64. "get_user_directory_stream_pos",
  65. "get_current_state_deltas",
  66. ]),
  67. state_handler=self.state_handler,
  68. handlers=Mock(),
  69. notifier=mock_notifier,
  70. resource_for_client=Mock(),
  71. resource_for_federation=self.mock_federation_resource,
  72. http_client=self.mock_http_client,
  73. keyring=Mock(),
  74. )
  75. self.handler = hs.get_typing_handler()
  76. self.event_source = hs.get_event_sources().sources["typing"]
  77. self.datastore = hs.get_datastore()
  78. retry_timings_res = {
  79. "destination": "",
  80. "retry_last_ts": 0,
  81. "retry_interval": 0,
  82. }
  83. self.datastore.get_destination_retry_timings.return_value = (
  84. defer.succeed(retry_timings_res)
  85. )
  86. self.datastore.get_devices_by_remote.return_value = (0, [])
  87. def get_received_txn_response(*args):
  88. return defer.succeed(None)
  89. self.datastore.get_received_txn_response = get_received_txn_response
  90. self.room_id = "a-room"
  91. self.room_members = []
  92. def check_joined_room(room_id, user_id):
  93. if user_id not in [u.to_string() for u in self.room_members]:
  94. raise AuthError(401, "User is not in the room")
  95. def get_joined_hosts_for_room(room_id):
  96. return set(member.domain for member in self.room_members)
  97. self.datastore.get_joined_hosts_for_room = get_joined_hosts_for_room
  98. def get_current_user_in_room(room_id):
  99. return set(str(u) for u in self.room_members)
  100. self.state_handler.get_current_user_in_room = get_current_user_in_room
  101. self.datastore.get_user_directory_stream_pos.return_value = (
  102. # we deliberately return a non-None stream pos to avoid doing an initial_spam
  103. defer.succeed(1)
  104. )
  105. self.datastore.get_current_state_deltas.return_value = (
  106. None
  107. )
  108. self.auth.check_joined_room = check_joined_room
  109. self.datastore.get_to_device_stream_token = lambda: 0
  110. self.datastore.get_new_device_msgs_for_remote = (
  111. lambda *args, **kargs: ([], 0)
  112. )
  113. self.datastore.delete_device_msgs_for_remote = (
  114. lambda *args, **kargs: None
  115. )
  116. # Some local users to test with
  117. self.u_apple = UserID.from_string("@apple:test")
  118. self.u_banana = UserID.from_string("@banana:test")
  119. # Remote user
  120. self.u_onion = UserID.from_string("@onion:farm")
  121. @defer.inlineCallbacks
  122. def test_started_typing_local(self):
  123. self.room_members = [self.u_apple, self.u_banana]
  124. self.assertEquals(self.event_source.get_current_key(), 0)
  125. yield self.handler.started_typing(
  126. target_user=self.u_apple,
  127. auth_user=self.u_apple,
  128. room_id=self.room_id,
  129. timeout=20000,
  130. )
  131. self.on_new_event.assert_has_calls([
  132. call('typing_key', 1, rooms=[self.room_id]),
  133. ])
  134. self.assertEquals(self.event_source.get_current_key(), 1)
  135. events = yield self.event_source.get_new_events(
  136. room_ids=[self.room_id],
  137. from_key=0,
  138. )
  139. self.assertEquals(
  140. events[0],
  141. [
  142. {"type": "m.typing",
  143. "room_id": self.room_id,
  144. "content": {
  145. "user_ids": [self.u_apple.to_string()],
  146. }},
  147. ]
  148. )
  149. @defer.inlineCallbacks
  150. def test_started_typing_remote_send(self):
  151. self.room_members = [self.u_apple, self.u_onion]
  152. put_json = self.mock_http_client.put_json
  153. put_json.expect_call_and_return(
  154. call(
  155. "farm",
  156. path="/_matrix/federation/v1/send/1000000/",
  157. data=_expect_edu(
  158. "farm",
  159. "m.typing",
  160. content={
  161. "room_id": self.room_id,
  162. "user_id": self.u_apple.to_string(),
  163. "typing": True,
  164. }
  165. ),
  166. json_data_callback=ANY,
  167. long_retries=True,
  168. backoff_on_404=True,
  169. ),
  170. defer.succeed((200, "OK"))
  171. )
  172. yield self.handler.started_typing(
  173. target_user=self.u_apple,
  174. auth_user=self.u_apple,
  175. room_id=self.room_id,
  176. timeout=20000,
  177. )
  178. yield put_json.await_calls()
  179. @defer.inlineCallbacks
  180. def test_started_typing_remote_recv(self):
  181. self.room_members = [self.u_apple, self.u_onion]
  182. self.assertEquals(self.event_source.get_current_key(), 0)
  183. yield self.mock_federation_resource.trigger(
  184. "PUT",
  185. "/_matrix/federation/v1/send/1000000/",
  186. _make_edu_json(
  187. "farm",
  188. "m.typing",
  189. content={
  190. "room_id": self.room_id,
  191. "user_id": self.u_onion.to_string(),
  192. "typing": True,
  193. }
  194. ),
  195. federation_auth=True,
  196. )
  197. self.on_new_event.assert_has_calls([
  198. call('typing_key', 1, rooms=[self.room_id]),
  199. ])
  200. self.assertEquals(self.event_source.get_current_key(), 1)
  201. events = yield self.event_source.get_new_events(
  202. room_ids=[self.room_id],
  203. from_key=0
  204. )
  205. self.assertEquals(events[0], [{
  206. "type": "m.typing",
  207. "room_id": self.room_id,
  208. "content": {
  209. "user_ids": [self.u_onion.to_string()],
  210. },
  211. }])
  212. @defer.inlineCallbacks
  213. def test_stopped_typing(self):
  214. self.room_members = [self.u_apple, self.u_banana, self.u_onion]
  215. put_json = self.mock_http_client.put_json
  216. put_json.expect_call_and_return(
  217. call(
  218. "farm",
  219. path="/_matrix/federation/v1/send/1000000/",
  220. data=_expect_edu(
  221. "farm",
  222. "m.typing",
  223. content={
  224. "room_id": self.room_id,
  225. "user_id": self.u_apple.to_string(),
  226. "typing": False,
  227. }
  228. ),
  229. json_data_callback=ANY,
  230. long_retries=True,
  231. backoff_on_404=True,
  232. ),
  233. defer.succeed((200, "OK"))
  234. )
  235. # Gut-wrenching
  236. from synapse.handlers.typing import RoomMember
  237. member = RoomMember(self.room_id, self.u_apple.to_string())
  238. self.handler._member_typing_until[member] = 1002000
  239. self.handler._room_typing[self.room_id] = set([self.u_apple.to_string()])
  240. self.assertEquals(self.event_source.get_current_key(), 0)
  241. yield self.handler.stopped_typing(
  242. target_user=self.u_apple,
  243. auth_user=self.u_apple,
  244. room_id=self.room_id,
  245. )
  246. self.on_new_event.assert_has_calls([
  247. call('typing_key', 1, rooms=[self.room_id]),
  248. ])
  249. yield put_json.await_calls()
  250. self.assertEquals(self.event_source.get_current_key(), 1)
  251. events = yield self.event_source.get_new_events(
  252. room_ids=[self.room_id],
  253. from_key=0,
  254. )
  255. self.assertEquals(events[0], [{
  256. "type": "m.typing",
  257. "room_id": self.room_id,
  258. "content": {
  259. "user_ids": [],
  260. },
  261. }])
  262. @defer.inlineCallbacks
  263. def test_typing_timeout(self):
  264. self.room_members = [self.u_apple, self.u_banana]
  265. self.assertEquals(self.event_source.get_current_key(), 0)
  266. yield self.handler.started_typing(
  267. target_user=self.u_apple,
  268. auth_user=self.u_apple,
  269. room_id=self.room_id,
  270. timeout=10000,
  271. )
  272. self.on_new_event.assert_has_calls([
  273. call('typing_key', 1, rooms=[self.room_id]),
  274. ])
  275. self.on_new_event.reset_mock()
  276. self.assertEquals(self.event_source.get_current_key(), 1)
  277. events = yield self.event_source.get_new_events(
  278. room_ids=[self.room_id],
  279. from_key=0,
  280. )
  281. self.assertEquals(events[0], [{
  282. "type": "m.typing",
  283. "room_id": self.room_id,
  284. "content": {
  285. "user_ids": [self.u_apple.to_string()],
  286. },
  287. }])
  288. self.clock.advance_time(16)
  289. self.on_new_event.assert_has_calls([
  290. call('typing_key', 2, rooms=[self.room_id]),
  291. ])
  292. self.assertEquals(self.event_source.get_current_key(), 2)
  293. events = yield self.event_source.get_new_events(
  294. room_ids=[self.room_id],
  295. from_key=1,
  296. )
  297. self.assertEquals(events[0], [{
  298. "type": "m.typing",
  299. "room_id": self.room_id,
  300. "content": {
  301. "user_ids": [],
  302. },
  303. }])
  304. # SYN-230 - see if we can still set after timeout
  305. yield self.handler.started_typing(
  306. target_user=self.u_apple,
  307. auth_user=self.u_apple,
  308. room_id=self.room_id,
  309. timeout=10000,
  310. )
  311. self.on_new_event.assert_has_calls([
  312. call('typing_key', 3, rooms=[self.room_id]),
  313. ])
  314. self.on_new_event.reset_mock()
  315. self.assertEquals(self.event_source.get_current_key(), 3)
  316. events = yield self.event_source.get_new_events(
  317. room_ids=[self.room_id],
  318. from_key=0,
  319. )
  320. self.assertEquals(events[0], [{
  321. "type": "m.typing",
  322. "room_id": self.room_id,
  323. "content": {
  324. "user_ids": [self.u_apple.to_string()],
  325. },
  326. }])