test_events.py 17 KB

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