test_events.py 17 KB

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