test_presence.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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. """Tests REST events for /presence paths."""
  16. from tests import unittest
  17. from twisted.internet import defer
  18. from mock import Mock
  19. from ....utils import MockHttpResource, setup_test_homeserver
  20. from synapse.api.constants import PresenceState
  21. from synapse.handlers.presence import PresenceHandler
  22. from synapse.rest.client.v1 import presence
  23. from synapse.rest.client.v1 import events
  24. from synapse.types import UserID
  25. from synapse.util.async import run_on_reactor
  26. from collections import namedtuple
  27. OFFLINE = PresenceState.OFFLINE
  28. UNAVAILABLE = PresenceState.UNAVAILABLE
  29. ONLINE = PresenceState.ONLINE
  30. myid = "@apple:test"
  31. PATH_PREFIX = "/_matrix/client/api/v1"
  32. class NullSource(object):
  33. """This event source never yields any events and its token remains at
  34. zero. It may be useful for unit-testing."""
  35. def __init__(self, hs):
  36. pass
  37. def get_new_events_for_user(self, user, from_key, limit):
  38. return defer.succeed(([], from_key))
  39. def get_current_key(self, direction='f'):
  40. return defer.succeed(0)
  41. def get_pagination_rows(self, user, pagination_config, key):
  42. return defer.succeed(([], pagination_config.from_key))
  43. class JustPresenceHandlers(object):
  44. def __init__(self, hs):
  45. self.presence_handler = PresenceHandler(hs)
  46. class PresenceStateTestCase(unittest.TestCase):
  47. @defer.inlineCallbacks
  48. def setUp(self):
  49. self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
  50. hs = yield setup_test_homeserver(
  51. datastore=Mock(spec=[
  52. "get_presence_state",
  53. "set_presence_state",
  54. "insert_client_ip",
  55. ]),
  56. http_client=None,
  57. resource_for_client=self.mock_resource,
  58. resource_for_federation=self.mock_resource,
  59. )
  60. hs.handlers = JustPresenceHandlers(hs)
  61. self.datastore = hs.get_datastore()
  62. self.datastore.get_app_service_by_token = Mock(return_value=None)
  63. def get_presence_list(*a, **kw):
  64. return defer.succeed([])
  65. self.datastore.get_presence_list = get_presence_list
  66. def _get_user_by_access_token(token=None):
  67. return {
  68. "user": UserID.from_string(myid),
  69. "token_id": 1,
  70. }
  71. hs.get_v1auth()._get_user_by_access_token = _get_user_by_access_token
  72. room_member_handler = hs.handlers.room_member_handler = Mock(
  73. spec=[
  74. "get_joined_rooms_for_user",
  75. ]
  76. )
  77. def get_rooms_for_user(user):
  78. return defer.succeed([])
  79. room_member_handler.get_joined_rooms_for_user = get_rooms_for_user
  80. presence.register_servlets(hs, self.mock_resource)
  81. self.u_apple = UserID.from_string(myid)
  82. @defer.inlineCallbacks
  83. def test_get_my_status(self):
  84. mocked_get = self.datastore.get_presence_state
  85. mocked_get.return_value = defer.succeed(
  86. {"state": ONLINE, "status_msg": "Available"}
  87. )
  88. (code, response) = yield self.mock_resource.trigger("GET",
  89. "/presence/%s/status" % (myid), None)
  90. self.assertEquals(200, code)
  91. self.assertEquals(
  92. {"presence": ONLINE, "status_msg": "Available"},
  93. response
  94. )
  95. mocked_get.assert_called_with("apple")
  96. @defer.inlineCallbacks
  97. def test_set_my_status(self):
  98. mocked_set = self.datastore.set_presence_state
  99. mocked_set.return_value = defer.succeed({"state": OFFLINE})
  100. (code, response) = yield self.mock_resource.trigger("PUT",
  101. "/presence/%s/status" % (myid),
  102. '{"presence": "unavailable", "status_msg": "Away"}')
  103. self.assertEquals(200, code)
  104. mocked_set.assert_called_with("apple",
  105. {"state": UNAVAILABLE, "status_msg": "Away"}
  106. )
  107. class PresenceListTestCase(unittest.TestCase):
  108. @defer.inlineCallbacks
  109. def setUp(self):
  110. self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
  111. hs = yield setup_test_homeserver(
  112. datastore=Mock(spec=[
  113. "has_presence_state",
  114. "get_presence_state",
  115. "allow_presence_visible",
  116. "is_presence_visible",
  117. "add_presence_list_pending",
  118. "set_presence_list_accepted",
  119. "del_presence_list",
  120. "get_presence_list",
  121. "insert_client_ip",
  122. ]),
  123. http_client=None,
  124. resource_for_client=self.mock_resource,
  125. resource_for_federation=self.mock_resource,
  126. )
  127. hs.handlers = JustPresenceHandlers(hs)
  128. self.datastore = hs.get_datastore()
  129. self.datastore.get_app_service_by_token = Mock(return_value=None)
  130. def has_presence_state(user_localpart):
  131. return defer.succeed(
  132. user_localpart in ("apple", "banana",)
  133. )
  134. self.datastore.has_presence_state = has_presence_state
  135. def _get_user_by_access_token(token=None):
  136. return {
  137. "user": UserID.from_string(myid),
  138. "token_id": 1,
  139. }
  140. hs.handlers.room_member_handler = Mock(
  141. spec=[
  142. "get_joined_rooms_for_user",
  143. ]
  144. )
  145. hs.get_v1auth()._get_user_by_access_token = _get_user_by_access_token
  146. presence.register_servlets(hs, self.mock_resource)
  147. self.u_apple = UserID.from_string("@apple:test")
  148. self.u_banana = UserID.from_string("@banana:test")
  149. @defer.inlineCallbacks
  150. def test_get_my_list(self):
  151. self.datastore.get_presence_list.return_value = defer.succeed(
  152. [{"observed_user_id": "@banana:test", "accepted": True}],
  153. )
  154. (code, response) = yield self.mock_resource.trigger("GET",
  155. "/presence/list/%s" % (myid), None)
  156. self.assertEquals(200, code)
  157. self.assertEquals([
  158. {"user_id": "@banana:test", "presence": OFFLINE, "accepted": True},
  159. ], response)
  160. self.datastore.get_presence_list.assert_called_with(
  161. "apple", accepted=True
  162. )
  163. @defer.inlineCallbacks
  164. def test_invite(self):
  165. self.datastore.add_presence_list_pending.return_value = (
  166. defer.succeed(())
  167. )
  168. self.datastore.is_presence_visible.return_value = defer.succeed(
  169. True
  170. )
  171. (code, response) = yield self.mock_resource.trigger("POST",
  172. "/presence/list/%s" % (myid),
  173. """{"invite": ["@banana:test"]}"""
  174. )
  175. self.assertEquals(200, code)
  176. self.datastore.add_presence_list_pending.assert_called_with(
  177. "apple", "@banana:test"
  178. )
  179. self.datastore.set_presence_list_accepted.assert_called_with(
  180. "apple", "@banana:test"
  181. )
  182. @defer.inlineCallbacks
  183. def test_drop(self):
  184. self.datastore.del_presence_list.return_value = (
  185. defer.succeed(())
  186. )
  187. (code, response) = yield self.mock_resource.trigger("POST",
  188. "/presence/list/%s" % (myid),
  189. """{"drop": ["@banana:test"]}"""
  190. )
  191. self.assertEquals(200, code)
  192. self.datastore.del_presence_list.assert_called_with(
  193. "apple", "@banana:test"
  194. )
  195. class PresenceEventStreamTestCase(unittest.TestCase):
  196. @defer.inlineCallbacks
  197. def setUp(self):
  198. self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
  199. # HIDEOUS HACKERY
  200. # TODO(paul): This should be injected in via the HomeServer DI system
  201. from synapse.streams.events import (
  202. PresenceEventSource, EventSources
  203. )
  204. old_SOURCE_TYPES = EventSources.SOURCE_TYPES
  205. def tearDown():
  206. EventSources.SOURCE_TYPES = old_SOURCE_TYPES
  207. self.tearDown = tearDown
  208. EventSources.SOURCE_TYPES = {
  209. k: NullSource for k in old_SOURCE_TYPES.keys()
  210. }
  211. EventSources.SOURCE_TYPES["presence"] = PresenceEventSource
  212. hs = yield setup_test_homeserver(
  213. http_client=None,
  214. resource_for_client=self.mock_resource,
  215. resource_for_federation=self.mock_resource,
  216. datastore=Mock(spec=[
  217. "set_presence_state",
  218. "get_presence_list",
  219. "get_rooms_for_user",
  220. ]),
  221. clock=Mock(spec=[
  222. "call_later",
  223. "cancel_call_later",
  224. "time_msec",
  225. "looping_call",
  226. ]),
  227. )
  228. hs.get_clock().time_msec.return_value = 1000000
  229. def _get_user_by_req(req=None):
  230. return (UserID.from_string(myid), "")
  231. hs.get_v1auth().get_user_by_req = _get_user_by_req
  232. presence.register_servlets(hs, self.mock_resource)
  233. events.register_servlets(hs, self.mock_resource)
  234. hs.handlers.room_member_handler = Mock(spec=[])
  235. self.room_members = []
  236. def get_rooms_for_user(user):
  237. if user in self.room_members:
  238. return ["a-room"]
  239. else:
  240. return []
  241. hs.handlers.room_member_handler.get_joined_rooms_for_user = get_rooms_for_user
  242. hs.handlers.room_member_handler.get_room_members = (
  243. lambda r: self.room_members if r == "a-room" else []
  244. )
  245. self.mock_datastore = hs.get_datastore()
  246. self.mock_datastore.get_app_service_by_token = Mock(return_value=None)
  247. self.mock_datastore.get_app_service_by_user_id = Mock(
  248. return_value=defer.succeed(None)
  249. )
  250. self.mock_datastore.get_rooms_for_user = (
  251. lambda u: [
  252. namedtuple("Room", "room_id")(r)
  253. for r in get_rooms_for_user(UserID.from_string(u))
  254. ]
  255. )
  256. def get_profile_displayname(user_id):
  257. return defer.succeed("Frank")
  258. self.mock_datastore.get_profile_displayname = get_profile_displayname
  259. def get_profile_avatar_url(user_id):
  260. return defer.succeed(None)
  261. self.mock_datastore.get_profile_avatar_url = get_profile_avatar_url
  262. def user_rooms_intersect(user_list):
  263. room_member_ids = map(lambda u: u.to_string(), self.room_members)
  264. shared = all(map(lambda i: i in room_member_ids, user_list))
  265. return defer.succeed(shared)
  266. self.mock_datastore.user_rooms_intersect = user_rooms_intersect
  267. def get_joined_hosts_for_room(room_id):
  268. return []
  269. self.mock_datastore.get_joined_hosts_for_room = get_joined_hosts_for_room
  270. self.presence = hs.get_handlers().presence_handler
  271. self.u_apple = UserID.from_string("@apple:test")
  272. self.u_banana = UserID.from_string("@banana:test")
  273. @defer.inlineCallbacks
  274. def test_shortpoll(self):
  275. self.room_members = [self.u_apple, self.u_banana]
  276. self.mock_datastore.set_presence_state.return_value = defer.succeed(
  277. {"state": ONLINE}
  278. )
  279. self.mock_datastore.get_presence_list.return_value = defer.succeed(
  280. []
  281. )
  282. (code, response) = yield self.mock_resource.trigger("GET",
  283. "/events?timeout=0", None)
  284. self.assertEquals(200, code)
  285. # We've forced there to be only one data stream so the tokens will
  286. # all be ours
  287. # I'll already get my own presence state change
  288. self.assertEquals({"start": "0_1_0_0", "end": "0_1_0_0", "chunk": []},
  289. response
  290. )
  291. self.mock_datastore.set_presence_state.return_value = defer.succeed(
  292. {"state": ONLINE}
  293. )
  294. self.mock_datastore.get_presence_list.return_value = defer.succeed([])
  295. yield self.presence.set_state(self.u_banana, self.u_banana,
  296. state={"presence": ONLINE}
  297. )
  298. yield run_on_reactor()
  299. (code, response) = yield self.mock_resource.trigger("GET",
  300. "/events?from=s0_1_0&timeout=0", None)
  301. self.assertEquals(200, code)
  302. self.assertEquals({"start": "s0_1_0_0", "end": "s0_2_0_0", "chunk": [
  303. {"type": "m.presence",
  304. "content": {
  305. "user_id": "@banana:test",
  306. "presence": ONLINE,
  307. "displayname": "Frank",
  308. "last_active_ago": 0,
  309. }},
  310. ]}, response)