test_presence.py 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014 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 twisted.trial import unittest
  16. from twisted.internet import defer, reactor
  17. from mock import Mock, call, ANY
  18. import logging
  19. import json
  20. from ..utils import MockHttpResource, MockClock, DeferredMockCallable
  21. from synapse.server import HomeServer
  22. from synapse.api.constants import PresenceState
  23. from synapse.api.errors import SynapseError
  24. from synapse.handlers.presence import PresenceHandler, UserPresenceCache
  25. OFFLINE = PresenceState.OFFLINE
  26. UNAVAILABLE = PresenceState.UNAVAILABLE
  27. ONLINE = PresenceState.ONLINE
  28. logging.getLogger().addHandler(logging.NullHandler())
  29. def _expect_edu(destination, edu_type, content, origin="test"):
  30. return {
  31. "origin": origin,
  32. "ts": 1000000,
  33. "pdus": [],
  34. "edus": [
  35. {
  36. "origin": origin,
  37. "destination": destination,
  38. "edu_type": edu_type,
  39. "content": content,
  40. }
  41. ],
  42. }
  43. def _make_edu_json(origin, edu_type, content):
  44. return json.dumps(_expect_edu("test", edu_type, content, origin=origin))
  45. class JustPresenceHandlers(object):
  46. def __init__(self, hs):
  47. self.presence_handler = PresenceHandler(hs)
  48. class PresenceStateTestCase(unittest.TestCase):
  49. """ Tests presence management. """
  50. def setUp(self):
  51. hs = HomeServer("test",
  52. clock=MockClock(),
  53. db_pool=None,
  54. datastore=Mock(spec=[
  55. "get_presence_state",
  56. "set_presence_state",
  57. "add_presence_list_pending",
  58. "set_presence_list_accepted",
  59. ]),
  60. handlers=None,
  61. resource_for_federation=Mock(),
  62. http_client=None,
  63. )
  64. hs.handlers = JustPresenceHandlers(hs)
  65. self.datastore = hs.get_datastore()
  66. def is_presence_visible(observed_localpart, observer_userid):
  67. allow = (observed_localpart == "apple" and
  68. observer_userid == "@banana:test"
  69. )
  70. return defer.succeed(allow)
  71. self.datastore.is_presence_visible = is_presence_visible
  72. # Mock the RoomMemberHandler
  73. room_member_handler = Mock(spec=[])
  74. hs.handlers.room_member_handler = room_member_handler
  75. logging.getLogger().debug("Mocking room_member_handler=%r", room_member_handler)
  76. # Some local users to test with
  77. self.u_apple = hs.parse_userid("@apple:test")
  78. self.u_banana = hs.parse_userid("@banana:test")
  79. self.u_clementine = hs.parse_userid("@clementine:test")
  80. self.handler = hs.get_handlers().presence_handler
  81. self.room_members = []
  82. def get_rooms_for_user(user):
  83. if user in self.room_members:
  84. return defer.succeed(["a-room"])
  85. else:
  86. return defer.succeed([])
  87. room_member_handler.get_rooms_for_user = get_rooms_for_user
  88. def get_room_members(room_id):
  89. if room_id == "a-room":
  90. return defer.succeed(self.room_members)
  91. else:
  92. return defer.succeed([])
  93. room_member_handler.get_room_members = get_room_members
  94. def user_rooms_intersect(userlist):
  95. room_member_ids = map(lambda u: u.to_string(), self.room_members)
  96. shared = all(map(lambda i: i in room_member_ids, userlist))
  97. return defer.succeed(shared)
  98. self.datastore.user_rooms_intersect = user_rooms_intersect
  99. self.mock_start = Mock()
  100. self.mock_stop = Mock()
  101. self.handler.start_polling_presence = self.mock_start
  102. self.handler.stop_polling_presence = self.mock_stop
  103. @defer.inlineCallbacks
  104. def test_get_my_state(self):
  105. mocked_get = self.datastore.get_presence_state
  106. mocked_get.return_value = defer.succeed(
  107. {"presence": ONLINE, "status_msg": "Online"}
  108. )
  109. state = yield self.handler.get_state(
  110. target_user=self.u_apple, auth_user=self.u_apple
  111. )
  112. self.assertEquals(
  113. {"presence": ONLINE, "status_msg": "Online"},
  114. state
  115. )
  116. mocked_get.assert_called_with("apple")
  117. @defer.inlineCallbacks
  118. def test_get_allowed_state(self):
  119. mocked_get = self.datastore.get_presence_state
  120. mocked_get.return_value = defer.succeed(
  121. {"presence": ONLINE, "status_msg": "Online"}
  122. )
  123. state = yield self.handler.get_state(
  124. target_user=self.u_apple, auth_user=self.u_banana
  125. )
  126. self.assertEquals(
  127. {"presence": ONLINE, "status_msg": "Online"},
  128. state
  129. )
  130. mocked_get.assert_called_with("apple")
  131. @defer.inlineCallbacks
  132. def test_get_same_room_state(self):
  133. mocked_get = self.datastore.get_presence_state
  134. mocked_get.return_value = defer.succeed(
  135. {"presence": ONLINE, "status_msg": "Online"}
  136. )
  137. self.room_members = [self.u_apple, self.u_clementine]
  138. state = yield self.handler.get_state(
  139. target_user=self.u_apple, auth_user=self.u_clementine
  140. )
  141. self.assertEquals(
  142. {"presence": ONLINE, "status_msg": "Online"},
  143. state
  144. )
  145. @defer.inlineCallbacks
  146. def test_get_disallowed_state(self):
  147. mocked_get = self.datastore.get_presence_state
  148. mocked_get.return_value = defer.succeed(
  149. {"presence": ONLINE, "status_msg": "Online"}
  150. )
  151. self.room_members = []
  152. yield self.assertFailure(
  153. self.handler.get_state(
  154. target_user=self.u_apple, auth_user=self.u_clementine
  155. ),
  156. SynapseError
  157. )
  158. @defer.inlineCallbacks
  159. def test_set_my_state(self):
  160. mocked_set = self.datastore.set_presence_state
  161. mocked_set.return_value = defer.succeed({"presence": OFFLINE})
  162. yield self.handler.set_state(
  163. target_user=self.u_apple, auth_user=self.u_apple,
  164. state={"presence": UNAVAILABLE, "status_msg": "Away"})
  165. mocked_set.assert_called_with("apple",
  166. {"presence": UNAVAILABLE,
  167. "status_msg": "Away",
  168. "last_active": 1000000, # MockClock
  169. }
  170. )
  171. self.mock_start.assert_called_with(self.u_apple,
  172. state={
  173. "presence": UNAVAILABLE,
  174. "status_msg": "Away",
  175. "last_active": 1000000, # MockClock
  176. })
  177. yield self.handler.set_state(
  178. target_user=self.u_apple, auth_user=self.u_apple,
  179. state={"presence": OFFLINE})
  180. self.mock_stop.assert_called_with(self.u_apple)
  181. class PresenceInvitesTestCase(unittest.TestCase):
  182. """ Tests presence management. """
  183. def setUp(self):
  184. self.mock_http_client = Mock(spec=[])
  185. self.mock_http_client.put_json = DeferredMockCallable()
  186. self.mock_federation_resource = MockHttpResource()
  187. hs = HomeServer("test",
  188. clock=MockClock(),
  189. db_pool=None,
  190. datastore=Mock(spec=[
  191. "has_presence_state",
  192. "allow_presence_visible",
  193. "add_presence_list_pending",
  194. "set_presence_list_accepted",
  195. "get_presence_list",
  196. "del_presence_list",
  197. # Bits that Federation needs
  198. "prep_send_transaction",
  199. "delivered_txn",
  200. "get_received_txn_response",
  201. "set_received_txn_response",
  202. ]),
  203. handlers=None,
  204. resource_for_client=Mock(),
  205. resource_for_federation=self.mock_federation_resource,
  206. http_client=self.mock_http_client,
  207. )
  208. hs.handlers = JustPresenceHandlers(hs)
  209. self.datastore = hs.get_datastore()
  210. def has_presence_state(user_localpart):
  211. return defer.succeed(
  212. user_localpart in ("apple", "banana"))
  213. self.datastore.has_presence_state = has_presence_state
  214. def get_received_txn_response(*args):
  215. return defer.succeed(None)
  216. self.datastore.get_received_txn_response = get_received_txn_response
  217. # Some local users to test with
  218. self.u_apple = hs.parse_userid("@apple:test")
  219. self.u_banana = hs.parse_userid("@banana:test")
  220. # ID of a local user that does not exist
  221. self.u_durian = hs.parse_userid("@durian:test")
  222. # A remote user
  223. self.u_cabbage = hs.parse_userid("@cabbage:elsewhere")
  224. self.handler = hs.get_handlers().presence_handler
  225. self.mock_start = Mock()
  226. self.mock_stop = Mock()
  227. self.handler.start_polling_presence = self.mock_start
  228. self.handler.stop_polling_presence = self.mock_stop
  229. @defer.inlineCallbacks
  230. def test_invite_local(self):
  231. # TODO(paul): This test will likely break if/when real auth permissions
  232. # are added; for now the HS will always accept any invite
  233. yield self.handler.send_invite(
  234. observer_user=self.u_apple, observed_user=self.u_banana)
  235. self.datastore.add_presence_list_pending.assert_called_with(
  236. "apple", "@banana:test")
  237. self.datastore.allow_presence_visible.assert_called_with(
  238. "banana", "@apple:test")
  239. self.datastore.set_presence_list_accepted.assert_called_with(
  240. "apple", "@banana:test")
  241. self.mock_start.assert_called_with(
  242. self.u_apple, target_user=self.u_banana)
  243. @defer.inlineCallbacks
  244. def test_invite_local_nonexistant(self):
  245. yield self.handler.send_invite(
  246. observer_user=self.u_apple, observed_user=self.u_durian)
  247. self.datastore.add_presence_list_pending.assert_called_with(
  248. "apple", "@durian:test")
  249. self.datastore.del_presence_list.assert_called_with(
  250. "apple", "@durian:test")
  251. @defer.inlineCallbacks
  252. def test_invite_remote(self):
  253. put_json = self.mock_http_client.put_json
  254. put_json.expect_call_and_return(
  255. call("elsewhere",
  256. path="/_matrix/federation/v1/send/1000000/",
  257. data=_expect_edu("elsewhere", "m.presence_invite",
  258. content={
  259. "observer_user": "@apple:test",
  260. "observed_user": "@cabbage:elsewhere",
  261. }
  262. )
  263. ),
  264. defer.succeed((200, "OK"))
  265. )
  266. yield self.handler.send_invite(
  267. observer_user=self.u_apple, observed_user=self.u_cabbage)
  268. self.datastore.add_presence_list_pending.assert_called_with(
  269. "apple", "@cabbage:elsewhere")
  270. yield put_json.await_calls()
  271. @defer.inlineCallbacks
  272. def test_accept_remote(self):
  273. # TODO(paul): This test will likely break if/when real auth permissions
  274. # are added; for now the HS will always accept any invite
  275. put_json = self.mock_http_client.put_json
  276. put_json.expect_call_and_return(
  277. call("elsewhere",
  278. path="/_matrix/federation/v1/send/1000000/",
  279. data=_expect_edu("elsewhere", "m.presence_accept",
  280. content={
  281. "observer_user": "@cabbage:elsewhere",
  282. "observed_user": "@apple:test",
  283. }
  284. )
  285. ),
  286. defer.succeed((200, "OK"))
  287. )
  288. yield self.mock_federation_resource.trigger("PUT",
  289. "/_matrix/federation/v1/send/1000000/",
  290. _make_edu_json("elsewhere", "m.presence_invite",
  291. content={
  292. "observer_user": "@cabbage:elsewhere",
  293. "observed_user": "@apple:test",
  294. }
  295. )
  296. )
  297. self.datastore.allow_presence_visible.assert_called_with(
  298. "apple", "@cabbage:elsewhere")
  299. yield put_json.await_calls()
  300. @defer.inlineCallbacks
  301. def test_invited_remote_nonexistant(self):
  302. put_json = self.mock_http_client.put_json
  303. put_json.expect_call_and_return(
  304. call("elsewhere",
  305. path="/_matrix/federation/v1/send/1000000/",
  306. data=_expect_edu("elsewhere", "m.presence_deny",
  307. content={
  308. "observer_user": "@cabbage:elsewhere",
  309. "observed_user": "@durian:test",
  310. }
  311. )
  312. ),
  313. defer.succeed((200, "OK"))
  314. )
  315. yield self.mock_federation_resource.trigger("PUT",
  316. "/_matrix/federation/v1/send/1000000/",
  317. _make_edu_json("elsewhere", "m.presence_invite",
  318. content={
  319. "observer_user": "@cabbage:elsewhere",
  320. "observed_user": "@durian:test",
  321. }
  322. )
  323. )
  324. yield put_json.await_calls()
  325. @defer.inlineCallbacks
  326. def test_accepted_remote(self):
  327. yield self.mock_federation_resource.trigger("PUT",
  328. "/_matrix/federation/v1/send/1000000/",
  329. _make_edu_json("elsewhere", "m.presence_accept",
  330. content={
  331. "observer_user": "@apple:test",
  332. "observed_user": "@cabbage:elsewhere",
  333. }
  334. )
  335. )
  336. self.datastore.set_presence_list_accepted.assert_called_with(
  337. "apple", "@cabbage:elsewhere")
  338. self.mock_start.assert_called_with(
  339. self.u_apple, target_user=self.u_cabbage)
  340. @defer.inlineCallbacks
  341. def test_denied_remote(self):
  342. yield self.mock_federation_resource.trigger("PUT",
  343. "/_matrix/federation/v1/send/1000000/",
  344. _make_edu_json("elsewhere", "m.presence_deny",
  345. content={
  346. "observer_user": "@apple:test",
  347. "observed_user": "@eggplant:elsewhere",
  348. }
  349. )
  350. )
  351. self.datastore.del_presence_list.assert_called_with(
  352. "apple", "@eggplant:elsewhere")
  353. @defer.inlineCallbacks
  354. def test_drop_local(self):
  355. yield self.handler.drop(
  356. observer_user=self.u_apple, observed_user=self.u_banana)
  357. self.datastore.del_presence_list.assert_called_with(
  358. "apple", "@banana:test")
  359. self.mock_stop.assert_called_with(
  360. self.u_apple, target_user=self.u_banana)
  361. @defer.inlineCallbacks
  362. def test_drop_remote(self):
  363. yield self.handler.drop(
  364. observer_user=self.u_apple, observed_user=self.u_cabbage)
  365. self.datastore.del_presence_list.assert_called_with(
  366. "apple", "@cabbage:elsewhere")
  367. @defer.inlineCallbacks
  368. def test_get_presence_list(self):
  369. self.datastore.get_presence_list.return_value = defer.succeed(
  370. [{"observed_user_id": "@banana:test"}]
  371. )
  372. presence = yield self.handler.get_presence_list(
  373. observer_user=self.u_apple)
  374. self.assertEquals([
  375. {"observed_user": self.u_banana,
  376. "presence": OFFLINE},
  377. ], presence)
  378. self.datastore.get_presence_list.assert_called_with("apple",
  379. accepted=None
  380. )
  381. self.datastore.get_presence_list.return_value = defer.succeed(
  382. [{"observed_user_id": "@banana:test"}]
  383. )
  384. presence = yield self.handler.get_presence_list(
  385. observer_user=self.u_apple, accepted=True
  386. )
  387. self.assertEquals([
  388. {"observed_user": self.u_banana,
  389. "presence": OFFLINE},
  390. ], presence)
  391. self.datastore.get_presence_list.assert_called_with("apple",
  392. accepted=True)
  393. class PresencePushTestCase(unittest.TestCase):
  394. """ Tests steady-state presence status updates.
  395. They assert that presence state update messages are pushed around the place
  396. when users change state, presuming that the watches are all established.
  397. These tests are MASSIVELY fragile currently as they poke internals of the
  398. presence handler; namely the _local_pushmap and _remote_recvmap.
  399. BE WARNED...
  400. """
  401. def setUp(self):
  402. self.clock = MockClock()
  403. self.mock_http_client = Mock(spec=[])
  404. self.mock_http_client.put_json = DeferredMockCallable()
  405. self.mock_federation_resource = MockHttpResource()
  406. hs = HomeServer("test",
  407. clock=self.clock,
  408. db_pool=None,
  409. datastore=Mock(spec=[
  410. "set_presence_state",
  411. "get_joined_hosts_for_room",
  412. # Bits that Federation needs
  413. "prep_send_transaction",
  414. "delivered_txn",
  415. "get_received_txn_response",
  416. "set_received_txn_response",
  417. ]),
  418. handlers=None,
  419. resource_for_client=Mock(),
  420. resource_for_federation=self.mock_federation_resource,
  421. http_client=self.mock_http_client,
  422. )
  423. hs.handlers = JustPresenceHandlers(hs)
  424. self.datastore = hs.get_datastore()
  425. def get_received_txn_response(*args):
  426. return defer.succeed(None)
  427. self.datastore.get_received_txn_response = get_received_txn_response
  428. self.handler = hs.get_handlers().presence_handler
  429. self.event_source = hs.get_event_sources().sources["presence"]
  430. # Mock the RoomMemberHandler
  431. hs.handlers.room_member_handler = Mock(spec=[
  432. "get_rooms_for_user",
  433. "get_room_members",
  434. ])
  435. self.room_member_handler = hs.handlers.room_member_handler
  436. self.room_members = []
  437. def get_rooms_for_user(user):
  438. if user in self.room_members:
  439. return defer.succeed(["a-room"])
  440. else:
  441. return defer.succeed([])
  442. self.room_member_handler.get_rooms_for_user = get_rooms_for_user
  443. def get_room_members(room_id):
  444. if room_id == "a-room":
  445. return defer.succeed(self.room_members)
  446. else:
  447. return defer.succeed([])
  448. self.room_member_handler.get_room_members = get_room_members
  449. def get_room_hosts(room_id):
  450. if room_id == "a-room":
  451. hosts = set([u.domain for u in self.room_members])
  452. return defer.succeed(hosts)
  453. else:
  454. return defer.succeed([])
  455. self.datastore.get_joined_hosts_for_room = get_room_hosts
  456. def user_rooms_intersect(userlist):
  457. room_member_ids = map(lambda u: u.to_string(), self.room_members)
  458. shared = all(map(lambda i: i in room_member_ids, userlist))
  459. return defer.succeed(shared)
  460. self.datastore.user_rooms_intersect = user_rooms_intersect
  461. @defer.inlineCallbacks
  462. def fetch_room_distributions_into(room_id, localusers=None,
  463. remotedomains=None, ignore_user=None):
  464. members = yield get_room_members(room_id)
  465. for member in members:
  466. if ignore_user is not None and member == ignore_user:
  467. continue
  468. if member.is_mine:
  469. if localusers is not None:
  470. localusers.add(member)
  471. else:
  472. if remotedomains is not None:
  473. remotedomains.add(member.domain)
  474. self.room_member_handler.fetch_room_distributions_into = (
  475. fetch_room_distributions_into)
  476. def get_presence_list(user_localpart, accepted=None):
  477. if user_localpart == "apple":
  478. return defer.succeed([
  479. {"observed_user_id": "@banana:test"},
  480. {"observed_user_id": "@clementine:test"},
  481. ])
  482. else:
  483. return defer.succeed([])
  484. self.datastore.get_presence_list = get_presence_list
  485. def is_presence_visible(observer_userid, observed_localpart):
  486. if (observed_localpart == "clementine" and
  487. observer_userid == "@banana:test"):
  488. return False
  489. return False
  490. self.datastore.is_presence_visible = is_presence_visible
  491. self.distributor = hs.get_distributor()
  492. self.distributor.declare("user_joined_room")
  493. # Some local users to test with
  494. self.u_apple = hs.parse_userid("@apple:test")
  495. self.u_banana = hs.parse_userid("@banana:test")
  496. self.u_clementine = hs.parse_userid("@clementine:test")
  497. self.u_durian = hs.parse_userid("@durian:test")
  498. self.u_elderberry = hs.parse_userid("@elderberry:test")
  499. # Remote user
  500. self.u_onion = hs.parse_userid("@onion:farm")
  501. self.u_potato = hs.parse_userid("@potato:remote")
  502. @defer.inlineCallbacks
  503. def test_push_local(self):
  504. self.room_members = [self.u_apple, self.u_elderberry]
  505. self.datastore.set_presence_state.return_value = defer.succeed(
  506. {"presence": ONLINE}
  507. )
  508. # TODO(paul): Gut-wrenching
  509. self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
  510. self.handler._user_cachemap[self.u_apple].update(
  511. {"presence": OFFLINE}, serial=0
  512. )
  513. apple_set = self.handler._local_pushmap.setdefault("apple", set())
  514. apple_set.add(self.u_banana)
  515. apple_set.add(self.u_clementine)
  516. self.assertEquals(self.event_source.get_current_key(), 0)
  517. yield self.handler.set_state(self.u_apple, self.u_apple,
  518. {"presence": ONLINE}
  519. )
  520. # Apple sees self-reflection
  521. (events, _) = yield self.event_source.get_new_events_for_user(
  522. self.u_apple, 0, None
  523. )
  524. self.assertEquals(self.event_source.get_current_key(), 1)
  525. self.assertEquals(events,
  526. [
  527. {"type": "m.presence",
  528. "content": {
  529. "user_id": "@apple:test",
  530. "presence": ONLINE,
  531. "last_active_ago": 0,
  532. }},
  533. ],
  534. msg="Presence event should be visible to self-reflection"
  535. )
  536. # Banana sees it because of presence subscription
  537. (events, _) = yield self.event_source.get_new_events_for_user(
  538. self.u_banana, 0, None
  539. )
  540. self.assertEquals(self.event_source.get_current_key(), 1)
  541. self.assertEquals(events,
  542. [
  543. {"type": "m.presence",
  544. "content": {
  545. "user_id": "@apple:test",
  546. "presence": ONLINE,
  547. "last_active_ago": 0,
  548. }},
  549. ],
  550. msg="Presence event should be visible to explicit subscribers"
  551. )
  552. # Elderberry sees it because of same room
  553. (events, _) = yield self.event_source.get_new_events_for_user(
  554. self.u_elderberry, 0, None
  555. )
  556. self.assertEquals(self.event_source.get_current_key(), 1)
  557. self.assertEquals(events,
  558. [
  559. {"type": "m.presence",
  560. "content": {
  561. "user_id": "@apple:test",
  562. "presence": ONLINE,
  563. "last_active_ago": 0,
  564. }},
  565. ],
  566. msg="Presence event should be visible to other room members"
  567. )
  568. # Durian is not in the room, should not see this event
  569. (events, _) = yield self.event_source.get_new_events_for_user(
  570. self.u_durian, 0, None
  571. )
  572. self.assertEquals(self.event_source.get_current_key(), 1)
  573. self.assertEquals(events, [],
  574. msg="Presence event should not be visible to others"
  575. )
  576. presence = yield self.handler.get_presence_list(
  577. observer_user=self.u_apple, accepted=True)
  578. self.assertEquals(
  579. [
  580. {"observed_user": self.u_banana,
  581. "presence": OFFLINE},
  582. {"observed_user": self.u_clementine,
  583. "presence": OFFLINE},
  584. ],
  585. presence
  586. )
  587. # TODO(paul): Gut-wrenching
  588. banana_set = self.handler._local_pushmap.setdefault("banana", set())
  589. banana_set.add(self.u_apple)
  590. yield self.handler.set_state(self.u_banana, self.u_banana,
  591. {"presence": ONLINE}
  592. )
  593. self.clock.advance_time(2)
  594. presence = yield self.handler.get_presence_list(
  595. observer_user=self.u_apple, accepted=True)
  596. self.assertEquals([
  597. {"observed_user": self.u_banana,
  598. "presence": ONLINE,
  599. "last_active_ago": 2000},
  600. {"observed_user": self.u_clementine,
  601. "presence": OFFLINE},
  602. ], presence)
  603. (events, _) = yield self.event_source.get_new_events_for_user(
  604. self.u_apple, 1, None
  605. )
  606. self.assertEquals(self.event_source.get_current_key(), 2)
  607. self.assertEquals(events,
  608. [
  609. {"type": "m.presence",
  610. "content": {
  611. "user_id": "@banana:test",
  612. "presence": ONLINE,
  613. "last_active_ago": 2000
  614. }},
  615. ]
  616. )
  617. @defer.inlineCallbacks
  618. def test_push_remote(self):
  619. put_json = self.mock_http_client.put_json
  620. put_json.expect_call_and_return(
  621. call("farm",
  622. path=ANY, # Can't guarantee which txn ID will be which
  623. data=_expect_edu("farm", "m.presence",
  624. content={
  625. "push": [
  626. {"user_id": "@apple:test",
  627. "presence": u"online",
  628. "last_active_ago": 0},
  629. ],
  630. }
  631. )
  632. ),
  633. defer.succeed((200, "OK"))
  634. )
  635. put_json.expect_call_and_return(
  636. call("remote",
  637. path=ANY, # Can't guarantee which txn ID will be which
  638. data=_expect_edu("remote", "m.presence",
  639. content={
  640. "push": [
  641. {"user_id": "@apple:test",
  642. "presence": u"online",
  643. "last_active_ago": 0},
  644. ],
  645. }
  646. )
  647. ),
  648. defer.succeed((200, "OK"))
  649. )
  650. self.room_members = [self.u_apple, self.u_onion]
  651. self.datastore.set_presence_state.return_value = defer.succeed(
  652. {"presence": ONLINE}
  653. )
  654. # TODO(paul): Gut-wrenching
  655. self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
  656. self.handler._user_cachemap[self.u_apple].update(
  657. {"presence": OFFLINE}, serial=0
  658. )
  659. apple_set = self.handler._remote_sendmap.setdefault("apple", set())
  660. apple_set.add(self.u_potato.domain)
  661. yield self.handler.set_state(self.u_apple, self.u_apple,
  662. {"presence": ONLINE}
  663. )
  664. yield put_json.await_calls()
  665. @defer.inlineCallbacks
  666. def test_recv_remote(self):
  667. # TODO(paul): Gut-wrenching
  668. potato_set = self.handler._remote_recvmap.setdefault(self.u_potato,
  669. set())
  670. potato_set.add(self.u_apple)
  671. self.room_members = [self.u_banana, self.u_potato]
  672. self.assertEquals(self.event_source.get_current_key(), 0)
  673. yield self.mock_federation_resource.trigger("PUT",
  674. "/_matrix/federation/v1/send/1000000/",
  675. _make_edu_json("elsewhere", "m.presence",
  676. content={
  677. "push": [
  678. {"user_id": "@potato:remote",
  679. "presence": "online",
  680. "last_active_ago": 1000},
  681. ],
  682. }
  683. )
  684. )
  685. (events, _) = yield self.event_source.get_new_events_for_user(
  686. self.u_apple, 0, None
  687. )
  688. self.assertEquals(self.event_source.get_current_key(), 1)
  689. self.assertEquals(events,
  690. [
  691. {"type": "m.presence",
  692. "content": {
  693. "user_id": "@potato:remote",
  694. "presence": ONLINE,
  695. "last_active_ago": 1000,
  696. }}
  697. ]
  698. )
  699. self.clock.advance_time(2)
  700. state = yield self.handler.get_state(self.u_potato, self.u_apple)
  701. self.assertEquals(
  702. {"presence": ONLINE, "last_active_ago": 3000},
  703. state
  704. )
  705. @defer.inlineCallbacks
  706. def test_join_room_local(self):
  707. self.room_members = [self.u_apple, self.u_banana]
  708. self.assertEquals(self.event_source.get_current_key(), 0)
  709. # TODO(paul): Gut-wrenching
  710. self.handler._user_cachemap[self.u_clementine] = UserPresenceCache()
  711. self.handler._user_cachemap[self.u_clementine].update(
  712. {
  713. "presence": PresenceState.ONLINE,
  714. "last_active": self.clock.time_msec(),
  715. }, self.u_clementine
  716. )
  717. yield self.distributor.fire("user_joined_room", self.u_clementine,
  718. "a-room"
  719. )
  720. self.room_members.append(self.u_clementine)
  721. (events, _) = yield self.event_source.get_new_events_for_user(
  722. self.u_apple, 0, None
  723. )
  724. self.assertEquals(self.event_source.get_current_key(), 1)
  725. self.assertEquals(events,
  726. [
  727. {"type": "m.presence",
  728. "content": {
  729. "user_id": "@clementine:test",
  730. "presence": ONLINE,
  731. "last_active_ago": 0,
  732. }}
  733. ]
  734. )
  735. @defer.inlineCallbacks
  736. def test_join_room_remote(self):
  737. ## Sending local user state to a newly-joined remote user
  738. put_json = self.mock_http_client.put_json
  739. put_json.expect_call_and_return(
  740. call("remote",
  741. path=ANY, # Can't guarantee which txn ID will be which
  742. data=_expect_edu("remote", "m.presence",
  743. content={
  744. "push": [
  745. {"user_id": "@apple:test",
  746. "presence": "online"},
  747. ],
  748. }
  749. ),
  750. ),
  751. defer.succeed((200, "OK"))
  752. )
  753. put_json.expect_call_and_return(
  754. call("remote",
  755. path=ANY, # Can't guarantee which txn ID will be which
  756. data=_expect_edu("remote", "m.presence",
  757. content={
  758. "push": [
  759. {"user_id": "@banana:test",
  760. "presence": "offline"},
  761. ],
  762. }
  763. ),
  764. ),
  765. defer.succeed((200, "OK"))
  766. )
  767. # TODO(paul): Gut-wrenching
  768. self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
  769. self.handler._user_cachemap[self.u_apple].update(
  770. {"presence": PresenceState.ONLINE}, self.u_apple)
  771. self.room_members = [self.u_apple, self.u_banana]
  772. yield self.distributor.fire("user_joined_room", self.u_potato,
  773. "a-room"
  774. )
  775. yield put_json.await_calls()
  776. ## Sending newly-joined local user state to remote users
  777. put_json.expect_call_and_return(
  778. call("remote",
  779. path="/_matrix/federation/v1/send/1000002/",
  780. data=_expect_edu("remote", "m.presence",
  781. content={
  782. "push": [
  783. {"user_id": "@clementine:test",
  784. "presence": "online"},
  785. ],
  786. }
  787. ),
  788. ),
  789. defer.succeed((200, "OK"))
  790. )
  791. self.handler._user_cachemap[self.u_clementine] = UserPresenceCache()
  792. self.handler._user_cachemap[self.u_clementine].update(
  793. {"presence": ONLINE}, self.u_clementine)
  794. self.room_members.append(self.u_potato)
  795. yield self.distributor.fire("user_joined_room", self.u_clementine,
  796. "a-room"
  797. )
  798. put_json.await_calls()
  799. class PresencePollingTestCase(unittest.TestCase):
  800. """ Tests presence status polling. """
  801. # For this test, we have three local users; apple is watching and is
  802. # watched by the other two, but the others don't watch each other.
  803. # Additionally clementine is watching a remote user.
  804. PRESENCE_LIST = {
  805. 'apple': [ "@banana:test", "@clementine:test" ],
  806. 'banana': [ "@apple:test" ],
  807. 'clementine': [ "@apple:test", "@potato:remote" ],
  808. 'fig': [ "@potato:remote" ],
  809. }
  810. def setUp(self):
  811. self.mock_http_client = Mock(spec=[])
  812. self.mock_http_client.put_json = DeferredMockCallable()
  813. self.mock_federation_resource = MockHttpResource()
  814. hs = HomeServer("test",
  815. clock=MockClock(),
  816. db_pool=None,
  817. datastore=Mock(spec=[
  818. # Bits that Federation needs
  819. "prep_send_transaction",
  820. "delivered_txn",
  821. "get_received_txn_response",
  822. "set_received_txn_response",
  823. ]),
  824. handlers=None,
  825. resource_for_client=Mock(),
  826. resource_for_federation=self.mock_federation_resource,
  827. http_client=self.mock_http_client,
  828. )
  829. hs.handlers = JustPresenceHandlers(hs)
  830. self.datastore = hs.get_datastore()
  831. def get_received_txn_response(*args):
  832. return defer.succeed(None)
  833. self.datastore.get_received_txn_response = get_received_txn_response
  834. self.mock_update_client = Mock()
  835. def update(*args,**kwargs):
  836. # print "mock_update_client: Args=%s, kwargs=%s" %(args, kwargs,)
  837. return defer.succeed(None)
  838. self.mock_update_client.side_effect = update
  839. self.handler = hs.get_handlers().presence_handler
  840. self.handler.push_update_to_clients = self.mock_update_client
  841. hs.handlers.room_member_handler = Mock(spec=[
  842. "get_rooms_for_user",
  843. ])
  844. # For this test no users are ever in rooms
  845. def get_rooms_for_user(user):
  846. return defer.succeed([])
  847. hs.handlers.room_member_handler.get_rooms_for_user = get_rooms_for_user
  848. # Mocked database state
  849. # Local users always start offline
  850. self.current_user_state = {
  851. "apple": OFFLINE,
  852. "banana": OFFLINE,
  853. "clementine": OFFLINE,
  854. "fig": OFFLINE,
  855. }
  856. def get_presence_state(user_localpart):
  857. return defer.succeed(
  858. {"presence": self.current_user_state[user_localpart],
  859. "status_msg": None,
  860. "last_active": 500000}
  861. )
  862. self.datastore.get_presence_state = get_presence_state
  863. def set_presence_state(user_localpart, new_state):
  864. was = self.current_user_state[user_localpart]
  865. self.current_user_state[user_localpart] = new_state["presence"]
  866. return defer.succeed({"presence": was})
  867. self.datastore.set_presence_state = set_presence_state
  868. def get_presence_list(user_localpart, accepted):
  869. return defer.succeed([
  870. {"observed_user_id": u} for u in
  871. self.PRESENCE_LIST[user_localpart]])
  872. self.datastore.get_presence_list = get_presence_list
  873. def is_presence_visible(observed_localpart, observer_userid):
  874. return True
  875. self.datastore.is_presence_visible = is_presence_visible
  876. # Local users
  877. self.u_apple = hs.parse_userid("@apple:test")
  878. self.u_banana = hs.parse_userid("@banana:test")
  879. self.u_clementine = hs.parse_userid("@clementine:test")
  880. self.u_fig = hs.parse_userid("@fig:test")
  881. # Remote users
  882. self.u_potato = hs.parse_userid("@potato:remote")
  883. @defer.inlineCallbacks
  884. def test_push_local(self):
  885. # apple goes online
  886. yield self.handler.set_state(
  887. target_user=self.u_apple, auth_user=self.u_apple,
  888. state={"presence": ONLINE}
  889. )
  890. # apple should see both banana and clementine currently offline
  891. self.mock_update_client.assert_has_calls([
  892. call(users_to_push=[self.u_apple],
  893. observed_user=self.u_banana,
  894. statuscache=ANY),
  895. call(users_to_push=[self.u_apple],
  896. observed_user=self.u_clementine,
  897. statuscache=ANY),
  898. ], any_order=True)
  899. # Gut-wrenching tests
  900. self.assertTrue("banana" in self.handler._local_pushmap)
  901. self.assertTrue(self.u_apple in self.handler._local_pushmap["banana"])
  902. self.assertTrue("clementine" in self.handler._local_pushmap)
  903. self.assertTrue(self.u_apple in self.handler._local_pushmap["clementine"])
  904. self.mock_update_client.reset_mock()
  905. # banana goes online
  906. yield self.handler.set_state(
  907. target_user=self.u_banana, auth_user=self.u_banana,
  908. state={"presence": ONLINE}
  909. )
  910. # apple and banana should now both see each other online
  911. self.mock_update_client.assert_has_calls([
  912. call(users_to_push=set([self.u_apple]),
  913. observed_user=self.u_banana,
  914. room_ids=[],
  915. statuscache=ANY),
  916. call(users_to_push=[self.u_banana],
  917. observed_user=self.u_apple,
  918. statuscache=ANY),
  919. ], any_order=True)
  920. self.assertTrue("apple" in self.handler._local_pushmap)
  921. self.assertTrue(self.u_banana in self.handler._local_pushmap["apple"])
  922. self.mock_update_client.reset_mock()
  923. # apple goes offline
  924. yield self.handler.set_state(
  925. target_user=self.u_apple, auth_user=self.u_apple,
  926. state={"presence": OFFLINE}
  927. )
  928. # banana should now be told apple is offline
  929. self.mock_update_client.assert_has_calls([
  930. call(users_to_push=set([self.u_banana, self.u_apple]),
  931. observed_user=self.u_apple,
  932. room_ids=[],
  933. statuscache=ANY),
  934. ], any_order=True)
  935. self.assertFalse("banana" in self.handler._local_pushmap)
  936. self.assertFalse("clementine" in self.handler._local_pushmap)
  937. @defer.inlineCallbacks
  938. def test_remote_poll_send(self):
  939. put_json = self.mock_http_client.put_json
  940. put_json.expect_call_and_return(
  941. call("remote",
  942. path=ANY,
  943. data=_expect_edu("remote", "m.presence",
  944. content={
  945. "poll": [ "@potato:remote" ],
  946. },
  947. ),
  948. ),
  949. defer.succeed((200, "OK"))
  950. )
  951. put_json.expect_call_and_return(
  952. call("remote",
  953. path=ANY,
  954. data=_expect_edu("remote", "m.presence",
  955. content={
  956. "push": [ {"user_id": "@clementine:test" }],
  957. },
  958. ),
  959. ),
  960. defer.succeed((200, "OK"))
  961. )
  962. # clementine goes online
  963. yield self.handler.set_state(
  964. target_user=self.u_clementine, auth_user=self.u_clementine,
  965. state={"presence": ONLINE}
  966. )
  967. yield put_json.await_calls()
  968. # Gut-wrenching tests
  969. self.assertTrue(self.u_potato in self.handler._remote_recvmap,
  970. msg="expected potato to be in _remote_recvmap"
  971. )
  972. self.assertTrue(self.u_clementine in
  973. self.handler._remote_recvmap[self.u_potato])
  974. put_json.expect_call_and_return(
  975. call("remote",
  976. path=ANY,
  977. data=_expect_edu("remote", "m.presence",
  978. content={
  979. "push": [ {"user_id": "@fig:test" }],
  980. },
  981. ),
  982. ),
  983. defer.succeed((200, "OK"))
  984. )
  985. # fig goes online; shouldn't send a second poll
  986. yield self.handler.set_state(
  987. target_user=self.u_fig, auth_user=self.u_fig,
  988. state={"presence": ONLINE}
  989. )
  990. # reactor.iterate(delay=0)
  991. yield put_json.await_calls()
  992. # fig goes offline
  993. yield self.handler.set_state(
  994. target_user=self.u_fig, auth_user=self.u_fig,
  995. state={"presence": OFFLINE}
  996. )
  997. reactor.iterate(delay=0)
  998. put_json.assert_had_no_calls()
  999. put_json.expect_call_and_return(
  1000. call("remote",
  1001. path=ANY,
  1002. data=_expect_edu("remote", "m.presence",
  1003. content={
  1004. "unpoll": [ "@potato:remote" ],
  1005. },
  1006. ),
  1007. ),
  1008. defer.succeed((200, "OK"))
  1009. )
  1010. # clementine goes offline
  1011. yield self.handler.set_state(
  1012. target_user=self.u_clementine, auth_user=self.u_clementine,
  1013. state={"presence": OFFLINE}
  1014. )
  1015. yield put_json.await_calls()
  1016. self.assertFalse(self.u_potato in self.handler._remote_recvmap,
  1017. msg="expected potato not to be in _remote_recvmap"
  1018. )
  1019. @defer.inlineCallbacks
  1020. def test_remote_poll_receive(self):
  1021. put_json = self.mock_http_client.put_json
  1022. put_json.expect_call_and_return(
  1023. call("remote",
  1024. path="/_matrix/federation/v1/send/1000000/",
  1025. data=_expect_edu("remote", "m.presence",
  1026. content={
  1027. "push": [
  1028. {"user_id": "@banana:test",
  1029. "presence": "offline",
  1030. "status_msg": None,
  1031. "last_active_ago": 500000},
  1032. ],
  1033. },
  1034. ),
  1035. ),
  1036. defer.succeed((200, "OK"))
  1037. )
  1038. yield self.mock_federation_resource.trigger("PUT",
  1039. "/_matrix/federation/v1/send/1000000/",
  1040. _make_edu_json("remote", "m.presence",
  1041. content={
  1042. "poll": [ "@banana:test" ],
  1043. },
  1044. )
  1045. )
  1046. yield put_json.await_calls()
  1047. # Gut-wrenching tests
  1048. self.assertTrue(self.u_banana in self.handler._remote_sendmap)
  1049. yield self.mock_federation_resource.trigger("PUT",
  1050. "/_matrix/federation/v1/send/1000001/",
  1051. _make_edu_json("remote", "m.presence",
  1052. content={
  1053. "unpoll": [ "@banana:test" ],
  1054. }
  1055. )
  1056. )
  1057. # Gut-wrenching tests
  1058. self.assertFalse(self.u_banana in self.handler._remote_sendmap)