test_presence.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. # -*- coding: utf-8 -*-
  2. # Copyright 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 mock import Mock, call
  16. from signedjson.key import generate_signing_key
  17. from synapse.api.constants import EventTypes, Membership, PresenceState
  18. from synapse.events import room_version_to_event_format
  19. from synapse.events.builder import EventBuilder
  20. from synapse.handlers.presence import (
  21. FEDERATION_PING_INTERVAL,
  22. FEDERATION_TIMEOUT,
  23. IDLE_TIMER,
  24. LAST_ACTIVE_GRANULARITY,
  25. SYNC_ONLINE_TIMEOUT,
  26. handle_timeout,
  27. handle_update,
  28. )
  29. from synapse.rest.client.v1 import room
  30. from synapse.storage.presence import UserPresenceState
  31. from synapse.types import UserID, get_domain_from_id
  32. from tests import unittest
  33. class PresenceUpdateTestCase(unittest.TestCase):
  34. def test_offline_to_online(self):
  35. wheel_timer = Mock()
  36. user_id = "@foo:bar"
  37. now = 5000000
  38. prev_state = UserPresenceState.default(user_id)
  39. new_state = prev_state.copy_and_replace(
  40. state=PresenceState.ONLINE, last_active_ts=now
  41. )
  42. state, persist_and_notify, federation_ping = handle_update(
  43. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  44. )
  45. self.assertTrue(persist_and_notify)
  46. self.assertTrue(state.currently_active)
  47. self.assertEquals(new_state.state, state.state)
  48. self.assertEquals(new_state.status_msg, state.status_msg)
  49. self.assertEquals(state.last_federation_update_ts, now)
  50. self.assertEquals(wheel_timer.insert.call_count, 3)
  51. wheel_timer.insert.assert_has_calls(
  52. [
  53. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  54. call(
  55. now=now,
  56. obj=user_id,
  57. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  58. ),
  59. call(
  60. now=now,
  61. obj=user_id,
  62. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  63. ),
  64. ],
  65. any_order=True,
  66. )
  67. def test_online_to_online(self):
  68. wheel_timer = Mock()
  69. user_id = "@foo:bar"
  70. now = 5000000
  71. prev_state = UserPresenceState.default(user_id)
  72. prev_state = prev_state.copy_and_replace(
  73. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  74. )
  75. new_state = prev_state.copy_and_replace(
  76. state=PresenceState.ONLINE, last_active_ts=now
  77. )
  78. state, persist_and_notify, federation_ping = handle_update(
  79. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  80. )
  81. self.assertFalse(persist_and_notify)
  82. self.assertTrue(federation_ping)
  83. self.assertTrue(state.currently_active)
  84. self.assertEquals(new_state.state, state.state)
  85. self.assertEquals(new_state.status_msg, state.status_msg)
  86. self.assertEquals(state.last_federation_update_ts, now)
  87. self.assertEquals(wheel_timer.insert.call_count, 3)
  88. wheel_timer.insert.assert_has_calls(
  89. [
  90. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  91. call(
  92. now=now,
  93. obj=user_id,
  94. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  95. ),
  96. call(
  97. now=now,
  98. obj=user_id,
  99. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  100. ),
  101. ],
  102. any_order=True,
  103. )
  104. def test_online_to_online_last_active_noop(self):
  105. wheel_timer = Mock()
  106. user_id = "@foo:bar"
  107. now = 5000000
  108. prev_state = UserPresenceState.default(user_id)
  109. prev_state = prev_state.copy_and_replace(
  110. state=PresenceState.ONLINE,
  111. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 10,
  112. currently_active=True,
  113. )
  114. new_state = prev_state.copy_and_replace(
  115. state=PresenceState.ONLINE, last_active_ts=now
  116. )
  117. state, persist_and_notify, federation_ping = handle_update(
  118. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  119. )
  120. self.assertFalse(persist_and_notify)
  121. self.assertTrue(federation_ping)
  122. self.assertTrue(state.currently_active)
  123. self.assertEquals(new_state.state, state.state)
  124. self.assertEquals(new_state.status_msg, state.status_msg)
  125. self.assertEquals(state.last_federation_update_ts, now)
  126. self.assertEquals(wheel_timer.insert.call_count, 3)
  127. wheel_timer.insert.assert_has_calls(
  128. [
  129. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  130. call(
  131. now=now,
  132. obj=user_id,
  133. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  134. ),
  135. call(
  136. now=now,
  137. obj=user_id,
  138. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  139. ),
  140. ],
  141. any_order=True,
  142. )
  143. def test_online_to_online_last_active(self):
  144. wheel_timer = Mock()
  145. user_id = "@foo:bar"
  146. now = 5000000
  147. prev_state = UserPresenceState.default(user_id)
  148. prev_state = prev_state.copy_and_replace(
  149. state=PresenceState.ONLINE,
  150. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 1,
  151. currently_active=True,
  152. )
  153. new_state = prev_state.copy_and_replace(state=PresenceState.ONLINE)
  154. state, persist_and_notify, federation_ping = handle_update(
  155. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  156. )
  157. self.assertTrue(persist_and_notify)
  158. self.assertFalse(state.currently_active)
  159. self.assertEquals(new_state.state, state.state)
  160. self.assertEquals(new_state.status_msg, state.status_msg)
  161. self.assertEquals(state.last_federation_update_ts, now)
  162. self.assertEquals(wheel_timer.insert.call_count, 2)
  163. wheel_timer.insert.assert_has_calls(
  164. [
  165. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  166. call(
  167. now=now,
  168. obj=user_id,
  169. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  170. ),
  171. ],
  172. any_order=True,
  173. )
  174. def test_remote_ping_timer(self):
  175. wheel_timer = Mock()
  176. user_id = "@foo:bar"
  177. now = 5000000
  178. prev_state = UserPresenceState.default(user_id)
  179. prev_state = prev_state.copy_and_replace(
  180. state=PresenceState.ONLINE, last_active_ts=now
  181. )
  182. new_state = prev_state.copy_and_replace(state=PresenceState.ONLINE)
  183. state, persist_and_notify, federation_ping = handle_update(
  184. prev_state, new_state, is_mine=False, wheel_timer=wheel_timer, now=now
  185. )
  186. self.assertFalse(persist_and_notify)
  187. self.assertFalse(federation_ping)
  188. self.assertFalse(state.currently_active)
  189. self.assertEquals(new_state.state, state.state)
  190. self.assertEquals(new_state.status_msg, state.status_msg)
  191. self.assertEquals(wheel_timer.insert.call_count, 1)
  192. wheel_timer.insert.assert_has_calls(
  193. [
  194. call(
  195. now=now,
  196. obj=user_id,
  197. then=new_state.last_federation_update_ts + FEDERATION_TIMEOUT,
  198. )
  199. ],
  200. any_order=True,
  201. )
  202. def test_online_to_offline(self):
  203. wheel_timer = Mock()
  204. user_id = "@foo:bar"
  205. now = 5000000
  206. prev_state = UserPresenceState.default(user_id)
  207. prev_state = prev_state.copy_and_replace(
  208. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  209. )
  210. new_state = prev_state.copy_and_replace(state=PresenceState.OFFLINE)
  211. state, persist_and_notify, federation_ping = handle_update(
  212. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  213. )
  214. self.assertTrue(persist_and_notify)
  215. self.assertEquals(new_state.state, state.state)
  216. self.assertEquals(state.last_federation_update_ts, now)
  217. self.assertEquals(wheel_timer.insert.call_count, 0)
  218. def test_online_to_idle(self):
  219. wheel_timer = Mock()
  220. user_id = "@foo:bar"
  221. now = 5000000
  222. prev_state = UserPresenceState.default(user_id)
  223. prev_state = prev_state.copy_and_replace(
  224. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  225. )
  226. new_state = prev_state.copy_and_replace(state=PresenceState.UNAVAILABLE)
  227. state, persist_and_notify, federation_ping = handle_update(
  228. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  229. )
  230. self.assertTrue(persist_and_notify)
  231. self.assertEquals(new_state.state, state.state)
  232. self.assertEquals(state.last_federation_update_ts, now)
  233. self.assertEquals(new_state.state, state.state)
  234. self.assertEquals(new_state.status_msg, state.status_msg)
  235. self.assertEquals(wheel_timer.insert.call_count, 1)
  236. wheel_timer.insert.assert_has_calls(
  237. [
  238. call(
  239. now=now,
  240. obj=user_id,
  241. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  242. )
  243. ],
  244. any_order=True,
  245. )
  246. class PresenceTimeoutTestCase(unittest.TestCase):
  247. def test_idle_timer(self):
  248. user_id = "@foo:bar"
  249. now = 5000000
  250. state = UserPresenceState.default(user_id)
  251. state = state.copy_and_replace(
  252. state=PresenceState.ONLINE,
  253. last_active_ts=now - IDLE_TIMER - 1,
  254. last_user_sync_ts=now,
  255. )
  256. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  257. self.assertIsNotNone(new_state)
  258. self.assertEquals(new_state.state, PresenceState.UNAVAILABLE)
  259. def test_sync_timeout(self):
  260. user_id = "@foo:bar"
  261. now = 5000000
  262. state = UserPresenceState.default(user_id)
  263. state = state.copy_and_replace(
  264. state=PresenceState.ONLINE,
  265. last_active_ts=0,
  266. last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1,
  267. )
  268. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  269. self.assertIsNotNone(new_state)
  270. self.assertEquals(new_state.state, PresenceState.OFFLINE)
  271. def test_sync_online(self):
  272. user_id = "@foo:bar"
  273. now = 5000000
  274. state = UserPresenceState.default(user_id)
  275. state = state.copy_and_replace(
  276. state=PresenceState.ONLINE,
  277. last_active_ts=now - SYNC_ONLINE_TIMEOUT - 1,
  278. last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1,
  279. )
  280. new_state = handle_timeout(
  281. state, is_mine=True, syncing_user_ids=set([user_id]), now=now
  282. )
  283. self.assertIsNotNone(new_state)
  284. self.assertEquals(new_state.state, PresenceState.ONLINE)
  285. def test_federation_ping(self):
  286. user_id = "@foo:bar"
  287. now = 5000000
  288. state = UserPresenceState.default(user_id)
  289. state = state.copy_and_replace(
  290. state=PresenceState.ONLINE,
  291. last_active_ts=now,
  292. last_user_sync_ts=now,
  293. last_federation_update_ts=now - FEDERATION_PING_INTERVAL - 1,
  294. )
  295. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  296. self.assertIsNotNone(new_state)
  297. self.assertEquals(new_state, new_state)
  298. def test_no_timeout(self):
  299. user_id = "@foo:bar"
  300. now = 5000000
  301. state = UserPresenceState.default(user_id)
  302. state = state.copy_and_replace(
  303. state=PresenceState.ONLINE,
  304. last_active_ts=now,
  305. last_user_sync_ts=now,
  306. last_federation_update_ts=now,
  307. )
  308. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  309. self.assertIsNone(new_state)
  310. def test_federation_timeout(self):
  311. user_id = "@foo:bar"
  312. now = 5000000
  313. state = UserPresenceState.default(user_id)
  314. state = state.copy_and_replace(
  315. state=PresenceState.ONLINE,
  316. last_active_ts=now,
  317. last_user_sync_ts=now,
  318. last_federation_update_ts=now - FEDERATION_TIMEOUT - 1,
  319. )
  320. new_state = handle_timeout(
  321. state, is_mine=False, syncing_user_ids=set(), now=now
  322. )
  323. self.assertIsNotNone(new_state)
  324. self.assertEquals(new_state.state, PresenceState.OFFLINE)
  325. def test_last_active(self):
  326. user_id = "@foo:bar"
  327. now = 5000000
  328. state = UserPresenceState.default(user_id)
  329. state = state.copy_and_replace(
  330. state=PresenceState.ONLINE,
  331. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 1,
  332. last_user_sync_ts=now,
  333. last_federation_update_ts=now,
  334. )
  335. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  336. self.assertIsNotNone(new_state)
  337. self.assertEquals(state, new_state)
  338. class PresenceJoinTestCase(unittest.HomeserverTestCase):
  339. """Tests remote servers get told about presence of users in the room when
  340. they join and when new local users join.
  341. """
  342. user_id = "@test:server"
  343. servlets = [room.register_servlets]
  344. def make_homeserver(self, reactor, clock):
  345. hs = self.setup_test_homeserver(
  346. "server", http_client=None, federation_sender=Mock()
  347. )
  348. return hs
  349. def prepare(self, reactor, clock, hs):
  350. self.federation_sender = hs.get_federation_sender()
  351. self.event_builder_factory = hs.get_event_builder_factory()
  352. self.federation_handler = hs.get_handlers().federation_handler
  353. self.presence_handler = hs.get_presence_handler()
  354. # self.event_builder_for_2 = EventBuilderFactory(hs)
  355. # self.event_builder_for_2.hostname = "test2"
  356. self.store = hs.get_datastore()
  357. self.state = hs.get_state_handler()
  358. self.auth = hs.get_auth()
  359. # We don't actually check signatures in tests, so lets just create a
  360. # random key to use.
  361. self.random_signing_key = generate_signing_key("ver")
  362. def test_remote_joins(self):
  363. # We advance time to something that isn't 0, as we use 0 as a special
  364. # value.
  365. self.reactor.advance(1000000000000)
  366. # Create a room with two local users
  367. room_id = self.helper.create_room_as(self.user_id)
  368. self.helper.join(room_id, "@test2:server")
  369. # Mark test2 as online, test will be offline with a last_active of 0
  370. self.presence_handler.set_state(
  371. UserID.from_string("@test2:server"), {"presence": PresenceState.ONLINE}
  372. )
  373. self.reactor.pump([0]) # Wait for presence updates to be handled
  374. #
  375. # Test that a new server gets told about existing presence
  376. #
  377. self.federation_sender.reset_mock()
  378. # Add a new remote server to the room
  379. self._add_new_user(room_id, "@alice:server2")
  380. # We shouldn't have sent out any local presence *updates*
  381. self.federation_sender.send_presence.assert_not_called()
  382. # When new server is joined we send it the local users presence states.
  383. # We expect to only see user @test2:server, as @test:server is offline
  384. # and has a zero last_active_ts
  385. expected_state = self.get_success(
  386. self.presence_handler.current_state_for_user("@test2:server")
  387. )
  388. self.assertEqual(expected_state.state, PresenceState.ONLINE)
  389. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  390. destinations=["server2"], states=[expected_state]
  391. )
  392. #
  393. # Test that only the new server gets sent presence and not existing servers
  394. #
  395. self.federation_sender.reset_mock()
  396. self._add_new_user(room_id, "@bob:server3")
  397. self.federation_sender.send_presence.assert_not_called()
  398. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  399. destinations=["server3"], states=[expected_state]
  400. )
  401. def test_remote_gets_presence_when_local_user_joins(self):
  402. # We advance time to something that isn't 0, as we use 0 as a special
  403. # value.
  404. self.reactor.advance(1000000000000)
  405. # Create a room with one local users
  406. room_id = self.helper.create_room_as(self.user_id)
  407. # Mark test as online
  408. self.presence_handler.set_state(
  409. UserID.from_string("@test:server"), {"presence": PresenceState.ONLINE}
  410. )
  411. # Mark test2 as online, test will be offline with a last_active of 0.
  412. # Note we don't join them to the room yet
  413. self.presence_handler.set_state(
  414. UserID.from_string("@test2:server"), {"presence": PresenceState.ONLINE}
  415. )
  416. # Add servers to the room
  417. self._add_new_user(room_id, "@alice:server2")
  418. self._add_new_user(room_id, "@bob:server3")
  419. self.reactor.pump([0]) # Wait for presence updates to be handled
  420. #
  421. # Test that when a local join happens remote servers get told about it
  422. #
  423. self.federation_sender.reset_mock()
  424. # Join local user to room
  425. self.helper.join(room_id, "@test2:server")
  426. self.reactor.pump([0]) # Wait for presence updates to be handled
  427. # We shouldn't have sent out any local presence *updates*
  428. self.federation_sender.send_presence.assert_not_called()
  429. # We expect to only send test2 presence to server2 and server3
  430. expected_state = self.get_success(
  431. self.presence_handler.current_state_for_user("@test2:server")
  432. )
  433. self.assertEqual(expected_state.state, PresenceState.ONLINE)
  434. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  435. destinations=set(("server2", "server3")), states=[expected_state]
  436. )
  437. def _add_new_user(self, room_id, user_id):
  438. """Add new user to the room by creating an event and poking the federation API.
  439. """
  440. hostname = get_domain_from_id(user_id)
  441. room_version = self.get_success(self.store.get_room_version(room_id))
  442. builder = EventBuilder(
  443. state=self.state,
  444. auth=self.auth,
  445. store=self.store,
  446. clock=self.clock,
  447. hostname=hostname,
  448. signing_key=self.random_signing_key,
  449. format_version=room_version_to_event_format(room_version),
  450. room_id=room_id,
  451. type=EventTypes.Member,
  452. sender=user_id,
  453. state_key=user_id,
  454. content={"membership": Membership.JOIN},
  455. )
  456. prev_event_ids = self.get_success(
  457. self.store.get_latest_event_ids_in_room(room_id)
  458. )
  459. event = self.get_success(builder.build(prev_event_ids))
  460. self.get_success(self.federation_handler.on_receive_pdu(hostname, event))
  461. # Check that it was successfully persisted.
  462. self.get_success(self.store.get_event(event.event_id))
  463. self.get_success(self.store.get_event(event.event_id))