1
0

test_presence.py 41 KB

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