You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1869 lines
67 KiB

  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 UserDevicePresenceState, 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. BUSY_ONLINE_TIMEOUT,
  26. EXTERNAL_PROCESS_EXPIRY,
  27. FEDERATION_PING_INTERVAL,
  28. FEDERATION_TIMEOUT,
  29. IDLE_TIMER,
  30. LAST_ACTIVE_GRANULARITY,
  31. SYNC_ONLINE_TIMEOUT,
  32. handle_timeout,
  33. handle_update,
  34. )
  35. from synapse.rest import admin
  36. from synapse.rest.client import room
  37. from synapse.server import HomeServer
  38. from synapse.storage.database import LoggingDatabaseConnection
  39. from synapse.types import JsonDict, UserID, get_domain_from_id
  40. from synapse.util import Clock
  41. from tests import unittest
  42. from tests.replication._base import BaseMultiWorkerStreamTestCase
  43. class PresenceUpdateTestCase(unittest.HomeserverTestCase):
  44. servlets = [admin.register_servlets]
  45. def prepare(
  46. self, reactor: MemoryReactor, clock: Clock, homeserver: HomeServer
  47. ) -> None:
  48. self.store = homeserver.get_datastores().main
  49. def test_offline_to_online(self) -> None:
  50. wheel_timer = Mock()
  51. user_id = "@foo:bar"
  52. now = 5000000
  53. prev_state = UserPresenceState.default(user_id)
  54. new_state = prev_state.copy_and_replace(
  55. state=PresenceState.ONLINE, last_active_ts=now
  56. )
  57. state, persist_and_notify, federation_ping = handle_update(
  58. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  59. )
  60. self.assertTrue(persist_and_notify)
  61. self.assertTrue(state.currently_active)
  62. self.assertEqual(new_state.state, state.state)
  63. self.assertEqual(new_state.status_msg, state.status_msg)
  64. self.assertEqual(state.last_federation_update_ts, now)
  65. self.assertEqual(wheel_timer.insert.call_count, 3)
  66. wheel_timer.insert.assert_has_calls(
  67. [
  68. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  69. call(
  70. now=now,
  71. obj=user_id,
  72. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  73. ),
  74. call(
  75. now=now,
  76. obj=user_id,
  77. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  78. ),
  79. ],
  80. any_order=True,
  81. )
  82. def test_online_to_online(self) -> None:
  83. wheel_timer = Mock()
  84. user_id = "@foo:bar"
  85. now = 5000000
  86. prev_state = UserPresenceState.default(user_id)
  87. prev_state = prev_state.copy_and_replace(
  88. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  89. )
  90. new_state = prev_state.copy_and_replace(
  91. state=PresenceState.ONLINE, last_active_ts=now
  92. )
  93. state, persist_and_notify, federation_ping = handle_update(
  94. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  95. )
  96. self.assertFalse(persist_and_notify)
  97. self.assertTrue(federation_ping)
  98. self.assertTrue(state.currently_active)
  99. self.assertEqual(new_state.state, state.state)
  100. self.assertEqual(new_state.status_msg, state.status_msg)
  101. self.assertEqual(state.last_federation_update_ts, now)
  102. self.assertEqual(wheel_timer.insert.call_count, 3)
  103. wheel_timer.insert.assert_has_calls(
  104. [
  105. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  106. call(
  107. now=now,
  108. obj=user_id,
  109. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  110. ),
  111. call(
  112. now=now,
  113. obj=user_id,
  114. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  115. ),
  116. ],
  117. any_order=True,
  118. )
  119. def test_online_to_online_last_active_noop(self) -> None:
  120. wheel_timer = Mock()
  121. user_id = "@foo:bar"
  122. now = 5000000
  123. prev_state = UserPresenceState.default(user_id)
  124. prev_state = prev_state.copy_and_replace(
  125. state=PresenceState.ONLINE,
  126. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 10,
  127. currently_active=True,
  128. )
  129. new_state = prev_state.copy_and_replace(
  130. state=PresenceState.ONLINE, last_active_ts=now
  131. )
  132. state, persist_and_notify, federation_ping = handle_update(
  133. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  134. )
  135. self.assertFalse(persist_and_notify)
  136. self.assertTrue(federation_ping)
  137. self.assertTrue(state.currently_active)
  138. self.assertEqual(new_state.state, state.state)
  139. self.assertEqual(new_state.status_msg, state.status_msg)
  140. self.assertEqual(state.last_federation_update_ts, now)
  141. self.assertEqual(wheel_timer.insert.call_count, 3)
  142. wheel_timer.insert.assert_has_calls(
  143. [
  144. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  145. call(
  146. now=now,
  147. obj=user_id,
  148. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  149. ),
  150. call(
  151. now=now,
  152. obj=user_id,
  153. then=new_state.last_active_ts + LAST_ACTIVE_GRANULARITY,
  154. ),
  155. ],
  156. any_order=True,
  157. )
  158. def test_online_to_online_last_active(self) -> None:
  159. wheel_timer = Mock()
  160. user_id = "@foo:bar"
  161. now = 5000000
  162. prev_state = UserPresenceState.default(user_id)
  163. prev_state = prev_state.copy_and_replace(
  164. state=PresenceState.ONLINE,
  165. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 1,
  166. currently_active=True,
  167. )
  168. new_state = prev_state.copy_and_replace(state=PresenceState.ONLINE)
  169. state, persist_and_notify, federation_ping = handle_update(
  170. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  171. )
  172. self.assertTrue(persist_and_notify)
  173. self.assertFalse(state.currently_active)
  174. self.assertEqual(new_state.state, state.state)
  175. self.assertEqual(new_state.status_msg, state.status_msg)
  176. self.assertEqual(state.last_federation_update_ts, now)
  177. self.assertEqual(wheel_timer.insert.call_count, 2)
  178. wheel_timer.insert.assert_has_calls(
  179. [
  180. call(now=now, obj=user_id, then=new_state.last_active_ts + IDLE_TIMER),
  181. call(
  182. now=now,
  183. obj=user_id,
  184. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  185. ),
  186. ],
  187. any_order=True,
  188. )
  189. def test_remote_ping_timer(self) -> None:
  190. wheel_timer = Mock()
  191. user_id = "@foo:bar"
  192. now = 5000000
  193. prev_state = UserPresenceState.default(user_id)
  194. prev_state = prev_state.copy_and_replace(
  195. state=PresenceState.ONLINE, last_active_ts=now
  196. )
  197. new_state = prev_state.copy_and_replace(state=PresenceState.ONLINE)
  198. state, persist_and_notify, federation_ping = handle_update(
  199. prev_state, new_state, is_mine=False, wheel_timer=wheel_timer, now=now
  200. )
  201. self.assertFalse(persist_and_notify)
  202. self.assertFalse(federation_ping)
  203. self.assertFalse(state.currently_active)
  204. self.assertEqual(new_state.state, state.state)
  205. self.assertEqual(new_state.status_msg, state.status_msg)
  206. self.assertEqual(wheel_timer.insert.call_count, 1)
  207. wheel_timer.insert.assert_has_calls(
  208. [
  209. call(
  210. now=now,
  211. obj=user_id,
  212. then=new_state.last_federation_update_ts + FEDERATION_TIMEOUT,
  213. )
  214. ],
  215. any_order=True,
  216. )
  217. def test_online_to_offline(self) -> None:
  218. wheel_timer = Mock()
  219. user_id = "@foo:bar"
  220. now = 5000000
  221. prev_state = UserPresenceState.default(user_id)
  222. prev_state = prev_state.copy_and_replace(
  223. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  224. )
  225. new_state = prev_state.copy_and_replace(state=PresenceState.OFFLINE)
  226. state, persist_and_notify, federation_ping = handle_update(
  227. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  228. )
  229. self.assertTrue(persist_and_notify)
  230. self.assertEqual(new_state.state, state.state)
  231. self.assertEqual(state.last_federation_update_ts, now)
  232. self.assertEqual(wheel_timer.insert.call_count, 0)
  233. def test_online_to_idle(self) -> None:
  234. wheel_timer = Mock()
  235. user_id = "@foo:bar"
  236. now = 5000000
  237. prev_state = UserPresenceState.default(user_id)
  238. prev_state = prev_state.copy_and_replace(
  239. state=PresenceState.ONLINE, last_active_ts=now, currently_active=True
  240. )
  241. new_state = prev_state.copy_and_replace(state=PresenceState.UNAVAILABLE)
  242. state, persist_and_notify, federation_ping = handle_update(
  243. prev_state, new_state, is_mine=True, wheel_timer=wheel_timer, now=now
  244. )
  245. self.assertTrue(persist_and_notify)
  246. self.assertEqual(new_state.state, state.state)
  247. self.assertEqual(state.last_federation_update_ts, now)
  248. self.assertEqual(new_state.state, state.state)
  249. self.assertEqual(new_state.status_msg, state.status_msg)
  250. self.assertEqual(wheel_timer.insert.call_count, 1)
  251. wheel_timer.insert.assert_has_calls(
  252. [
  253. call(
  254. now=now,
  255. obj=user_id,
  256. then=new_state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT,
  257. )
  258. ],
  259. any_order=True,
  260. )
  261. def test_persisting_presence_updates(self) -> None:
  262. """Tests that the latest presence state for each user is persisted correctly"""
  263. # Create some test users and presence states for them
  264. presence_states = []
  265. for i in range(5):
  266. user_id = self.register_user(f"user_{i}", "password")
  267. presence_state = UserPresenceState(
  268. user_id=user_id,
  269. state="online",
  270. last_active_ts=1,
  271. last_federation_update_ts=1,
  272. last_user_sync_ts=1,
  273. status_msg="I'm online!",
  274. currently_active=True,
  275. )
  276. presence_states.append(presence_state)
  277. # Persist these presence updates to the database
  278. self.get_success(self.store.update_presence(presence_states))
  279. # Check that each update is present in the database
  280. db_presence_states_raw = self.get_success(
  281. self.store.get_all_presence_updates(
  282. instance_name="master",
  283. last_id=0,
  284. current_id=len(presence_states) + 1,
  285. limit=len(presence_states),
  286. )
  287. )
  288. # Extract presence update user ID and state information into lists of tuples
  289. db_presence_states = [(ps[0], ps[1]) for _, ps in db_presence_states_raw[0]]
  290. presence_states_compare = [(ps.user_id, ps.state) for ps in presence_states]
  291. # Compare what we put into the storage with what we got out.
  292. # They should be identical.
  293. self.assertEqual(presence_states_compare, db_presence_states)
  294. class PresenceTimeoutTestCase(unittest.TestCase):
  295. """Tests different timers and that the timer does not change `status_msg` of user."""
  296. def test_idle_timer(self) -> None:
  297. user_id = "@foo:bar"
  298. device_id = "dev-1"
  299. status_msg = "I'm here!"
  300. now = 5000000
  301. state = UserPresenceState.default(user_id)
  302. state = state.copy_and_replace(
  303. state=PresenceState.ONLINE,
  304. last_active_ts=now - IDLE_TIMER - 1,
  305. last_user_sync_ts=now,
  306. status_msg=status_msg,
  307. )
  308. device_state = UserDevicePresenceState(
  309. user_id=user_id,
  310. device_id=device_id,
  311. state=state.state,
  312. last_active_ts=state.last_active_ts,
  313. last_sync_ts=state.last_user_sync_ts,
  314. )
  315. new_state = handle_timeout(
  316. state,
  317. is_mine=True,
  318. syncing_device_ids=set(),
  319. user_devices={device_id: device_state},
  320. now=now,
  321. )
  322. self.assertIsNotNone(new_state)
  323. assert new_state is not None
  324. self.assertEqual(new_state.state, PresenceState.UNAVAILABLE)
  325. self.assertEqual(new_state.status_msg, status_msg)
  326. def test_busy_no_idle(self) -> None:
  327. """
  328. Tests that a user setting their presence to busy but idling doesn't turn their
  329. presence state into unavailable.
  330. """
  331. user_id = "@foo:bar"
  332. device_id = "dev-1"
  333. status_msg = "I'm here!"
  334. now = 5000000
  335. state = UserPresenceState.default(user_id)
  336. state = state.copy_and_replace(
  337. state=PresenceState.BUSY,
  338. last_active_ts=now - IDLE_TIMER - 1,
  339. last_user_sync_ts=now,
  340. status_msg=status_msg,
  341. )
  342. device_state = UserDevicePresenceState(
  343. user_id=user_id,
  344. device_id=device_id,
  345. state=state.state,
  346. last_active_ts=state.last_active_ts,
  347. last_sync_ts=state.last_user_sync_ts,
  348. )
  349. new_state = handle_timeout(
  350. state,
  351. is_mine=True,
  352. syncing_device_ids=set(),
  353. user_devices={device_id: device_state},
  354. now=now,
  355. )
  356. self.assertIsNotNone(new_state)
  357. assert new_state is not None
  358. self.assertEqual(new_state.state, PresenceState.BUSY)
  359. self.assertEqual(new_state.status_msg, status_msg)
  360. def test_sync_timeout(self) -> None:
  361. user_id = "@foo:bar"
  362. device_id = "dev-1"
  363. status_msg = "I'm here!"
  364. now = 5000000
  365. state = UserPresenceState.default(user_id)
  366. state = state.copy_and_replace(
  367. state=PresenceState.ONLINE,
  368. last_active_ts=0,
  369. last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1,
  370. status_msg=status_msg,
  371. )
  372. device_state = UserDevicePresenceState(
  373. user_id=user_id,
  374. device_id=device_id,
  375. state=state.state,
  376. last_active_ts=state.last_active_ts,
  377. last_sync_ts=state.last_user_sync_ts,
  378. )
  379. new_state = handle_timeout(
  380. state,
  381. is_mine=True,
  382. syncing_device_ids=set(),
  383. user_devices={device_id: device_state},
  384. now=now,
  385. )
  386. self.assertIsNotNone(new_state)
  387. assert new_state is not None
  388. self.assertEqual(new_state.state, PresenceState.OFFLINE)
  389. self.assertEqual(new_state.status_msg, status_msg)
  390. def test_sync_online(self) -> None:
  391. user_id = "@foo:bar"
  392. device_id = "dev-1"
  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 - SYNC_ONLINE_TIMEOUT - 1,
  399. last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1,
  400. status_msg=status_msg,
  401. )
  402. device_state = UserDevicePresenceState(
  403. user_id=user_id,
  404. device_id=device_id,
  405. state=state.state,
  406. last_active_ts=state.last_active_ts,
  407. last_sync_ts=state.last_user_sync_ts,
  408. )
  409. new_state = handle_timeout(
  410. state,
  411. is_mine=True,
  412. syncing_device_ids={(user_id, device_id)},
  413. user_devices={device_id: device_state},
  414. now=now,
  415. )
  416. self.assertIsNotNone(new_state)
  417. assert new_state is not None
  418. self.assertEqual(new_state.state, PresenceState.ONLINE)
  419. self.assertEqual(new_state.status_msg, status_msg)
  420. def test_federation_ping(self) -> None:
  421. user_id = "@foo:bar"
  422. device_id = "dev-1"
  423. status_msg = "I'm here!"
  424. now = 5000000
  425. state = UserPresenceState.default(user_id)
  426. state = state.copy_and_replace(
  427. state=PresenceState.ONLINE,
  428. last_active_ts=now,
  429. last_user_sync_ts=now,
  430. last_federation_update_ts=now - FEDERATION_PING_INTERVAL - 1,
  431. status_msg=status_msg,
  432. )
  433. device_state = UserDevicePresenceState(
  434. user_id=user_id,
  435. device_id=device_id,
  436. state=state.state,
  437. last_active_ts=state.last_active_ts,
  438. last_sync_ts=state.last_user_sync_ts,
  439. )
  440. new_state = handle_timeout(
  441. state,
  442. is_mine=True,
  443. syncing_device_ids=set(),
  444. user_devices={device_id: device_state},
  445. now=now,
  446. )
  447. self.assertIsNotNone(new_state)
  448. self.assertEqual(state, new_state)
  449. def test_no_timeout(self) -> None:
  450. user_id = "@foo:bar"
  451. device_id = "dev-1"
  452. now = 5000000
  453. state = UserPresenceState.default(user_id)
  454. state = state.copy_and_replace(
  455. state=PresenceState.ONLINE,
  456. last_active_ts=now,
  457. last_user_sync_ts=now,
  458. last_federation_update_ts=now,
  459. )
  460. device_state = UserDevicePresenceState(
  461. user_id=user_id,
  462. device_id=device_id,
  463. state=state.state,
  464. last_active_ts=state.last_active_ts,
  465. last_sync_ts=state.last_user_sync_ts,
  466. )
  467. new_state = handle_timeout(
  468. state,
  469. is_mine=True,
  470. syncing_device_ids=set(),
  471. user_devices={device_id: device_state},
  472. now=now,
  473. )
  474. self.assertIsNone(new_state)
  475. def test_federation_timeout(self) -> None:
  476. user_id = "@foo:bar"
  477. status_msg = "I'm here!"
  478. now = 5000000
  479. state = UserPresenceState.default(user_id)
  480. state = state.copy_and_replace(
  481. state=PresenceState.ONLINE,
  482. last_active_ts=now,
  483. last_user_sync_ts=now,
  484. last_federation_update_ts=now - FEDERATION_TIMEOUT - 1,
  485. status_msg=status_msg,
  486. )
  487. # Note that this is a remote user so we do not have their device information.
  488. new_state = handle_timeout(
  489. state, is_mine=False, syncing_device_ids=set(), user_devices={}, now=now
  490. )
  491. self.assertIsNotNone(new_state)
  492. assert new_state is not None
  493. self.assertEqual(new_state.state, PresenceState.OFFLINE)
  494. self.assertEqual(new_state.status_msg, status_msg)
  495. def test_last_active(self) -> None:
  496. user_id = "@foo:bar"
  497. device_id = "dev-1"
  498. status_msg = "I'm here!"
  499. now = 5000000
  500. state = UserPresenceState.default(user_id)
  501. state = state.copy_and_replace(
  502. state=PresenceState.ONLINE,
  503. last_active_ts=now - LAST_ACTIVE_GRANULARITY - 1,
  504. last_user_sync_ts=now,
  505. last_federation_update_ts=now,
  506. status_msg=status_msg,
  507. )
  508. device_state = UserDevicePresenceState(
  509. user_id=user_id,
  510. device_id=device_id,
  511. state=state.state,
  512. last_active_ts=state.last_active_ts,
  513. last_sync_ts=state.last_user_sync_ts,
  514. )
  515. new_state = handle_timeout(
  516. state,
  517. is_mine=True,
  518. syncing_device_ids=set(),
  519. user_devices={device_id: device_state},
  520. now=now,
  521. )
  522. self.assertIsNotNone(new_state)
  523. self.assertEqual(state, new_state)
  524. class PresenceHandlerInitTestCase(unittest.HomeserverTestCase):
  525. def default_config(self) -> JsonDict:
  526. config = super().default_config()
  527. # Disable background tasks on this worker so that the PresenceHandler isn't
  528. # loaded until we request it.
  529. config["run_background_tasks_on"] = "other"
  530. return config
  531. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  532. self.user_id = f"@test:{self.hs.config.server.server_name}"
  533. self.device_id = "dev-1"
  534. # Move the reactor to the initial time.
  535. self.reactor.advance(1000)
  536. now = self.clock.time_msec()
  537. main_store = hs.get_datastores().main
  538. self.get_success(
  539. main_store.update_presence(
  540. [
  541. UserPresenceState(
  542. user_id=self.user_id,
  543. state=PresenceState.ONLINE,
  544. last_active_ts=now,
  545. last_federation_update_ts=now,
  546. last_user_sync_ts=now,
  547. status_msg=None,
  548. currently_active=True,
  549. )
  550. ]
  551. )
  552. )
  553. # Regenerate the preloaded presence information on PresenceStore.
  554. def refill_presence(db_conn: LoggingDatabaseConnection) -> None:
  555. main_store._presence_on_startup = main_store._get_active_presence(db_conn)
  556. self.get_success(main_store.db_pool.runWithConnection(refill_presence))
  557. def test_restored_presence_idles(self) -> None:
  558. """The presence state restored from the database should not persist forever."""
  559. # Get the handler (which kicks off a bunch of timers).
  560. presence_handler = self.hs.get_presence_handler()
  561. # Assert the user is online.
  562. state = self.get_success(
  563. presence_handler.get_state(UserID.from_string(self.user_id))
  564. )
  565. self.assertEqual(state.state, PresenceState.ONLINE)
  566. # Advance such that the user should timeout.
  567. self.reactor.advance(SYNC_ONLINE_TIMEOUT / 1000)
  568. self.reactor.pump([5])
  569. # Check that the user is now offline.
  570. state = self.get_success(
  571. presence_handler.get_state(UserID.from_string(self.user_id))
  572. )
  573. self.assertEqual(state.state, PresenceState.OFFLINE)
  574. @parameterized.expand(
  575. [
  576. (PresenceState.BUSY, PresenceState.BUSY),
  577. (PresenceState.ONLINE, PresenceState.ONLINE),
  578. (PresenceState.UNAVAILABLE, PresenceState.ONLINE),
  579. # Offline syncs don't update the state.
  580. (PresenceState.OFFLINE, PresenceState.ONLINE),
  581. ]
  582. )
  583. @unittest.override_config({"experimental_features": {"msc3026_enabled": True}})
  584. def test_restored_presence_online_after_sync(
  585. self, sync_state: str, expected_state: str
  586. ) -> None:
  587. """
  588. The presence state restored from the database should be overridden with sync after a timeout.
  589. Args:
  590. sync_state: The presence state of the new sync.
  591. expected_state: The expected presence right after the sync.
  592. """
  593. # Get the handler (which kicks off a bunch of timers).
  594. presence_handler = self.hs.get_presence_handler()
  595. # Assert the user is online, as restored.
  596. state = self.get_success(
  597. presence_handler.get_state(UserID.from_string(self.user_id))
  598. )
  599. self.assertEqual(state.state, PresenceState.ONLINE)
  600. # Advance slightly and sync.
  601. self.reactor.advance(SYNC_ONLINE_TIMEOUT / 1000 / 2)
  602. self.get_success(
  603. presence_handler.user_syncing(
  604. self.user_id,
  605. self.device_id,
  606. sync_state != PresenceState.OFFLINE,
  607. sync_state,
  608. )
  609. )
  610. # Assert the user is in the expected state.
  611. state = self.get_success(
  612. presence_handler.get_state(UserID.from_string(self.user_id))
  613. )
  614. self.assertEqual(state.state, expected_state)
  615. # Advance such that the user's preloaded data times out, but not the new sync.
  616. self.reactor.advance(SYNC_ONLINE_TIMEOUT / 1000 / 2)
  617. self.reactor.pump([5])
  618. # Check that the user is in the sync state (as the client is currently syncing still).
  619. state = self.get_success(
  620. presence_handler.get_state(UserID.from_string(self.user_id))
  621. )
  622. self.assertEqual(state.state, sync_state)
  623. class PresenceHandlerTestCase(BaseMultiWorkerStreamTestCase):
  624. user_id = "@test:server"
  625. user_id_obj = UserID.from_string(user_id)
  626. device_id = "dev-1"
  627. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  628. self.presence_handler = hs.get_presence_handler()
  629. self.clock = hs.get_clock()
  630. def test_external_process_timeout(self) -> None:
  631. """Test that if an external process doesn't update the records for a while
  632. we time out their syncing users presence.
  633. """
  634. # Create a worker and use it to handle /sync traffic instead.
  635. # This is used to test that presence changes get replicated from workers
  636. # to the main process correctly.
  637. worker_to_sync_against = self.make_worker_hs(
  638. "synapse.app.generic_worker", {"worker_name": "synchrotron"}
  639. )
  640. worker_presence_handler = worker_to_sync_against.get_presence_handler()
  641. self.get_success(
  642. worker_presence_handler.user_syncing(
  643. self.user_id, self.device_id, True, PresenceState.ONLINE
  644. ),
  645. by=0.1,
  646. )
  647. # Check that if we wait a while without telling the handler the user has
  648. # stopped syncing that their presence state doesn't get timed out.
  649. self.reactor.advance(EXTERNAL_PROCESS_EXPIRY / 2)
  650. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  651. self.assertEqual(state.state, PresenceState.ONLINE)
  652. # Check that if the external process timeout fires, then the syncing
  653. # user gets timed out
  654. self.reactor.advance(EXTERNAL_PROCESS_EXPIRY)
  655. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  656. self.assertEqual(state.state, PresenceState.OFFLINE)
  657. def test_user_goes_offline_by_timeout_status_msg_remain(self) -> None:
  658. """Test that if a user doesn't update the records for a while
  659. users presence goes `OFFLINE` because of timeout and `status_msg` remains.
  660. """
  661. status_msg = "I'm here!"
  662. # Mark user as online
  663. self._set_presencestate_with_status_msg(PresenceState.ONLINE, status_msg)
  664. # Check that if we wait a while without telling the handler the user has
  665. # stopped syncing that their presence state doesn't get timed out.
  666. self.reactor.advance(SYNC_ONLINE_TIMEOUT / 2)
  667. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  668. self.assertEqual(state.state, PresenceState.ONLINE)
  669. self.assertEqual(state.status_msg, status_msg)
  670. # Check that if the timeout fires, then the syncing user gets timed out
  671. self.reactor.advance(SYNC_ONLINE_TIMEOUT)
  672. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  673. # status_msg should remain even after going offline
  674. self.assertEqual(state.state, PresenceState.OFFLINE)
  675. self.assertEqual(state.status_msg, status_msg)
  676. def test_user_goes_offline_manually_with_no_status_msg(self) -> None:
  677. """Test that if a user change presence manually to `OFFLINE`
  678. and no status is set, that `status_msg` is `None`.
  679. """
  680. status_msg = "I'm here!"
  681. # Mark user as online
  682. self._set_presencestate_with_status_msg(PresenceState.ONLINE, status_msg)
  683. # Mark user as offline
  684. self.get_success(
  685. self.presence_handler.set_state(
  686. self.user_id_obj, self.device_id, {"presence": PresenceState.OFFLINE}
  687. )
  688. )
  689. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  690. self.assertEqual(state.state, PresenceState.OFFLINE)
  691. self.assertEqual(state.status_msg, None)
  692. def test_user_goes_offline_manually_with_status_msg(self) -> None:
  693. """Test that if a user change presence manually to `OFFLINE`
  694. and a status is set, that `status_msg` appears.
  695. """
  696. status_msg = "I'm here!"
  697. # Mark user as online
  698. self._set_presencestate_with_status_msg(PresenceState.ONLINE, status_msg)
  699. # Mark user as offline
  700. self._set_presencestate_with_status_msg(PresenceState.OFFLINE, "And now here.")
  701. def test_user_reset_online_with_no_status(self) -> None:
  702. """Test that if a user set again the presence manually
  703. and no status is set, that `status_msg` is `None`.
  704. """
  705. status_msg = "I'm here!"
  706. # Mark user as online
  707. self._set_presencestate_with_status_msg(PresenceState.ONLINE, status_msg)
  708. # Mark user as online again
  709. self.get_success(
  710. self.presence_handler.set_state(
  711. self.user_id_obj, self.device_id, {"presence": PresenceState.ONLINE}
  712. )
  713. )
  714. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  715. # status_msg should remain even after going offline
  716. self.assertEqual(state.state, PresenceState.ONLINE)
  717. self.assertEqual(state.status_msg, None)
  718. def test_set_presence_with_status_msg_none(self) -> None:
  719. """Test that if a user set again the presence manually
  720. and status is `None`, that `status_msg` is `None`.
  721. """
  722. status_msg = "I'm here!"
  723. # Mark user as online
  724. self._set_presencestate_with_status_msg(PresenceState.ONLINE, status_msg)
  725. # Mark user as online and `status_msg = None`
  726. self._set_presencestate_with_status_msg(PresenceState.ONLINE, None)
  727. def test_set_presence_from_syncing_not_set(self) -> None:
  728. """Test that presence is not set by syncing if affect_presence is false"""
  729. status_msg = "I'm here!"
  730. self._set_presencestate_with_status_msg(PresenceState.UNAVAILABLE, status_msg)
  731. self.get_success(
  732. self.presence_handler.user_syncing(
  733. self.user_id, self.device_id, False, PresenceState.ONLINE
  734. )
  735. )
  736. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  737. # we should still be unavailable
  738. self.assertEqual(state.state, PresenceState.UNAVAILABLE)
  739. # and status message should still be the same
  740. self.assertEqual(state.status_msg, status_msg)
  741. def test_set_presence_from_syncing_is_set(self) -> None:
  742. """Test that presence is set by syncing if affect_presence is true"""
  743. status_msg = "I'm here!"
  744. self._set_presencestate_with_status_msg(PresenceState.UNAVAILABLE, status_msg)
  745. self.get_success(
  746. self.presence_handler.user_syncing(
  747. self.user_id, self.device_id, True, PresenceState.ONLINE
  748. )
  749. )
  750. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  751. # we should now be online
  752. self.assertEqual(state.state, PresenceState.ONLINE)
  753. @parameterized.expand(
  754. # A list of tuples of 4 strings:
  755. #
  756. # * The presence state of device 1.
  757. # * The presence state of device 2.
  758. # * The expected user presence state after both devices have synced.
  759. # * The expected user presence state after device 1 has idled.
  760. # * The expected user presence state after device 2 has idled.
  761. # * True to use workers, False a monolith.
  762. [
  763. (*cases, workers)
  764. for workers in (False, True)
  765. for cases in [
  766. # If both devices have the same state, online should eventually idle.
  767. # Otherwise, the state doesn't change.
  768. (
  769. PresenceState.BUSY,
  770. PresenceState.BUSY,
  771. PresenceState.BUSY,
  772. PresenceState.BUSY,
  773. PresenceState.BUSY,
  774. ),
  775. (
  776. PresenceState.ONLINE,
  777. PresenceState.ONLINE,
  778. PresenceState.ONLINE,
  779. PresenceState.ONLINE,
  780. PresenceState.UNAVAILABLE,
  781. ),
  782. (
  783. PresenceState.UNAVAILABLE,
  784. PresenceState.UNAVAILABLE,
  785. PresenceState.UNAVAILABLE,
  786. PresenceState.UNAVAILABLE,
  787. PresenceState.UNAVAILABLE,
  788. ),
  789. (
  790. PresenceState.OFFLINE,
  791. PresenceState.OFFLINE,
  792. PresenceState.OFFLINE,
  793. PresenceState.OFFLINE,
  794. PresenceState.OFFLINE,
  795. ),
  796. # If the second device has a "lower" state it should fallback to it,
  797. # except for "busy" which overrides.
  798. (
  799. PresenceState.BUSY,
  800. PresenceState.ONLINE,
  801. PresenceState.BUSY,
  802. PresenceState.BUSY,
  803. PresenceState.BUSY,
  804. ),
  805. (
  806. PresenceState.BUSY,
  807. PresenceState.UNAVAILABLE,
  808. PresenceState.BUSY,
  809. PresenceState.BUSY,
  810. PresenceState.BUSY,
  811. ),
  812. (
  813. PresenceState.BUSY,
  814. PresenceState.OFFLINE,
  815. PresenceState.BUSY,
  816. PresenceState.BUSY,
  817. PresenceState.BUSY,
  818. ),
  819. (
  820. PresenceState.ONLINE,
  821. PresenceState.UNAVAILABLE,
  822. PresenceState.ONLINE,
  823. PresenceState.UNAVAILABLE,
  824. PresenceState.UNAVAILABLE,
  825. ),
  826. (
  827. PresenceState.ONLINE,
  828. PresenceState.OFFLINE,
  829. PresenceState.ONLINE,
  830. PresenceState.UNAVAILABLE,
  831. PresenceState.UNAVAILABLE,
  832. ),
  833. (
  834. PresenceState.UNAVAILABLE,
  835. PresenceState.OFFLINE,
  836. PresenceState.UNAVAILABLE,
  837. PresenceState.UNAVAILABLE,
  838. PresenceState.UNAVAILABLE,
  839. ),
  840. # If the second device has a "higher" state it should override.
  841. (
  842. PresenceState.ONLINE,
  843. PresenceState.BUSY,
  844. PresenceState.BUSY,
  845. PresenceState.BUSY,
  846. PresenceState.BUSY,
  847. ),
  848. (
  849. PresenceState.UNAVAILABLE,
  850. PresenceState.BUSY,
  851. PresenceState.BUSY,
  852. PresenceState.BUSY,
  853. PresenceState.BUSY,
  854. ),
  855. (
  856. PresenceState.OFFLINE,
  857. PresenceState.BUSY,
  858. PresenceState.BUSY,
  859. PresenceState.BUSY,
  860. PresenceState.BUSY,
  861. ),
  862. (
  863. PresenceState.UNAVAILABLE,
  864. PresenceState.ONLINE,
  865. PresenceState.ONLINE,
  866. PresenceState.ONLINE,
  867. PresenceState.UNAVAILABLE,
  868. ),
  869. (
  870. PresenceState.OFFLINE,
  871. PresenceState.ONLINE,
  872. PresenceState.ONLINE,
  873. PresenceState.ONLINE,
  874. PresenceState.UNAVAILABLE,
  875. ),
  876. (
  877. PresenceState.OFFLINE,
  878. PresenceState.UNAVAILABLE,
  879. PresenceState.UNAVAILABLE,
  880. PresenceState.UNAVAILABLE,
  881. PresenceState.UNAVAILABLE,
  882. ),
  883. ]
  884. ],
  885. name_func=lambda testcase_func, param_num, params: f"{testcase_func.__name__}_{param_num}_{'workers' if params.args[5] else 'monolith'}",
  886. )
  887. @unittest.override_config({"experimental_features": {"msc3026_enabled": True}})
  888. def test_set_presence_from_syncing_multi_device(
  889. self,
  890. dev_1_state: str,
  891. dev_2_state: str,
  892. expected_state_1: str,
  893. expected_state_2: str,
  894. expected_state_3: str,
  895. test_with_workers: bool,
  896. ) -> None:
  897. """
  898. Test the behaviour of multiple devices syncing at the same time.
  899. Roughly the user's presence state should be set to the "highest" priority
  900. of all the devices. When a device then goes offline its state should be
  901. discarded and the next highest should win.
  902. Note that these tests use the idle timer (and don't close the syncs), it
  903. is unlikely that a *single* sync would last this long, but is close enough
  904. to continually syncing with that current state.
  905. """
  906. user_id = f"@test:{self.hs.config.server.server_name}"
  907. # By default, we call /sync against the main process.
  908. worker_presence_handler = self.presence_handler
  909. if test_with_workers:
  910. # Create a worker and use it to handle /sync traffic instead.
  911. # This is used to test that presence changes get replicated from workers
  912. # to the main process correctly.
  913. worker_to_sync_against = self.make_worker_hs(
  914. "synapse.app.generic_worker", {"worker_name": "synchrotron"}
  915. )
  916. worker_presence_handler = worker_to_sync_against.get_presence_handler()
  917. # 1. Sync with the first device.
  918. self.get_success(
  919. worker_presence_handler.user_syncing(
  920. user_id,
  921. "dev-1",
  922. affect_presence=dev_1_state != PresenceState.OFFLINE,
  923. presence_state=dev_1_state,
  924. ),
  925. by=0.01,
  926. )
  927. # 2. Wait half the idle timer.
  928. self.reactor.advance(IDLE_TIMER / 1000 / 2)
  929. self.reactor.pump([0.1])
  930. # 3. Sync with the second device.
  931. self.get_success(
  932. worker_presence_handler.user_syncing(
  933. user_id,
  934. "dev-2",
  935. affect_presence=dev_2_state != PresenceState.OFFLINE,
  936. presence_state=dev_2_state,
  937. ),
  938. by=0.01,
  939. )
  940. # 4. Assert the expected presence state.
  941. state = self.get_success(
  942. self.presence_handler.get_state(UserID.from_string(user_id))
  943. )
  944. self.assertEqual(state.state, expected_state_1)
  945. if test_with_workers:
  946. state = self.get_success(
  947. worker_presence_handler.get_state(UserID.from_string(user_id))
  948. )
  949. self.assertEqual(state.state, expected_state_1)
  950. # When testing with workers, make another random sync (with any *different*
  951. # user) to keep the process information from expiring.
  952. #
  953. # This is due to EXTERNAL_PROCESS_EXPIRY being equivalent to IDLE_TIMER.
  954. if test_with_workers:
  955. with self.get_success(
  956. worker_presence_handler.user_syncing(
  957. f"@other-user:{self.hs.config.server.server_name}",
  958. "dev-3",
  959. affect_presence=True,
  960. presence_state=PresenceState.ONLINE,
  961. ),
  962. by=0.01,
  963. ):
  964. pass
  965. # 5. Advance such that the first device should be discarded (the idle timer),
  966. # then pump so _handle_timeouts function to called.
  967. self.reactor.advance(IDLE_TIMER / 1000 / 2)
  968. self.reactor.pump([0.01])
  969. # 6. Assert the expected presence state.
  970. state = self.get_success(
  971. self.presence_handler.get_state(UserID.from_string(user_id))
  972. )
  973. self.assertEqual(state.state, expected_state_2)
  974. if test_with_workers:
  975. state = self.get_success(
  976. worker_presence_handler.get_state(UserID.from_string(user_id))
  977. )
  978. self.assertEqual(state.state, expected_state_2)
  979. # 7. Advance such that the second device should be discarded (half the idle timer),
  980. # then pump so _handle_timeouts function to called.
  981. self.reactor.advance(IDLE_TIMER / 1000 / 2)
  982. self.reactor.pump([0.1])
  983. # 8. The devices are still "syncing" (the sync context managers were never
  984. # closed), so might idle.
  985. state = self.get_success(
  986. self.presence_handler.get_state(UserID.from_string(user_id))
  987. )
  988. self.assertEqual(state.state, expected_state_3)
  989. if test_with_workers:
  990. state = self.get_success(
  991. worker_presence_handler.get_state(UserID.from_string(user_id))
  992. )
  993. self.assertEqual(state.state, expected_state_3)
  994. @parameterized.expand(
  995. # A list of tuples of 4 strings:
  996. #
  997. # * The presence state of device 1.
  998. # * The presence state of device 2.
  999. # * The expected user presence state after both devices have synced.
  1000. # * The expected user presence state after device 1 has stopped syncing.
  1001. # * True to use workers, False a monolith.
  1002. [
  1003. (*cases, workers)
  1004. for workers in (False, True)
  1005. for cases in [
  1006. # If both devices have the same state, nothing exciting should happen.
  1007. (
  1008. PresenceState.BUSY,
  1009. PresenceState.BUSY,
  1010. PresenceState.BUSY,
  1011. PresenceState.BUSY,
  1012. ),
  1013. (
  1014. PresenceState.ONLINE,
  1015. PresenceState.ONLINE,
  1016. PresenceState.ONLINE,
  1017. PresenceState.ONLINE,
  1018. ),
  1019. (
  1020. PresenceState.UNAVAILABLE,
  1021. PresenceState.UNAVAILABLE,
  1022. PresenceState.UNAVAILABLE,
  1023. PresenceState.UNAVAILABLE,
  1024. ),
  1025. (
  1026. PresenceState.OFFLINE,
  1027. PresenceState.OFFLINE,
  1028. PresenceState.OFFLINE,
  1029. PresenceState.OFFLINE,
  1030. ),
  1031. # If the second device has a "lower" state it should fallback to it,
  1032. # except for "busy" which overrides.
  1033. (
  1034. PresenceState.BUSY,
  1035. PresenceState.ONLINE,
  1036. PresenceState.BUSY,
  1037. PresenceState.BUSY,
  1038. ),
  1039. (
  1040. PresenceState.BUSY,
  1041. PresenceState.UNAVAILABLE,
  1042. PresenceState.BUSY,
  1043. PresenceState.BUSY,
  1044. ),
  1045. (
  1046. PresenceState.BUSY,
  1047. PresenceState.OFFLINE,
  1048. PresenceState.BUSY,
  1049. PresenceState.BUSY,
  1050. ),
  1051. (
  1052. PresenceState.ONLINE,
  1053. PresenceState.UNAVAILABLE,
  1054. PresenceState.ONLINE,
  1055. PresenceState.UNAVAILABLE,
  1056. ),
  1057. (
  1058. PresenceState.ONLINE,
  1059. PresenceState.OFFLINE,
  1060. PresenceState.ONLINE,
  1061. PresenceState.OFFLINE,
  1062. ),
  1063. (
  1064. PresenceState.UNAVAILABLE,
  1065. PresenceState.OFFLINE,
  1066. PresenceState.UNAVAILABLE,
  1067. PresenceState.OFFLINE,
  1068. ),
  1069. # If the second device has a "higher" state it should override.
  1070. (
  1071. PresenceState.ONLINE,
  1072. PresenceState.BUSY,
  1073. PresenceState.BUSY,
  1074. PresenceState.BUSY,
  1075. ),
  1076. (
  1077. PresenceState.UNAVAILABLE,
  1078. PresenceState.BUSY,
  1079. PresenceState.BUSY,
  1080. PresenceState.BUSY,
  1081. ),
  1082. (
  1083. PresenceState.OFFLINE,
  1084. PresenceState.BUSY,
  1085. PresenceState.BUSY,
  1086. PresenceState.BUSY,
  1087. ),
  1088. (
  1089. PresenceState.UNAVAILABLE,
  1090. PresenceState.ONLINE,
  1091. PresenceState.ONLINE,
  1092. PresenceState.ONLINE,
  1093. ),
  1094. (
  1095. PresenceState.OFFLINE,
  1096. PresenceState.ONLINE,
  1097. PresenceState.ONLINE,
  1098. PresenceState.ONLINE,
  1099. ),
  1100. (
  1101. PresenceState.OFFLINE,
  1102. PresenceState.UNAVAILABLE,
  1103. PresenceState.UNAVAILABLE,
  1104. PresenceState.UNAVAILABLE,
  1105. ),
  1106. ]
  1107. ],
  1108. name_func=lambda testcase_func, param_num, params: f"{testcase_func.__name__}_{param_num}_{'workers' if params.args[4] else 'monolith'}",
  1109. )
  1110. @unittest.override_config({"experimental_features": {"msc3026_enabled": True}})
  1111. def test_set_presence_from_non_syncing_multi_device(
  1112. self,
  1113. dev_1_state: str,
  1114. dev_2_state: str,
  1115. expected_state_1: str,
  1116. expected_state_2: str,
  1117. test_with_workers: bool,
  1118. ) -> None:
  1119. """
  1120. Test the behaviour of multiple devices syncing at the same time.
  1121. Roughly the user's presence state should be set to the "highest" priority
  1122. of all the devices. When a device then goes offline its state should be
  1123. discarded and the next highest should win.
  1124. Note that these tests use the idle timer (and don't close the syncs), it
  1125. is unlikely that a *single* sync would last this long, but is close enough
  1126. to continually syncing with that current state.
  1127. """
  1128. user_id = f"@test:{self.hs.config.server.server_name}"
  1129. # By default, we call /sync against the main process.
  1130. worker_presence_handler = self.presence_handler
  1131. if test_with_workers:
  1132. # Create a worker and use it to handle /sync traffic instead.
  1133. # This is used to test that presence changes get replicated from workers
  1134. # to the main process correctly.
  1135. worker_to_sync_against = self.make_worker_hs(
  1136. "synapse.app.generic_worker", {"worker_name": "synchrotron"}
  1137. )
  1138. worker_presence_handler = worker_to_sync_against.get_presence_handler()
  1139. # 1. Sync with the first device.
  1140. sync_1 = self.get_success(
  1141. worker_presence_handler.user_syncing(
  1142. user_id,
  1143. "dev-1",
  1144. affect_presence=dev_1_state != PresenceState.OFFLINE,
  1145. presence_state=dev_1_state,
  1146. ),
  1147. by=0.1,
  1148. )
  1149. # 2. Sync with the second device.
  1150. sync_2 = self.get_success(
  1151. worker_presence_handler.user_syncing(
  1152. user_id,
  1153. "dev-2",
  1154. affect_presence=dev_2_state != PresenceState.OFFLINE,
  1155. presence_state=dev_2_state,
  1156. ),
  1157. by=0.1,
  1158. )
  1159. # 3. Assert the expected presence state.
  1160. state = self.get_success(
  1161. self.presence_handler.get_state(UserID.from_string(user_id))
  1162. )
  1163. self.assertEqual(state.state, expected_state_1)
  1164. if test_with_workers:
  1165. state = self.get_success(
  1166. worker_presence_handler.get_state(UserID.from_string(user_id))
  1167. )
  1168. self.assertEqual(state.state, expected_state_1)
  1169. # 4. Disconnect the first device.
  1170. with sync_1:
  1171. pass
  1172. # 5. Advance such that the first device should be discarded (the sync timeout),
  1173. # then pump so _handle_timeouts function to called.
  1174. self.reactor.advance(SYNC_ONLINE_TIMEOUT / 1000)
  1175. self.reactor.pump([5])
  1176. # 6. Assert the expected presence state.
  1177. state = self.get_success(
  1178. self.presence_handler.get_state(UserID.from_string(user_id))
  1179. )
  1180. self.assertEqual(state.state, expected_state_2)
  1181. if test_with_workers:
  1182. state = self.get_success(
  1183. worker_presence_handler.get_state(UserID.from_string(user_id))
  1184. )
  1185. self.assertEqual(state.state, expected_state_2)
  1186. # 7. Disconnect the second device.
  1187. with sync_2:
  1188. pass
  1189. # 8. Advance such that the second device should be discarded (the sync timeout),
  1190. # then pump so _handle_timeouts function to called.
  1191. if dev_1_state == PresenceState.BUSY or dev_2_state == PresenceState.BUSY:
  1192. timeout = BUSY_ONLINE_TIMEOUT
  1193. else:
  1194. timeout = SYNC_ONLINE_TIMEOUT
  1195. self.reactor.advance(timeout / 1000)
  1196. self.reactor.pump([5])
  1197. # 9. There are no more devices, should be offline.
  1198. state = self.get_success(
  1199. self.presence_handler.get_state(UserID.from_string(user_id))
  1200. )
  1201. self.assertEqual(state.state, PresenceState.OFFLINE)
  1202. if test_with_workers:
  1203. state = self.get_success(
  1204. worker_presence_handler.get_state(UserID.from_string(user_id))
  1205. )
  1206. self.assertEqual(state.state, PresenceState.OFFLINE)
  1207. def test_set_presence_from_syncing_keeps_status(self) -> None:
  1208. """Test that presence set by syncing retains status message"""
  1209. status_msg = "I'm here!"
  1210. self._set_presencestate_with_status_msg(PresenceState.UNAVAILABLE, status_msg)
  1211. self.get_success(
  1212. self.presence_handler.user_syncing(
  1213. self.user_id, self.device_id, True, PresenceState.ONLINE
  1214. )
  1215. )
  1216. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  1217. # our status message should be the same as it was before
  1218. self.assertEqual(state.status_msg, status_msg)
  1219. @parameterized.expand([(False,), (True,)])
  1220. @unittest.override_config({"experimental_features": {"msc3026_enabled": True}})
  1221. def test_set_presence_from_syncing_keeps_busy(
  1222. self, test_with_workers: bool
  1223. ) -> None:
  1224. """Test that presence set by syncing doesn't affect busy status
  1225. Args:
  1226. test_with_workers: If True, check the presence state of the user by calling
  1227. /sync against a worker, rather than the main process.
  1228. """
  1229. status_msg = "I'm busy!"
  1230. # By default, we call /sync against the main process.
  1231. worker_to_sync_against = self.hs
  1232. if test_with_workers:
  1233. # Create a worker and use it to handle /sync traffic instead.
  1234. # This is used to test that presence changes get replicated from workers
  1235. # to the main process correctly.
  1236. worker_to_sync_against = self.make_worker_hs(
  1237. "synapse.app.generic_worker", {"worker_name": "synchrotron"}
  1238. )
  1239. # Set presence to BUSY
  1240. self._set_presencestate_with_status_msg(PresenceState.BUSY, status_msg)
  1241. # Perform a sync with a presence state other than busy. This should NOT change
  1242. # our presence status; we only change from busy if we explicitly set it via
  1243. # /presence/*.
  1244. self.get_success(
  1245. worker_to_sync_against.get_presence_handler().user_syncing(
  1246. self.user_id, self.device_id, True, PresenceState.ONLINE
  1247. ),
  1248. by=0.1,
  1249. )
  1250. # Check against the main process that the user's presence did not change.
  1251. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  1252. # we should still be busy
  1253. self.assertEqual(state.state, PresenceState.BUSY)
  1254. # Advance such that the device would be discarded if it was not busy,
  1255. # then pump so _handle_timeouts function to called.
  1256. self.reactor.advance(IDLE_TIMER / 1000)
  1257. self.reactor.pump([5])
  1258. # The account should still be busy.
  1259. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  1260. self.assertEqual(state.state, PresenceState.BUSY)
  1261. # Ensure that a /presence call can set the user *off* busy.
  1262. self._set_presencestate_with_status_msg(PresenceState.ONLINE, status_msg)
  1263. state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  1264. self.assertEqual(state.state, PresenceState.ONLINE)
  1265. def _set_presencestate_with_status_msg(
  1266. self, state: str, status_msg: Optional[str]
  1267. ) -> None:
  1268. """Set a PresenceState and status_msg and check the result.
  1269. Args:
  1270. state: The new PresenceState.
  1271. status_msg: Status message that is to be set.
  1272. """
  1273. self.get_success(
  1274. self.presence_handler.set_state(
  1275. self.user_id_obj,
  1276. self.device_id,
  1277. {"presence": state, "status_msg": status_msg},
  1278. )
  1279. )
  1280. new_state = self.get_success(self.presence_handler.get_state(self.user_id_obj))
  1281. self.assertEqual(new_state.state, state)
  1282. self.assertEqual(new_state.status_msg, status_msg)
  1283. class PresenceFederationQueueTestCase(unittest.HomeserverTestCase):
  1284. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  1285. self.presence_handler = hs.get_presence_handler()
  1286. self.clock = hs.get_clock()
  1287. self.instance_name = hs.get_instance_name()
  1288. self.queue = self.presence_handler.get_federation_queue()
  1289. def test_send_and_get(self) -> None:
  1290. state1 = UserPresenceState.default("@user1:test")
  1291. state2 = UserPresenceState.default("@user2:test")
  1292. state3 = UserPresenceState.default("@user3:test")
  1293. prev_token = self.queue.get_current_token(self.instance_name)
  1294. self.get_success(
  1295. self.queue.send_presence_to_destinations(
  1296. (state1, state2), ("dest1", "dest2")
  1297. )
  1298. )
  1299. self.get_success(
  1300. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  1301. )
  1302. now_token = self.queue.get_current_token(self.instance_name)
  1303. rows, upto_token, limited = self.get_success(
  1304. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  1305. )
  1306. self.assertEqual(upto_token, now_token)
  1307. self.assertFalse(limited)
  1308. expected_rows = [
  1309. (1, ("dest1", "@user1:test")),
  1310. (1, ("dest2", "@user1:test")),
  1311. (1, ("dest1", "@user2:test")),
  1312. (1, ("dest2", "@user2:test")),
  1313. (2, ("dest3", "@user3:test")),
  1314. ]
  1315. self.assertCountEqual(rows, expected_rows)
  1316. now_token = self.queue.get_current_token(self.instance_name)
  1317. rows, upto_token, limited = self.get_success(
  1318. self.queue.get_replication_rows("master", upto_token, now_token, 10)
  1319. )
  1320. self.assertEqual(upto_token, now_token)
  1321. self.assertFalse(limited)
  1322. self.assertCountEqual(rows, [])
  1323. def test_send_and_get_split(self) -> None:
  1324. state1 = UserPresenceState.default("@user1:test")
  1325. state2 = UserPresenceState.default("@user2:test")
  1326. state3 = UserPresenceState.default("@user3:test")
  1327. prev_token = self.queue.get_current_token(self.instance_name)
  1328. self.get_success(
  1329. self.queue.send_presence_to_destinations(
  1330. (state1, state2), ("dest1", "dest2")
  1331. )
  1332. )
  1333. now_token = self.queue.get_current_token(self.instance_name)
  1334. self.get_success(
  1335. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  1336. )
  1337. rows, upto_token, limited = self.get_success(
  1338. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  1339. )
  1340. self.assertEqual(upto_token, now_token)
  1341. self.assertFalse(limited)
  1342. expected_rows = [
  1343. (1, ("dest1", "@user1:test")),
  1344. (1, ("dest2", "@user1:test")),
  1345. (1, ("dest1", "@user2:test")),
  1346. (1, ("dest2", "@user2:test")),
  1347. ]
  1348. self.assertCountEqual(rows, expected_rows)
  1349. now_token = self.queue.get_current_token(self.instance_name)
  1350. rows, upto_token, limited = self.get_success(
  1351. self.queue.get_replication_rows("master", upto_token, now_token, 10)
  1352. )
  1353. self.assertEqual(upto_token, now_token)
  1354. self.assertFalse(limited)
  1355. expected_rows = [
  1356. (2, ("dest3", "@user3:test")),
  1357. ]
  1358. self.assertCountEqual(rows, expected_rows)
  1359. def test_clear_queue_all(self) -> None:
  1360. state1 = UserPresenceState.default("@user1:test")
  1361. state2 = UserPresenceState.default("@user2:test")
  1362. state3 = UserPresenceState.default("@user3:test")
  1363. prev_token = self.queue.get_current_token(self.instance_name)
  1364. self.get_success(
  1365. self.queue.send_presence_to_destinations(
  1366. (state1, state2), ("dest1", "dest2")
  1367. )
  1368. )
  1369. self.get_success(
  1370. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  1371. )
  1372. self.reactor.advance(10 * 60 * 1000)
  1373. now_token = self.queue.get_current_token(self.instance_name)
  1374. rows, upto_token, limited = self.get_success(
  1375. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  1376. )
  1377. self.assertEqual(upto_token, now_token)
  1378. self.assertFalse(limited)
  1379. self.assertCountEqual(rows, [])
  1380. prev_token = self.queue.get_current_token(self.instance_name)
  1381. self.get_success(
  1382. self.queue.send_presence_to_destinations(
  1383. (state1, state2), ("dest1", "dest2")
  1384. )
  1385. )
  1386. self.get_success(
  1387. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  1388. )
  1389. now_token = self.queue.get_current_token(self.instance_name)
  1390. rows, upto_token, limited = self.get_success(
  1391. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  1392. )
  1393. self.assertEqual(upto_token, now_token)
  1394. self.assertFalse(limited)
  1395. expected_rows = [
  1396. (3, ("dest1", "@user1:test")),
  1397. (3, ("dest2", "@user1:test")),
  1398. (3, ("dest1", "@user2:test")),
  1399. (3, ("dest2", "@user2:test")),
  1400. (4, ("dest3", "@user3:test")),
  1401. ]
  1402. self.assertCountEqual(rows, expected_rows)
  1403. def test_partially_clear_queue(self) -> None:
  1404. state1 = UserPresenceState.default("@user1:test")
  1405. state2 = UserPresenceState.default("@user2:test")
  1406. state3 = UserPresenceState.default("@user3:test")
  1407. prev_token = self.queue.get_current_token(self.instance_name)
  1408. self.get_success(
  1409. self.queue.send_presence_to_destinations(
  1410. (state1, state2), ("dest1", "dest2")
  1411. )
  1412. )
  1413. self.reactor.advance(2 * 60 * 1000)
  1414. self.get_success(
  1415. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  1416. )
  1417. self.reactor.advance(4 * 60 * 1000)
  1418. now_token = self.queue.get_current_token(self.instance_name)
  1419. rows, upto_token, limited = self.get_success(
  1420. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  1421. )
  1422. self.assertEqual(upto_token, now_token)
  1423. self.assertFalse(limited)
  1424. self.assertCountEqual(rows, [])
  1425. prev_token = self.queue.get_current_token(self.instance_name)
  1426. self.get_success(
  1427. self.queue.send_presence_to_destinations(
  1428. (state1, state2), ("dest1", "dest2")
  1429. )
  1430. )
  1431. self.get_success(
  1432. self.queue.send_presence_to_destinations((state3,), ("dest3",))
  1433. )
  1434. now_token = self.queue.get_current_token(self.instance_name)
  1435. rows, upto_token, limited = self.get_success(
  1436. self.queue.get_replication_rows("master", prev_token, now_token, 10)
  1437. )
  1438. self.assertEqual(upto_token, now_token)
  1439. self.assertFalse(limited)
  1440. expected_rows = [
  1441. (3, ("dest1", "@user1:test")),
  1442. (3, ("dest2", "@user1:test")),
  1443. (3, ("dest1", "@user2:test")),
  1444. (3, ("dest2", "@user2:test")),
  1445. (4, ("dest3", "@user3:test")),
  1446. ]
  1447. self.assertCountEqual(rows, expected_rows)
  1448. class PresenceJoinTestCase(unittest.HomeserverTestCase):
  1449. """Tests remote servers get told about presence of users in the room when
  1450. they join and when new local users join.
  1451. """
  1452. user_id = "@test:server"
  1453. servlets = [room.register_servlets]
  1454. def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
  1455. hs = self.setup_test_homeserver(
  1456. "server",
  1457. federation_sender=Mock(spec=FederationSender),
  1458. )
  1459. return hs
  1460. def default_config(self) -> JsonDict:
  1461. config = super().default_config()
  1462. # Enable federation sending on the main process.
  1463. config["federation_sender_instances"] = None
  1464. return config
  1465. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  1466. self.federation_sender = cast(Mock, hs.get_federation_sender())
  1467. self.event_builder_factory = hs.get_event_builder_factory()
  1468. self.federation_event_handler = hs.get_federation_event_handler()
  1469. self.presence_handler = hs.get_presence_handler()
  1470. # self.event_builder_for_2 = EventBuilderFactory(hs)
  1471. # self.event_builder_for_2.hostname = "test2"
  1472. self.store = hs.get_datastores().main
  1473. self.state = hs.get_state_handler()
  1474. self._event_auth_handler = hs.get_event_auth_handler()
  1475. # We don't actually check signatures in tests, so lets just create a
  1476. # random key to use.
  1477. self.random_signing_key = generate_signing_key("ver")
  1478. def test_remote_joins(self) -> None:
  1479. # We advance time to something that isn't 0, as we use 0 as a special
  1480. # value.
  1481. self.reactor.advance(1000000000000)
  1482. # Create a room with two local users
  1483. room_id = self.helper.create_room_as(self.user_id)
  1484. self.helper.join(room_id, "@test2:server")
  1485. # Mark test2 as online, test will be offline with a last_active of 0
  1486. self.get_success(
  1487. self.presence_handler.set_state(
  1488. UserID.from_string("@test2:server"),
  1489. "dev-1",
  1490. {"presence": PresenceState.ONLINE},
  1491. )
  1492. )
  1493. self.reactor.pump([0]) # Wait for presence updates to be handled
  1494. #
  1495. # Test that a new server gets told about existing presence
  1496. #
  1497. self.federation_sender.reset_mock()
  1498. # Add a new remote server to the room
  1499. self._add_new_user(room_id, "@alice:server2")
  1500. # When new server is joined we send it the local users presence states.
  1501. # We expect to only see user @test2:server, as @test:server is offline
  1502. # and has a zero last_active_ts
  1503. expected_state = self.get_success(
  1504. self.presence_handler.current_state_for_user("@test2:server")
  1505. )
  1506. self.assertEqual(expected_state.state, PresenceState.ONLINE)
  1507. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  1508. destinations={"server2"}, states=[expected_state]
  1509. )
  1510. #
  1511. # Test that only the new server gets sent presence and not existing servers
  1512. #
  1513. self.federation_sender.reset_mock()
  1514. self._add_new_user(room_id, "@bob:server3")
  1515. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  1516. destinations={"server3"}, states=[expected_state]
  1517. )
  1518. def test_remote_gets_presence_when_local_user_joins(self) -> None:
  1519. # We advance time to something that isn't 0, as we use 0 as a special
  1520. # value.
  1521. self.reactor.advance(1000000000000)
  1522. # Create a room with one local users
  1523. room_id = self.helper.create_room_as(self.user_id)
  1524. # Mark test as online
  1525. self.get_success(
  1526. self.presence_handler.set_state(
  1527. UserID.from_string("@test:server"),
  1528. "dev-1",
  1529. {"presence": PresenceState.ONLINE},
  1530. )
  1531. )
  1532. # Mark test2 as online, test will be offline with a last_active of 0.
  1533. # Note we don't join them to the room yet
  1534. self.get_success(
  1535. self.presence_handler.set_state(
  1536. UserID.from_string("@test2:server"),
  1537. "dev-1",
  1538. {"presence": PresenceState.ONLINE},
  1539. )
  1540. )
  1541. # Add servers to the room
  1542. self._add_new_user(room_id, "@alice:server2")
  1543. self._add_new_user(room_id, "@bob:server3")
  1544. self.reactor.pump([0]) # Wait for presence updates to be handled
  1545. #
  1546. # Test that when a local join happens remote servers get told about it
  1547. #
  1548. self.federation_sender.reset_mock()
  1549. # Join local user to room
  1550. self.helper.join(room_id, "@test2:server")
  1551. self.reactor.pump([0]) # Wait for presence updates to be handled
  1552. # We expect to only send test2 presence to server2 and server3
  1553. expected_state = self.get_success(
  1554. self.presence_handler.current_state_for_user("@test2:server")
  1555. )
  1556. self.assertEqual(expected_state.state, PresenceState.ONLINE)
  1557. self.federation_sender.send_presence_to_destinations.assert_called_once_with(
  1558. destinations={"server2", "server3"}, states=[expected_state]
  1559. )
  1560. def _add_new_user(self, room_id: str, user_id: str) -> None:
  1561. """Add new user to the room by creating an event and poking the federation API."""
  1562. hostname = get_domain_from_id(user_id)
  1563. room_version = self.get_success(self.store.get_room_version_id(room_id))
  1564. builder = EventBuilder(
  1565. state=self.state,
  1566. event_auth_handler=self._event_auth_handler,
  1567. store=self.store,
  1568. clock=self.clock,
  1569. hostname=hostname,
  1570. signing_key=self.random_signing_key,
  1571. room_version=KNOWN_ROOM_VERSIONS[room_version],
  1572. room_id=room_id,
  1573. type=EventTypes.Member,
  1574. sender=user_id,
  1575. state_key=user_id,
  1576. content={"membership": Membership.JOIN},
  1577. )
  1578. prev_event_ids = self.get_success(
  1579. self.store.get_latest_event_ids_in_room(room_id)
  1580. )
  1581. event = self.get_success(
  1582. builder.build(prev_event_ids=list(prev_event_ids), auth_event_ids=None)
  1583. )
  1584. self.get_success(self.federation_event_handler.on_receive_pdu(hostname, event))
  1585. # Check that it was successfully persisted.
  1586. self.get_success(self.store.get_event(event.event_id))
  1587. self.get_success(self.store.get_event(event.event_id))