test_presence.py 42 KB

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