test_events.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. # Copyright 2020 The Matrix.org Foundation C.I.C.
  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 synapse.api.constants import EventTypes, Membership
  15. from synapse.api.room_versions import RoomVersions
  16. from synapse.federation.federation_base import event_from_pdu_json
  17. from synapse.rest import admin
  18. from synapse.rest.client import login, room
  19. from tests.unittest import HomeserverTestCase
  20. class ExtremPruneTestCase(HomeserverTestCase):
  21. servlets = [
  22. admin.register_servlets,
  23. room.register_servlets,
  24. login.register_servlets,
  25. ]
  26. def prepare(self, reactor, clock, homeserver):
  27. self.state = self.hs.get_state_handler()
  28. self._persistence = self.hs.get_storage_controllers().persistence
  29. self._state_storage_controller = self.hs.get_storage_controllers().state
  30. self.store = self.hs.get_datastores().main
  31. self.register_user("user", "pass")
  32. self.token = self.login("user", "pass")
  33. self.room_id = self.helper.create_room_as(
  34. "user", room_version=RoomVersions.V6.identifier, tok=self.token
  35. )
  36. body = self.helper.send(self.room_id, body="Test", tok=self.token)
  37. local_message_event_id = body["event_id"]
  38. # Fudge a remote event and persist it. This will be the extremity before
  39. # the gap.
  40. self.remote_event_1 = event_from_pdu_json(
  41. {
  42. "type": EventTypes.Message,
  43. "state_key": "@user:other",
  44. "content": {},
  45. "room_id": self.room_id,
  46. "sender": "@user:other",
  47. "depth": 5,
  48. "prev_events": [local_message_event_id],
  49. "auth_events": [],
  50. "origin_server_ts": self.clock.time_msec(),
  51. },
  52. RoomVersions.V6,
  53. )
  54. self.persist_event(self.remote_event_1)
  55. # Check that the current extremities is the remote event.
  56. self.assert_extremities([self.remote_event_1.event_id])
  57. def persist_event(self, event, state=None):
  58. """Persist the event, with optional state"""
  59. context = self.get_success(
  60. self.state.compute_event_context(
  61. event,
  62. state_ids_before_event=state,
  63. partial_state=None if state is None else False,
  64. )
  65. )
  66. self.get_success(self._persistence.persist_event(event, context))
  67. def assert_extremities(self, expected_extremities):
  68. """Assert the current extremities for the room"""
  69. extremities = self.get_success(
  70. self.store.get_prev_events_for_room(self.room_id)
  71. )
  72. self.assertCountEqual(extremities, expected_extremities)
  73. def test_prune_gap(self):
  74. """Test that we drop extremities after a gap when we see an event from
  75. the same domain.
  76. """
  77. # Fudge a second event which points to an event we don't have. This is a
  78. # state event so that the state changes (otherwise we won't prune the
  79. # extremity as they'll have the same state group).
  80. remote_event_2 = event_from_pdu_json(
  81. {
  82. "type": EventTypes.Member,
  83. "state_key": "@user:other",
  84. "content": {"membership": Membership.JOIN},
  85. "room_id": self.room_id,
  86. "sender": "@user:other",
  87. "depth": 50,
  88. "prev_events": ["$some_unknown_message"],
  89. "auth_events": [],
  90. "origin_server_ts": self.clock.time_msec(),
  91. },
  92. RoomVersions.V6,
  93. )
  94. state_before_gap = self.get_success(
  95. self._state_storage_controller.get_current_state_ids(self.room_id)
  96. )
  97. self.persist_event(remote_event_2, state=state_before_gap)
  98. # Check the new extremity is just the new remote event.
  99. self.assert_extremities([remote_event_2.event_id])
  100. def test_do_not_prune_gap_if_state_different(self):
  101. """Test that we don't prune extremities after a gap if the resolved
  102. state is different.
  103. """
  104. # Fudge a second event which points to an event we don't have.
  105. remote_event_2 = event_from_pdu_json(
  106. {
  107. "type": EventTypes.Message,
  108. "state_key": "@user:other",
  109. "content": {},
  110. "room_id": self.room_id,
  111. "sender": "@user:other",
  112. "depth": 10,
  113. "prev_events": ["$some_unknown_message"],
  114. "auth_events": [],
  115. "origin_server_ts": self.clock.time_msec(),
  116. },
  117. RoomVersions.V6,
  118. )
  119. # Now we persist it with state with a dropped history visibility
  120. # setting. The state resolution across the old and new event will then
  121. # include it, and so the resolved state won't match the new state.
  122. state_before_gap = dict(
  123. self.get_success(
  124. self._state_storage_controller.get_current_state_ids(self.room_id)
  125. )
  126. )
  127. state_before_gap.pop(("m.room.history_visibility", ""))
  128. context = self.get_success(
  129. self.state.compute_event_context(
  130. remote_event_2,
  131. state_ids_before_event=state_before_gap,
  132. partial_state=False,
  133. )
  134. )
  135. self.get_success(self._persistence.persist_event(remote_event_2, context))
  136. # Check that we haven't dropped the old extremity.
  137. self.assert_extremities([self.remote_event_1.event_id, remote_event_2.event_id])
  138. def test_prune_gap_if_old(self):
  139. """Test that we drop extremities after a gap when the previous extremity
  140. is "old"
  141. """
  142. # Advance the clock for many days to make the old extremity "old". We
  143. # also set the depth to "lots".
  144. self.reactor.advance(7 * 24 * 60 * 60)
  145. # Fudge a second event which points to an event we don't have. This is a
  146. # state event so that the state changes (otherwise we won't prune the
  147. # extremity as they'll have the same state group).
  148. remote_event_2 = event_from_pdu_json(
  149. {
  150. "type": EventTypes.Member,
  151. "state_key": "@user:other2",
  152. "content": {"membership": Membership.JOIN},
  153. "room_id": self.room_id,
  154. "sender": "@user:other2",
  155. "depth": 10000,
  156. "prev_events": ["$some_unknown_message"],
  157. "auth_events": [],
  158. "origin_server_ts": self.clock.time_msec(),
  159. },
  160. RoomVersions.V6,
  161. )
  162. state_before_gap = self.get_success(
  163. self._state_storage_controller.get_current_state_ids(self.room_id)
  164. )
  165. self.persist_event(remote_event_2, state=state_before_gap)
  166. # Check the new extremity is just the new remote event.
  167. self.assert_extremities([remote_event_2.event_id])
  168. def test_do_not_prune_gap_if_other_server(self):
  169. """Test that we do not drop extremities after a gap when we see an event
  170. from a different domain.
  171. """
  172. # Fudge a second event which points to an event we don't have. This is a
  173. # state event so that the state changes (otherwise we won't prune the
  174. # extremity as they'll have the same state group).
  175. remote_event_2 = event_from_pdu_json(
  176. {
  177. "type": EventTypes.Member,
  178. "state_key": "@user:other2",
  179. "content": {"membership": Membership.JOIN},
  180. "room_id": self.room_id,
  181. "sender": "@user:other2",
  182. "depth": 10,
  183. "prev_events": ["$some_unknown_message"],
  184. "auth_events": [],
  185. "origin_server_ts": self.clock.time_msec(),
  186. },
  187. RoomVersions.V6,
  188. )
  189. state_before_gap = self.get_success(
  190. self._state_storage_controller.get_current_state_ids(self.room_id)
  191. )
  192. self.persist_event(remote_event_2, state=state_before_gap)
  193. # Check the new extremity is just the new remote event.
  194. self.assert_extremities([self.remote_event_1.event_id, remote_event_2.event_id])
  195. def test_prune_gap_if_dummy_remote(self):
  196. """Test that we drop extremities after a gap when the previous extremity
  197. is a local dummy event and only points to remote events.
  198. """
  199. body = self.helper.send_event(
  200. self.room_id, type=EventTypes.Dummy, content={}, tok=self.token
  201. )
  202. local_message_event_id = body["event_id"]
  203. self.assert_extremities([local_message_event_id])
  204. # Advance the clock for many days to make the old extremity "old". We
  205. # also set the depth to "lots".
  206. self.reactor.advance(7 * 24 * 60 * 60)
  207. # Fudge a second event which points to an event we don't have. This is a
  208. # state event so that the state changes (otherwise we won't prune the
  209. # extremity as they'll have the same state group).
  210. remote_event_2 = event_from_pdu_json(
  211. {
  212. "type": EventTypes.Member,
  213. "state_key": "@user:other2",
  214. "content": {"membership": Membership.JOIN},
  215. "room_id": self.room_id,
  216. "sender": "@user:other2",
  217. "depth": 10000,
  218. "prev_events": ["$some_unknown_message"],
  219. "auth_events": [],
  220. "origin_server_ts": self.clock.time_msec(),
  221. },
  222. RoomVersions.V6,
  223. )
  224. state_before_gap = self.get_success(
  225. self._state_storage_controller.get_current_state_ids(self.room_id)
  226. )
  227. self.persist_event(remote_event_2, state=state_before_gap)
  228. # Check the new extremity is just the new remote event.
  229. self.assert_extremities([remote_event_2.event_id])
  230. def test_prune_gap_if_dummy_local(self):
  231. """Test that we don't drop extremities after a gap when the previous
  232. extremity is a local dummy event and points to local events.
  233. """
  234. body = self.helper.send(self.room_id, body="Test", tok=self.token)
  235. body = self.helper.send_event(
  236. self.room_id, type=EventTypes.Dummy, content={}, tok=self.token
  237. )
  238. local_message_event_id = body["event_id"]
  239. self.assert_extremities([local_message_event_id])
  240. # Advance the clock for many days to make the old extremity "old". We
  241. # also set the depth to "lots".
  242. self.reactor.advance(7 * 24 * 60 * 60)
  243. # Fudge a second event which points to an event we don't have. This is a
  244. # state event so that the state changes (otherwise we won't prune the
  245. # extremity as they'll have the same state group).
  246. remote_event_2 = event_from_pdu_json(
  247. {
  248. "type": EventTypes.Member,
  249. "state_key": "@user:other2",
  250. "content": {"membership": Membership.JOIN},
  251. "room_id": self.room_id,
  252. "sender": "@user:other2",
  253. "depth": 10000,
  254. "prev_events": ["$some_unknown_message"],
  255. "auth_events": [],
  256. "origin_server_ts": self.clock.time_msec(),
  257. },
  258. RoomVersions.V6,
  259. )
  260. state_before_gap = self.get_success(
  261. self._state_storage_controller.get_current_state_ids(self.room_id)
  262. )
  263. self.persist_event(remote_event_2, state=state_before_gap)
  264. # Check the new extremity is just the new remote event.
  265. self.assert_extremities([remote_event_2.event_id, local_message_event_id])
  266. def test_do_not_prune_gap_if_not_dummy(self):
  267. """Test that we do not drop extremities after a gap when the previous extremity
  268. is not a dummy event.
  269. """
  270. body = self.helper.send(self.room_id, body="test", tok=self.token)
  271. local_message_event_id = body["event_id"]
  272. self.assert_extremities([local_message_event_id])
  273. # Fudge a second event which points to an event we don't have. This is a
  274. # state event so that the state changes (otherwise we won't prune the
  275. # extremity as they'll have the same state group).
  276. remote_event_2 = event_from_pdu_json(
  277. {
  278. "type": EventTypes.Member,
  279. "state_key": "@user:other2",
  280. "content": {"membership": Membership.JOIN},
  281. "room_id": self.room_id,
  282. "sender": "@user:other2",
  283. "depth": 10000,
  284. "prev_events": ["$some_unknown_message"],
  285. "auth_events": [],
  286. "origin_server_ts": self.clock.time_msec(),
  287. },
  288. RoomVersions.V6,
  289. )
  290. state_before_gap = self.get_success(
  291. self._state_storage_controller.get_current_state_ids(self.room_id)
  292. )
  293. self.persist_event(remote_event_2, state=state_before_gap)
  294. # Check the new extremity is just the new remote event.
  295. self.assert_extremities([local_message_event_id, remote_event_2.event_id])
  296. class InvalideUsersInRoomCacheTestCase(HomeserverTestCase):
  297. servlets = [
  298. admin.register_servlets,
  299. room.register_servlets,
  300. login.register_servlets,
  301. ]
  302. def prepare(self, reactor, clock, homeserver):
  303. self.state = self.hs.get_state_handler()
  304. self._persistence = self.hs.get_storage_controllers().persistence
  305. self.store = self.hs.get_datastores().main
  306. def test_remote_user_rooms_cache_invalidated(self):
  307. """Test that if the server leaves a room the `get_rooms_for_user` cache
  308. is invalidated for remote users.
  309. """
  310. # Set up a room with a local and remote user in it.
  311. user_id = self.register_user("user", "pass")
  312. token = self.login("user", "pass")
  313. room_id = self.helper.create_room_as(
  314. "user", room_version=RoomVersions.V6.identifier, tok=token
  315. )
  316. body = self.helper.send(room_id, body="Test", tok=token)
  317. local_message_event_id = body["event_id"]
  318. # Fudge a join event for a remote user.
  319. remote_user = "@user:other"
  320. remote_event_1 = event_from_pdu_json(
  321. {
  322. "type": EventTypes.Member,
  323. "state_key": remote_user,
  324. "content": {"membership": Membership.JOIN},
  325. "room_id": room_id,
  326. "sender": remote_user,
  327. "depth": 5,
  328. "prev_events": [local_message_event_id],
  329. "auth_events": [],
  330. "origin_server_ts": self.clock.time_msec(),
  331. },
  332. RoomVersions.V6,
  333. )
  334. context = self.get_success(self.state.compute_event_context(remote_event_1))
  335. self.get_success(self._persistence.persist_event(remote_event_1, context))
  336. # Call `get_rooms_for_user` to add the remote user to the cache
  337. rooms = self.get_success(self.store.get_rooms_for_user(remote_user))
  338. self.assertEqual(set(rooms), {room_id})
  339. # Now we have the local server leave the room, and check that calling
  340. # `get_user_in_room` for the remote user no longer includes the room.
  341. self.helper.leave(room_id, user_id, tok=token)
  342. rooms = self.get_success(self.store.get_rooms_for_user(remote_user))
  343. self.assertEqual(set(rooms), set())
  344. def test_room_remote_user_cache_invalidated(self):
  345. """Test that if the server leaves a room the `get_users_in_room` cache
  346. is invalidated for remote users.
  347. """
  348. # Set up a room with a local and remote user in it.
  349. user_id = self.register_user("user", "pass")
  350. token = self.login("user", "pass")
  351. room_id = self.helper.create_room_as(
  352. "user", room_version=RoomVersions.V6.identifier, tok=token
  353. )
  354. body = self.helper.send(room_id, body="Test", tok=token)
  355. local_message_event_id = body["event_id"]
  356. # Fudge a join event for a remote user.
  357. remote_user = "@user:other"
  358. remote_event_1 = event_from_pdu_json(
  359. {
  360. "type": EventTypes.Member,
  361. "state_key": remote_user,
  362. "content": {"membership": Membership.JOIN},
  363. "room_id": room_id,
  364. "sender": remote_user,
  365. "depth": 5,
  366. "prev_events": [local_message_event_id],
  367. "auth_events": [],
  368. "origin_server_ts": self.clock.time_msec(),
  369. },
  370. RoomVersions.V6,
  371. )
  372. context = self.get_success(self.state.compute_event_context(remote_event_1))
  373. self.get_success(self._persistence.persist_event(remote_event_1, context))
  374. # Call `get_users_in_room` to add the remote user to the cache
  375. users = self.get_success(self.store.get_users_in_room(room_id))
  376. self.assertEqual(set(users), {user_id, remote_user})
  377. # Now we have the local server leave the room, and check that calling
  378. # `get_user_in_room` for the remote user no longer includes the room.
  379. self.helper.leave(room_id, user_id, tok=token)
  380. users = self.get_success(self.store.get_users_in_room(room_id))
  381. self.assertEqual(users, [])