test_presence.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  1. # Copyright 2016 OpenMarket Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from typing import Optional
  15. from unittest.mock import Mock, call
  16. from parameterized import parameterized
  17. from signedjson.key import generate_signing_key
  18. from synapse.api.constants import EventTypes, Membership, PresenceState
  19. from synapse.api.presence import UserPresenceState
  20. from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
  21. from synapse.events.builder import EventBuilder
  22. from synapse.federation.sender import FederationSender
  23. from synapse.handlers.presence import (
  24. EXTERNAL_PROCESS_EXPIRY,
  25. FEDERATION_PING_INTERVAL,
  26. FEDERATION_TIMEOUT,
  27. IDLE_TIMER,
  28. LAST_ACTIVE_GRANULARITY,
  29. SYNC_ONLINE_TIMEOUT,
  30. handle_timeout,
  31. handle_update,
  32. )
  33. from synapse.rest import admin
  34. from synapse.rest.client import room
  35. from synapse.types import UserID, get_domain_from_id
  36. from tests import unittest
  37. from tests.replication._base import BaseMultiWorkerStreamTestCase
  38. class PresenceUpdateTestCase(unittest.HomeserverTestCase):
  39. servlets = [admin.register_servlets]
  40. def prepare(self, reactor, clock, homeserver):
  41. self.store = homeserver.get_datastores().main
  42. def test_offline_to_online(self):
  43. wheel_timer = Mock()
  44. user_id = "@foo:bar"
  45. now = 5000000
  46. prev_state = UserPresenceState.default(user_id)
  47. new_state = prev_state.copy_and_replace(
  48. state=PresenceState.ONLINE, last_active_ts=now
  49. )
  50. state, persist_and_notify, federation_ping = handle_update(
  51. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  52. )
  53. self.assertTrue(persist_and_notify)
  54. self.assertTrue(state.currently_active)
  55. self.assertEqual(new_state.state, state.state)
  56. self.assertEqual(new_state.status_msg, state.status_msg)
  57. self.assertEqual(state.last_federation_update_ts, now)
  58. self.assertEqual(wheel_timer.insert.call_count, 3)
  59. wheel_timer.insert.assert_has_calls(
  60. [
  61. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  62. call(
  63. now=now,
  64. obj=user_id,
  65. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  66. ),
  67. call(
  68. now=now,
  69. obj=user_id,
  70. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  71. ),
  72. ],
  73. any_order=True,
  74. )
  75. def test_online_to_online(self):
  76. wheel_timer = Mock()
  77. user_id = "@foo:bar"
  78. now = 5000000
  79. prev_state = UserPresenceState.default(user_id)
  80. prev_state = prev_state.copy_and_replace(
  81. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  82. )
  83. new_state = prev_state.copy_and_replace(
  84. state=PresenceState.ONLINE, last_active_ts=now
  85. )
  86. state, persist_and_notify, federation_ping = handle_update(
  87. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  88. )
  89. self.assertFalse(persist_and_notify)
  90. self.assertTrue(federation_ping)
  91. self.assertTrue(state.currently_active)
  92. self.assertEqual(new_state.state, state.state)
  93. self.assertEqual(new_state.status_msg, state.status_msg)
  94. self.assertEqual(state.last_federation_update_ts, now)
  95. self.assertEqual(wheel_timer.insert.call_count, 3)
  96. wheel_timer.insert.assert_has_calls(
  97. [
  98. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  99. call(
  100. now=now,
  101. obj=user_id,
  102. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  103. ),
  104. call(
  105. now=now,
  106. obj=user_id,
  107. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  108. ),
  109. ],
  110. any_order=True,
  111. )
  112. def test_online_to_online_last_active_noop(self):
  113. wheel_timer = Mock()
  114. user_id = "@foo:bar"
  115. now = 5000000
  116. prev_state = UserPresenceState.default(user_id)
  117. prev_state = prev_state.copy_and_replace(
  118. state=PresenceState.ONLINE,
  119. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 10,
  120. currently_active=True,
  121. )
  122. new_state = prev_state.copy_and_replace(
  123. state=PresenceState.ONLINE, last_active_ts=now
  124. )
  125. state, persist_and_notify, federation_ping = handle_update(
  126. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  127. )
  128. self.assertFalse(persist_and_notify)
  129. self.assertTrue(federation_ping)
  130. self.assertTrue(state.currently_active)
  131. self.assertEqual(new_state.state, state.state)
  132. self.assertEqual(new_state.status_msg, state.status_msg)
  133. self.assertEqual(state.last_federation_update_ts, now)
  134. self.assertEqual(wheel_timer.insert.call_count, 3)
  135. wheel_timer.insert.assert_has_calls(
  136. [
  137. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  138. call(
  139. now=now,
  140. obj=user_id,
  141. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  142. ),
  143. call(
  144. now=now,
  145. obj=user_id,
  146. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  147. ),
  148. ],
  149. any_order=True,
  150. )
  151. def test_online_to_online_last_active(self):
  152. wheel_timer = Mock()
  153. user_id = "@foo:bar"
  154. now = 5000000
  155. prev_state = UserPresenceState.default(user_id)
  156. prev_state = prev_state.copy_and_replace(
  157. state=PresenceState.ONLINE,
  158. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 1,
  159. currently_active=True,
  160. )
  161. new_state = prev_state.copy_and_replace(state=PresenceState.ONLINE)
  162. state, persist_and_notify, federation_ping = handle_update(
  163. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  164. )
  165. self.assertTrue(persist_and_notify)
  166. self.assertFalse(state.currently_active)
  167. self.assertEqual(new_state.state, state.state)
  168. self.assertEqual(new_state.status_msg, state.status_msg)
  169. self.assertEqual(state.last_federation_update_ts, now)
  170. self.assertEqual(wheel_timer.insert.call_count, 2)
  171. wheel_timer.insert.assert_has_calls(
  172. [
  173. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  174. call(
  175. now=now,
  176. obj=user_id,
  177. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  178. ),
  179. ],
  180. any_order=True,
  181. )
  182. def test_remote_ping_timer(self):
  183. wheel_timer = Mock()
  184. user_id = "@foo:bar"
  185. now = 5000000
  186. prev_state = UserPresenceState.default(user_id)
  187. prev_state = prev_state.copy_and_replace(
  188. state=PresenceState.ONLINE, last_active_ts=now
  189. )
  190. new_state = prev_state.copy_and_replace(state=PresenceState.ONLINE)
  191. state, persist_and_notify, federation_ping = handle_update(
  192. prev_state, new_state, is_mine=False, wheel_timer=wheel_timer, now=now
  193. )
  194. self.assertFalse(persist_and_notify)
  195. self.assertFalse(federation_ping)
  196. self.assertFalse(state.currently_active)
  197. self.assertEqual(new_state.state, state.state)
  198. self.assertEqual(new_state.status_msg, state.status_msg)
  199. self.assertEqual(wheel_timer.insert.call_count, 1)
  200. wheel_timer.insert.assert_has_calls(
  201. [
  202. call(
  203. now=now,
  204. obj=user_id,
  205. then=new_state.last_federation_update_ts + FEDERATION_TIMEOUT,
  206. )
  207. ],
  208. any_order=True,
  209. )
  210. def test_online_to_offline(self):
  211. wheel_timer = Mock()
  212. user_id = "@foo:bar"
  213. now = 5000000
  214. prev_state = UserPresenceState.default(user_id)
  215. prev_state = prev_state.copy_and_replace(
  216. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  217. )
  218. new_state = prev_state.copy_and_replace(state=PresenceState.OFFLINE)
  219. state, persist_and_notify, federation_ping = handle_update(
  220. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  221. )
  222. self.assertTrue(persist_and_notify)
  223. self.assertEqual(new_state.state, state.state)
  224. self.assertEqual(state.last_federation_update_ts, now)
  225. self.assertEqual(wheel_timer.insert.call_count, 0)
  226. def test_online_to_idle(self):
  227. wheel_timer = Mock()
  228. user_id = "@foo:bar"
  229. now = 5000000
  230. prev_state = UserPresenceState.default(user_id)
  231. prev_state = prev_state.copy_and_replace(
  232. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  233. )
  234. new_state = prev_state.copy_and_replace(state=PresenceState.UNAVAILABLE)
  235. state, persist_and_notify, federation_ping = handle_update(
  236. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  237. )
  238. self.assertTrue(persist_and_notify)
  239. self.assertEqual(new_state.state, state.state)
  240. self.assertEqual(state.last_federation_update_ts, now)
  241. self.assertEqual(new_state.state, state.state)
  242. self.assertEqual(new_state.status_msg, state.status_msg)
  243. self.assertEqual(wheel_timer.insert.call_count, 1)
  244. wheel_timer.insert.assert_has_calls(
  245. [
  246. call(
  247. now=now,
  248. obj=user_id,
  249. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  250. )
  251. ],
  252. any_order=True,
  253. )
  254. def test_persisting_presence_updates(self):
  255. """Tests that the latest presence state for each user is persisted correctly"""
  256. # Create some test users and presence states for them
  257. presence_states = []
  258. for i in range(5):
  259. user_id = self.register_user(f"user_{i}", "password")
  260. presence_state = UserPresenceState(
  261. user_id=user_id,
  262. state="online",
  263. last_active_ts=1,
  264. last_federation_update_ts=1,
  265. last_user_sync_ts=1,
  266. status_msg="I'm online!",
  267. currently_active=True,
  268. )
  269. presence_states.append(presence_state)
  270. # Persist these presence updates to the database
  271. self.get_success(self.store.update_presence(presence_states))
  272. # Check that each update is present in the database
  273. db_presence_states = self.get_success(
  274. self.store.get_all_presence_updates(
  275. instance_name="master",
  276. last_id=0,
  277. current_id=len(presence_states) + 1,
  278. limit=len(presence_states),
  279. )
  280. )
  281. # Extract presence update user ID and state information into lists of tuples
  282. db_presence_states = [(ps[0], ps[1]) for _, ps in db_presence_states[0]]
  283. presence_states_compare = [(ps.user_id, ps.state) for ps in presence_states]
  284. # Compare what we put into the storage with what we got out.
  285. # They should be identical.
  286. self.assertEqual(presence_states_compare, db_presence_states)
  287. class PresenceTimeoutTestCase(unittest.TestCase):
  288. """Tests different timers and that the timer does not change `status_msg` of user."""
  289. def test_idle_timer(self):
  290. user_id = "@foo:bar"
  291. status_msg = "I'm here!"
  292. now = 5000000
  293. state = UserPresenceState.default(user_id)
  294. state = state.copy_and_replace(
  295. state=PresenceState.ONLINE,
  296. last_active_ts=now - IDLE_TIMER - 1,
  297. last_user_sync_ts=now,
  298. status_msg=status_msg,
  299. )
  300. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  301. self.assertIsNotNone(new_state)
  302. assert new_state is not None
  303. self.assertEqual(new_state.state, PresenceState.UNAVAILABLE)
  304. self.assertEqual(new_state.status_msg, status_msg)
  305. def test_busy_no_idle(self):
  306. """
  307. Tests that a user setting their presence to busy but idling doesn't turn their
  308. presence state into unavailable.
  309. """
  310. user_id = "@foo:bar"
  311. status_msg = "I'm here!"
  312. now = 5000000
  313. state = UserPresenceState.default(user_id)
  314. state = state.copy_and_replace(
  315. state=PresenceState.BUSY,
  316. last_active_ts=now - IDLE_TIMER - 1,
  317. last_user_sync_ts=now,
  318. status_msg=status_msg,
  319. )
  320. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  321. self.assertIsNotNone(new_state)
  322. assert new_state is not None
  323. self.assertEqual(new_state.state, PresenceState.BUSY)
  324. self.assertEqual(new_state.status_msg, status_msg)
  325. def test_sync_timeout(self):
  326. user_id = "@foo:bar"
  327. status_msg = "I'm here!"
  328. now = 5000000
  329. state = UserPresenceState.default(user_id)
  330. state = state.copy_and_replace(
  331. state=PresenceState.ONLINE,
  332. last_active_ts=0,
  333. last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1,
  334. status_msg=status_msg,
  335. )
  336. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  337. self.assertIsNotNone(new_state)
  338. assert new_state is not None
  339. self.assertEqual(new_state.state, PresenceState.OFFLINE)
  340. self.assertEqual(new_state.status_msg, status_msg)
  341. def test_sync_online(self):
  342. user_id = "@foo:bar"
  343. status_msg = "I'm here!"
  344. now = 5000000
  345. state = UserPresenceState.default(user_id)
  346. state = state.copy_and_replace(
  347. state=PresenceState.ONLINE,
  348. last_active_ts=now - SYNC_ONLINE_TIMEOUT - 1,
  349. last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1,
  350. status_msg=status_msg,
  351. )
  352. new_state = handle_timeout(
  353. state, is_mine=True, syncing_user_ids={user_id}, now=now
  354. )
  355. self.assertIsNotNone(new_state)
  356. assert new_state is not None
  357. self.assertEqual(new_state.state, PresenceState.ONLINE)
  358. self.assertEqual(new_state.status_msg, status_msg)
  359. def test_federation_ping(self):
  360. user_id = "@foo:bar"
  361. status_msg = "I'm here!"
  362. now = 5000000
  363. state = UserPresenceState.default(user_id)
  364. state = state.copy_and_replace(
  365. state=PresenceState.ONLINE,
  366. last_active_ts=now,
  367. last_user_sync_ts=now,
  368. last_federation_update_ts=now - FEDERATION_PING_INTERVAL - 1,
  369. status_msg=status_msg,
  370. )
  371. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  372. self.assertIsNotNone(new_state)
  373. self.assertEqual(state, new_state)
  374. def test_no_timeout(self):
  375. user_id = "@foo:bar"
  376. now = 5000000
  377. state = UserPresenceState.default(user_id)
  378. state = state.copy_and_replace(
  379. state=PresenceState.ONLINE,
  380. last_active_ts=now,
  381. last_user_sync_ts=now,
  382. last_federation_update_ts=now,
  383. )
  384. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  385. self.assertIsNone(new_state)
  386. def test_federation_timeout(self):
  387. user_id = "@foo:bar"
  388. status_msg = "I'm here!"
  389. now = 5000000
  390. state = UserPresenceState.default(user_id)
  391. state = state.copy_and_replace(
  392. state=PresenceState.ONLINE,
  393. last_active_ts=now,
  394. last_user_sync_ts=now,
  395. last_federation_update_ts=now - FEDERATION_TIMEOUT - 1,
  396. status_msg=status_msg,
  397. )
  398. new_state = handle_timeout(
  399. state, is_mine=False, syncing_user_ids=set(), now=now
  400. )
  401. self.assertIsNotNone(new_state)
  402. assert new_state is not None
  403. self.assertEqual(new_state.state, PresenceState.OFFLINE)
  404. self.assertEqual(new_state.status_msg, status_msg)
  405. def test_last_active(self):
  406. user_id = "@foo:bar"
  407. status_msg = "I'm here!"
  408. now = 5000000
  409. state = UserPresenceState.default(user_id)
  410. state = state.copy_and_replace(
  411. state=PresenceState.ONLINE,
  412. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 1,
  413. last_user_sync_ts=now,
  414. last_federation_update_ts=now,
  415. status_msg=status_msg,
  416. )
  417. new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
  418. self.assertIsNotNone(new_state)
  419. self.assertEqual(state, new_state)
  420. class PresenceHandlerTestCase(BaseMultiWorkerStreamTestCase):
  421. def prepare(self, reactor, clock, hs):
  422. self.presence_handler = hs.get_presence_handler()
  423. self.clock = hs.get_clock()
  424. def test_external_process_timeout(self):
  425. """Test that if an external process doesn't update the records for a while
  426. we time out their syncing users presence.
  427. """
  428. process_id = 1
  429. user_id = "@test:server"
  430. # Notify handler that a user is now syncing.
  431. self.get_success(
  432. self.presence_handler.update_external_syncs_row(
  433. process_id, user_id, True, self.clock.time_msec()
  434. )
  435. )
  436. # Check that if we wait a while without telling the handler the user has
  437. # stopped syncing that their presence state doesn't get timed out.
  438. self.reactor.advance(EXTERNAL_PROCESS_EXPIRY / 2)
  439. state = self.get_success(
  440. self.presence_handler.get_state(UserID.from_string(user_id))
  441. )
  442. self.assertEqual(state.state, PresenceState.ONLINE)
  443. # Check that if the external process timeout fires, then the syncing
  444. # user gets timed out
  445. self.reactor.advance(EXTERNAL_PROCESS_EXPIRY)
  446. state = self.get_success(
  447. self.presence_handler.get_state(UserID.from_string(user_id))
  448. )
  449. self.assertEqual(state.state, PresenceState.OFFLINE)
  450. def test_user_goes_offline_by_timeout_status_msg_remain(self):
  451. """Test that if a user doesn't update the records for a while
  452. users presence goes `OFFLINE` because of timeout and `status_msg` remains.
  453. """
  454. user_id = "@test:server"
  455. status_msg = "I'm here!"
  456. # Mark user as online
  457. self._set_presencestate_with_status_msg(
  458. user_id, PresenceState.ONLINE, status_msg
  459. )
  460. # Check that if we wait a while without telling the handler the user has
  461. # stopped syncing that their presence state doesn't get timed out.
  462. self.reactor.advance(SYNC_ONLINE_TIMEOUT / 2)
  463. state = self.get_success(
  464. self.presence_handler.get_state(UserID.from_string(user_id))
  465. )
  466. self.assertEqual(state.state, PresenceState.ONLINE)
  467. self.assertEqual(state.status_msg, status_msg)
  468. # Check that if the timeout fires, then the syncing user gets timed out
  469. self.reactor.advance(SYNC_ONLINE_TIMEOUT)
  470. state = self.get_success(
  471. self.presence_handler.get_state(UserID.from_string(user_id))
  472. )
  473. # status_msg should remain even after going offline
  474. self.assertEqual(state.state, PresenceState.OFFLINE)
  475. self.assertEqual(state.status_msg, status_msg)
  476. def test_user_goes_offline_manually_with_no_status_msg(self):
  477. """Test that if a user change presence manually to `OFFLINE`
  478. and no status is set, that `status_msg` is `None`.
  479. """
  480. user_id = "@test:server"
  481. status_msg = "I'm here!"
  482. # Mark user as online
  483. self._set_presencestate_with_status_msg(
  484. user_id, PresenceState.ONLINE, status_msg
  485. )
  486. # Mark user as offline
  487. self.get_success(
  488. self.presence_handler.set_state(
  489. UserID.from_string(user_id), {"presence": PresenceState.OFFLINE}
  490. )
  491. )
  492. state = self.get_success(
  493. self.presence_handler.get_state(UserID.from_string(user_id))
  494. )
  495. self.assertEqual(state.state, PresenceState.OFFLINE)
  496. self.assertEqual(state.status_msg, None)
  497. def test_user_goes_offline_manually_with_status_msg(self):
  498. """Test that if a user change presence manually to `OFFLINE`
  499. and a status is set, that `status_msg` appears.
  500. """
  501. user_id = "@test:server"
  502. status_msg = "I'm here!"
  503. # Mark user as online
  504. self._set_presencestate_with_status_msg(
  505. user_id, PresenceState.ONLINE, status_msg
  506. )
  507. # Mark user as offline
  508. self._set_presencestate_with_status_msg(
  509. user_id, PresenceState.OFFLINE, "And now here."
  510. )
  511. def test_user_reset_online_with_no_status(self):
  512. """Test that if a user set again the presence manually
  513. and no status is set, that `status_msg` is `None`.
  514. """
  515. user_id = "@test:server"
  516. status_msg = "I'm here!"
  517. # Mark user as online
  518. self._set_presencestate_with_status_msg(
  519. user_id, PresenceState.ONLINE, status_msg
  520. )
  521. # Mark user as online again
  522. self.get_success(
  523. self.presence_handler.set_state(
  524. UserID.from_string(user_id), {"presence": PresenceState.ONLINE}
  525. )
  526. )
  527. state = self.get_success(
  528. self.presence_handler.get_state(UserID.from_string(user_id))
  529. )
  530. # status_msg should remain even after going offline
  531. self.assertEqual(state.state, PresenceState.ONLINE)
  532. self.assertEqual(state.status_msg, None)
  533. def test_set_presence_with_status_msg_none(self):
  534. """Test that if a user set again the presence manually
  535. and status is `None`, that `status_msg` is `None`.
  536. """
  537. user_id = "@test:server"
  538. status_msg = "I'm here!"
  539. # Mark user as online
  540. self._set_presencestate_with_status_msg(
  541. user_id, PresenceState.ONLINE, status_msg
  542. )
  543. # Mark user as online and `status_msg = None`
  544. self._set_presencestate_with_status_msg(user_id, PresenceState.ONLINE, None)
  545. def test_set_presence_from_syncing_not_set(self):
  546. """Test that presence is not set by syncing if affect_presence is false"""
  547. user_id = "@test:server"
  548. status_msg = "I'm here!"
  549. self._set_presencestate_with_status_msg(
  550. user_id, PresenceState.UNAVAILABLE, status_msg
  551. )
  552. self.get_success(
  553. self.presence_handler.user_syncing(user_id, False, PresenceState.ONLINE)
  554. )
  555. state = self.get_success(
  556. self.presence_handler.get_state(UserID.from_string(user_id))
  557. )
  558. # we should still be unavailable
  559. self.assertEqual(state.state, PresenceState.UNAVAILABLE)
  560. # and status message should still be the same
  561. self.assertEqual(state.status_msg, status_msg)
  562. def test_set_presence_from_syncing_is_set(self):
  563. """Test that presence is set by syncing if affect_presence is true"""
  564. user_id = "@test:server"
  565. status_msg = "I'm here!"
  566. self._set_presencestate_with_status_msg(
  567. user_id, PresenceState.UNAVAILABLE, status_msg
  568. )
  569. self.get_success(
  570. self.presence_handler.user_syncing(user_id, True, PresenceState.ONLINE)
  571. )
  572. state = self.get_success(
  573. self.presence_handler.get_state(UserID.from_string(user_id))
  574. )
  575. # we should now be online
  576. self.assertEqual(state.state, PresenceState.ONLINE)
  577. def test_set_presence_from_syncing_keeps_status(self):
  578. """Test that presence set by syncing retains status message"""
  579. user_id = "@test:server"
  580. status_msg = "I'm here!"
  581. self._set_presencestate_with_status_msg(
  582. user_id, PresenceState.UNAVAILABLE, status_msg
  583. )
  584. self.get_success(
  585. self.presence_handler.user_syncing(user_id, True, PresenceState.ONLINE)
  586. )
  587. state = self.get_success(
  588. self.presence_handler.get_state(UserID.from_string(user_id))
  589. )
  590. # our status message should be the same as it was before
  591. self.assertEqual(state.status_msg, status_msg)
  592. @parameterized.expand([(False,), (True,)])
  593. @unittest.override_config(
  594. {
  595. "experimental_features": {
  596. "msc3026_enabled": True,
  597. },
  598. }
  599. )
  600. def test_set_presence_from_syncing_keeps_busy(self, test_with_workers: bool):
  601. """Test that presence set by syncing doesn't affect busy status
  602. Args:
  603. test_with_workers: If True, check the presence state of the user by calling
  604. /sync against a worker, rather than the main process.
  605. """
  606. user_id = "@test:server"
  607. status_msg = "I'm busy!"
  608. # By default, we call /sync against the main process.
  609. worker_to_sync_against = self.hs
  610. if test_with_workers:
  611. # Create a worker and use it to handle /sync traffic instead.
  612. # This is used to test that presence changes get replicated from workers
  613. # to the main process correctly.
  614. worker_to_sync_against = self.make_worker_hs(
  615. "synapse.app.generic_worker", {"worker_name": "presence_writer"}
  616. )
  617. # Set presence to BUSY
  618. self._set_presencestate_with_status_msg(user_id, PresenceState.BUSY, status_msg)
  619. # Perform a sync with a presence state other than busy. This should NOT change
  620. # our presence status; we only change from busy if we explicitly set it via
  621. # /presence/*.
  622. self.get_success(
  623. worker_to_sync_against.get_presence_handler().user_syncing(
  624. user_id, True, PresenceState.ONLINE
  625. )
  626. )
  627. # Check against the main process that the user's presence did not change.
  628. state = self.get_success(
  629. self.presence_handler.get_state(UserID.from_string(user_id))
  630. )
  631. # we should still be busy
  632. self.assertEqual(state.state, PresenceState.BUSY)
  633. def _set_presencestate_with_status_msg(
  634. self, user_id: str, state: str, status_msg: Optional[str]
  635. ):
  636. """Set a PresenceState and status_msg and check the result.
  637. Args:
  638. user_id: User for that the status is to be set.
  639. state: The new PresenceState.
  640. status_msg: Status message that is to be set.
  641. """
  642. self.get_success(
  643. self.presence_handler.set_state(
  644. UserID.from_string(user_id),
  645. {"presence": state, "status_msg": status_msg},
  646. )
  647. )
  648. new_state = self.get_success(
  649. self.presence_handler.get_state(UserID.from_string(user_id))
  650. )
  651. self.assertEqual(new_state.state, state)
  652. self.assertEqual(new_state.status_msg, status_msg)
  653. class PresenceFederationQueueTestCase(unittest.HomeserverTestCase):
  654. def prepare(self, reactor, clock, hs):
  655. self.presence_handler = hs.get_presence_handler()
  656. self.clock = hs.get_clock()
  657. self.instance_name = hs.get_instance_name()
  658. self.queue = self.presence_handler.get_federation_queue()
  659. def test_send_and_get(self):
  660. state1 = UserPresenceState.default("@user1:test")
  661. state2 = UserPresenceState.default("@user2:test")
  662. state3 = UserPresenceState.default("@user3:test")
  663. prev_token = self.queue.get_current_token(self.instance_name)
  664. self.queue.send_presence_to_destinations((state1, state2), ("dest1", "dest2"))
  665. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  666. now_token = self.queue.get_current_token(self.instance_name)
  667. rows, upto_token, limited = self.get_success(
  668. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  669. )
  670. self.assertEqual(upto_token, now_token)
  671. self.assertFalse(limited)
  672. expected_rows = [
  673. (1, ("dest1", "@user1:test")),
  674. (1, ("dest2", "@user1:test")),
  675. (1, ("dest1", "@user2:test")),
  676. (1, ("dest2", "@user2:test")),
  677. (2, ("dest3", "@user3:test")),
  678. ]
  679. self.assertCountEqual(rows, expected_rows)
  680. now_token = self.queue.get_current_token(self.instance_name)
  681. rows, upto_token, limited = self.get_success(
  682. self.queue.get_replication_rows("master", upto_token, now_token, 10)
  683. )
  684. self.assertEqual(upto_token, now_token)
  685. self.assertFalse(limited)
  686. self.assertCountEqual(rows, [])
  687. def test_send_and_get_split(self):
  688. state1 = UserPresenceState.default("@user1:test")
  689. state2 = UserPresenceState.default("@user2:test")
  690. state3 = UserPresenceState.default("@user3:test")
  691. prev_token = self.queue.get_current_token(self.instance_name)
  692. self.queue.send_presence_to_destinations((state1, state2), ("dest1", "dest2"))
  693. now_token = self.queue.get_current_token(self.instance_name)
  694. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  695. rows, upto_token, limited = self.get_success(
  696. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  697. )
  698. self.assertEqual(upto_token, now_token)
  699. self.assertFalse(limited)
  700. expected_rows = [
  701. (1, ("dest1", "@user1:test")),
  702. (1, ("dest2", "@user1:test")),
  703. (1, ("dest1", "@user2:test")),
  704. (1, ("dest2", "@user2:test")),
  705. ]
  706. self.assertCountEqual(rows, expected_rows)
  707. now_token = self.queue.get_current_token(self.instance_name)
  708. rows, upto_token, limited = self.get_success(
  709. self.queue.get_replication_rows("master", upto_token, now_token, 10)
  710. )
  711. self.assertEqual(upto_token, now_token)
  712. self.assertFalse(limited)
  713. expected_rows = [
  714. (2, ("dest3", "@user3:test")),
  715. ]
  716. self.assertCountEqual(rows, expected_rows)
  717. def test_clear_queue_all(self):
  718. state1 = UserPresenceState.default("@user1:test")
  719. state2 = UserPresenceState.default("@user2:test")
  720. state3 = UserPresenceState.default("@user3:test")
  721. prev_token = self.queue.get_current_token(self.instance_name)
  722. self.queue.send_presence_to_destinations((state1, state2), ("dest1", "dest2"))
  723. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  724. self.reactor.advance(10 * 60 * 1000)
  725. now_token = self.queue.get_current_token(self.instance_name)
  726. rows, upto_token, limited = self.get_success(
  727. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  728. )
  729. self.assertEqual(upto_token, now_token)
  730. self.assertFalse(limited)
  731. self.assertCountEqual(rows, [])
  732. prev_token = self.queue.get_current_token(self.instance_name)
  733. self.queue.send_presence_to_destinations((state1, state2), ("dest1", "dest2"))
  734. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  735. now_token = self.queue.get_current_token(self.instance_name)
  736. rows, upto_token, limited = self.get_success(
  737. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  738. )
  739. self.assertEqual(upto_token, now_token)
  740. self.assertFalse(limited)
  741. expected_rows = [
  742. (3, ("dest1", "@user1:test")),
  743. (3, ("dest2", "@user1:test")),
  744. (3, ("dest1", "@user2:test")),
  745. (3, ("dest2", "@user2:test")),
  746. (4, ("dest3", "@user3:test")),
  747. ]
  748. self.assertCountEqual(rows, expected_rows)
  749. def test_partially_clear_queue(self):
  750. state1 = UserPresenceState.default("@user1:test")
  751. state2 = UserPresenceState.default("@user2:test")
  752. state3 = UserPresenceState.default("@user3:test")
  753. prev_token = self.queue.get_current_token(self.instance_name)
  754. self.queue.send_presence_to_destinations((state1, state2), ("dest1", "dest2"))
  755. self.reactor.advance(2 * 60 * 1000)
  756. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  757. self.reactor.advance(4 * 60 * 1000)
  758. now_token = self.queue.get_current_token(self.instance_name)
  759. rows, upto_token, limited = self.get_success(
  760. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  761. )
  762. self.assertEqual(upto_token, now_token)
  763. self.assertFalse(limited)
  764. expected_rows = [
  765. (2, ("dest3", "@user3:test")),
  766. ]
  767. self.assertCountEqual(rows, [])
  768. prev_token = self.queue.get_current_token(self.instance_name)
  769. self.queue.send_presence_to_destinations((state1, state2), ("dest1", "dest2"))
  770. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  771. now_token = self.queue.get_current_token(self.instance_name)
  772. rows, upto_token, limited = self.get_success(
  773. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  774. )
  775. self.assertEqual(upto_token, now_token)
  776. self.assertFalse(limited)
  777. expected_rows = [
  778. (3, ("dest1", "@user1:test")),
  779. (3, ("dest2", "@user1:test")),
  780. (3, ("dest1", "@user2:test")),
  781. (3, ("dest2", "@user2:test")),
  782. (4, ("dest3", "@user3:test")),
  783. ]
  784. self.assertCountEqual(rows, expected_rows)
  785. class PresenceJoinTestCase(unittest.HomeserverTestCase):
  786. """Tests remote servers get told about presence of users in the room when
  787. they join and when new local users join.
  788. """
  789. user_id = "@test:server"
  790. servlets = [room.register_servlets]
  791. def make_homeserver(self, reactor, clock):
  792. hs = self.setup_test_homeserver(
  793. "server",
  794. federation_http_client=None,
  795. federation_sender=Mock(spec=FederationSender),
  796. )
  797. return hs
  798. def default_config(self):
  799. config = super().default_config()
  800. config["send_federation"] = True
  801. return config
  802. def prepare(self, reactor, clock, hs):
  803. self.federation_sender = hs.get_federation_sender()
  804. self.event_builder_factory = hs.get_event_builder_factory()
  805. self.federation_event_handler = hs.get_federation_event_handler()
  806. self.presence_handler = hs.get_presence_handler()
  807. # self.event_builder_for_2 = EventBuilderFactory(hs)
  808. # self.event_builder_for_2.hostname = "test2"
  809. self.store = hs.get_datastores().main
  810. self.state = hs.get_state_handler()
  811. self._event_auth_handler = hs.get_event_auth_handler()
  812. # We don't actually check signatures in tests, so lets just create a
  813. # random key to use.
  814. self.random_signing_key = generate_signing_key("ver")
  815. def test_remote_joins(self):
  816. # We advance time to something that isn't 0, as we use 0 as a special
  817. # value.
  818. self.reactor.advance(1000000000000)
  819. # Create a room with two local users
  820. room_id = self.helper.create_room_as(self.user_id)
  821. self.helper.join(room_id, "@test2:server")
  822. # Mark test2 as online, test will be offline with a last_active of 0
  823. self.get_success(
  824. self.presence_handler.set_state(
  825. UserID.from_string("@test2:server"), {"presence": PresenceState.ONLINE}
  826. )
  827. )
  828. self.reactor.pump([0]) # Wait for presence updates to be handled
  829. #
  830. # Test that a new server gets told about existing presence
  831. #
  832. self.federation_sender.reset_mock()
  833. # Add a new remote server to the room
  834. self._add_new_user(room_id, "@alice:server2")
  835. # When new server is joined we send it the local users presence states.
  836. # We expect to only see user @test2:server, as @test:server is offline
  837. # and has a zero last_active_ts
  838. expected_state = self.get_success(
  839. self.presence_handler.current_state_for_user("@test2:server")
  840. )
  841. self.assertEqual(expected_state.state, PresenceState.ONLINE)
  842. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  843. destinations={"server2"}, states=[expected_state]
  844. )
  845. #
  846. # Test that only the new server gets sent presence and not existing servers
  847. #
  848. self.federation_sender.reset_mock()
  849. self._add_new_user(room_id, "@bob:server3")
  850. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  851. destinations={"server3"}, states=[expected_state]
  852. )
  853. def test_remote_gets_presence_when_local_user_joins(self):
  854. # We advance time to something that isn't 0, as we use 0 as a special
  855. # value.
  856. self.reactor.advance(1000000000000)
  857. # Create a room with one local users
  858. room_id = self.helper.create_room_as(self.user_id)
  859. # Mark test as online
  860. self.get_success(
  861. self.presence_handler.set_state(
  862. UserID.from_string("@test:server"), {"presence": PresenceState.ONLINE}
  863. )
  864. )
  865. # Mark test2 as online, test will be offline with a last_active of 0.
  866. # Note we don't join them to the room yet
  867. self.get_success(
  868. self.presence_handler.set_state(
  869. UserID.from_string("@test2:server"), {"presence": PresenceState.ONLINE}
  870. )
  871. )
  872. # Add servers to the room
  873. self._add_new_user(room_id, "@alice:server2")
  874. self._add_new_user(room_id, "@bob:server3")
  875. self.reactor.pump([0]) # Wait for presence updates to be handled
  876. #
  877. # Test that when a local join happens remote servers get told about it
  878. #
  879. self.federation_sender.reset_mock()
  880. # Join local user to room
  881. self.helper.join(room_id, "@test2:server")
  882. self.reactor.pump([0]) # Wait for presence updates to be handled
  883. # We expect to only send test2 presence to server2 and server3
  884. expected_state = self.get_success(
  885. self.presence_handler.current_state_for_user("@test2:server")
  886. )
  887. self.assertEqual(expected_state.state, PresenceState.ONLINE)
  888. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  889. destinations={"server2", "server3"}, states=[expected_state]
  890. )
  891. def _add_new_user(self, room_id, user_id):
  892. """Add new user to the room by creating an event and poking the federation API."""
  893. hostname = get_domain_from_id(user_id)
  894. room_version = self.get_success(self.store.get_room_version_id(room_id))
  895. builder = EventBuilder(
  896. state=self.state,
  897. event_auth_handler=self._event_auth_handler,
  898. store=self.store,
  899. clock=self.clock,
  900. hostname=hostname,
  901. signing_key=self.random_signing_key,
  902. room_version=KNOWN_ROOM_VERSIONS[room_version],
  903. room_id=room_id,
  904. type=EventTypes.Member,
  905. sender=user_id,
  906. state_key=user_id,
  907. content={"membership": Membership.JOIN},
  908. )
  909. prev_event_ids = self.get_success(
  910. self.store.get_latest_event_ids_in_room(room_id)
  911. )
  912. event = self.get_success(
  913. builder.build(prev_event_ids=prev_event_ids, auth_event_ids=None)
  914. )
  915. self.get_success(self.federation_event_handler.on_receive_pdu(hostname, event))
  916. # Check that it was successfully persisted.
  917. self.get_success(self.store.get_event(event.event_id))
  918. self.get_success(self.store.get_event(event.event_id))