|
| 1 | +use futures_util::{FutureExt, StreamExt}; |
| 2 | +use matrix_sdk::{ |
| 3 | + assert_decrypted_message_eq, assert_next_matches_with_timeout, |
| 4 | + deserialized_responses::{TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason}, |
| 5 | + encryption::EncryptionSettings, |
| 6 | + test_utils::mocks::MatrixMockServer, |
| 7 | +}; |
| 8 | +use matrix_sdk_test::{ |
| 9 | + async_test, event_factory::EventFactory, InvitedRoomBuilder, JoinedRoomBuilder, StateTestEvent, |
| 10 | +}; |
| 11 | +use ruma::{ |
| 12 | + device_id, event_id, events::room::message::RoomMessageEventContent, mxc_uri, room_id, user_id, |
| 13 | +}; |
| 14 | + |
| 15 | +#[async_test] |
| 16 | +async fn test_shared_history_out_of_order() { |
| 17 | + let room_id = room_id!("!test:localhost"); |
| 18 | + let mxid = mxc_uri!("mxc://localhost/12345"); |
| 19 | + |
| 20 | + let alice_user_id = user_id!("@alice:localhost"); |
| 21 | + let alice_device_id = device_id!("ALICEDEVICE"); |
| 22 | + let bob_user_id = user_id!("@bob:localhost"); |
| 23 | + let bob_device_id = device_id!("BOBDEVICE"); |
| 24 | + |
| 25 | + let matrix_mock_server = MatrixMockServer::new().await; |
| 26 | + matrix_mock_server.mock_crypto_endpoints_preset().await; |
| 27 | + matrix_mock_server.mock_invite_user_by_id().ok().mock_once().mount().await; |
| 28 | + |
| 29 | + let encryption_settings = |
| 30 | + EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() }; |
| 31 | + |
| 32 | + let alice = matrix_mock_server |
| 33 | + .client_builder_for_crypto_end_to_end(alice_user_id, alice_device_id) |
| 34 | + .enable_share_history_on_invite() |
| 35 | + .with_encryption_settings(encryption_settings) |
| 36 | + .build() |
| 37 | + .await; |
| 38 | + |
| 39 | + let bob = matrix_mock_server |
| 40 | + .client_builder_for_crypto_end_to_end(bob_user_id, bob_device_id) |
| 41 | + .enable_share_history_on_invite() |
| 42 | + .with_encryption_settings(encryption_settings) |
| 43 | + .build() |
| 44 | + .await; |
| 45 | + |
| 46 | + matrix_mock_server.exchange_e2ee_identities(&alice, &bob).await; |
| 47 | + |
| 48 | + let event_factory = EventFactory::new().room(room_id); |
| 49 | + let alice_member_event = event_factory.member(alice_user_id).into_raw_timeline(); |
| 50 | + |
| 51 | + matrix_mock_server |
| 52 | + .mock_sync() |
| 53 | + .ok_and_run(&alice, |builder| { |
| 54 | + builder.add_joined_room( |
| 55 | + JoinedRoomBuilder::new(room_id) |
| 56 | + .add_state_event(StateTestEvent::Create) |
| 57 | + .add_state_event(StateTestEvent::Encryption), |
| 58 | + ); |
| 59 | + }) |
| 60 | + .await; |
| 61 | + |
| 62 | + let room = |
| 63 | + alice.get_room(room_id).expect("Alice should have access to the room now that we synced"); |
| 64 | + |
| 65 | + let event_id = event_id!("$some_id"); |
| 66 | + let (event_receiver, mock) = |
| 67 | + matrix_mock_server.mock_room_send().ok_with_capture(event_id, alice_user_id); |
| 68 | + |
| 69 | + mock.mock_once().mount().await; |
| 70 | + |
| 71 | + matrix_mock_server |
| 72 | + .mock_get_members() |
| 73 | + .ok(vec![alice_member_event.clone().cast()]) |
| 74 | + .mock_once() |
| 75 | + .mount() |
| 76 | + .await; |
| 77 | + |
| 78 | + let event_id = room |
| 79 | + .send(RoomMessageEventContent::text_plain("It's a secret to everybody")) |
| 80 | + .await |
| 81 | + .expect("We should be able to send an initial message") |
| 82 | + .event_id; |
| 83 | + |
| 84 | + matrix_mock_server.mock_authenticated_media_config().ok_default().mock_once().mount().await; |
| 85 | + |
| 86 | + let (receiver, upload_mock) = matrix_mock_server.mock_upload().ok_with_capture(mxid); |
| 87 | + upload_mock.mock_once().mount().await; |
| 88 | + |
| 89 | + let (_guard, bundle_info) = matrix_mock_server.mock_capture_put_to_device(alice_user_id).await; |
| 90 | + |
| 91 | + room.invite_user_by_id(bob_user_id).await.expect("We should be able to invite Bob"); |
| 92 | + let bundle = receiver.await.expect("We should have received a bundle now."); |
| 93 | + |
| 94 | + let mut bundle_stream = bob |
| 95 | + .encryption() |
| 96 | + .historic_room_key_stream() |
| 97 | + .await |
| 98 | + .expect("We should be able to get the bundle stream"); |
| 99 | + |
| 100 | + let bob_member_event = event_factory.member(alice_user_id).invited(bob_user_id); |
| 101 | + |
| 102 | + matrix_mock_server |
| 103 | + .mock_sync() |
| 104 | + .ok_and_run(&bob, |builder| { |
| 105 | + builder.add_invited_room( |
| 106 | + InvitedRoomBuilder::new(room_id) |
| 107 | + .add_state_event(alice_member_event.cast()) |
| 108 | + .add_state_event(bob_member_event.into_raw_timeline().cast()), |
| 109 | + ); |
| 110 | + }) |
| 111 | + .await; |
| 112 | + |
| 113 | + let bob_room = bob.get_room(room_id).expect("Bob should have access to the invited room"); |
| 114 | + |
| 115 | + matrix_mock_server.mock_room_join(room_id).ok().mock_once().mount().await; |
| 116 | + bob_room.join().await.expect("Bob should be able to join the room"); |
| 117 | + |
| 118 | + let details = bob_room |
| 119 | + .invite_acceptance_details() |
| 120 | + .expect("We should have stored invite acceptance details"); |
| 121 | + |
| 122 | + assert_eq!( |
| 123 | + details.inviter, |
| 124 | + alice.user_id().unwrap(), |
| 125 | + "We should have recorded that Alice has invited us" |
| 126 | + ); |
| 127 | + |
| 128 | + let bundle_info = bundle_info.await; |
| 129 | + matrix_mock_server.mock_media_download().ok_bytes(bundle).mock_once().mount().await; |
| 130 | + |
| 131 | + let mut room_key_stream = bob |
| 132 | + .encryption() |
| 133 | + .room_keys_received_stream() |
| 134 | + .await |
| 135 | + .expect("We should be able to listen to received room keys"); |
| 136 | + |
| 137 | + matrix_mock_server |
| 138 | + .mock_sync() |
| 139 | + .ok_and_run(&bob, |builder| { |
| 140 | + builder.add_to_device_event( |
| 141 | + bundle_info |
| 142 | + .deserialize_as() |
| 143 | + .expect("We should be able to deserialize the bundle info"), |
| 144 | + ); |
| 145 | + }) |
| 146 | + .await; |
| 147 | + |
| 148 | + let bundle_notification = bundle_stream |
| 149 | + .next() |
| 150 | + .now_or_never() |
| 151 | + .flatten() |
| 152 | + .expect("We should have been notiifed about the received bundle"); |
| 153 | + |
| 154 | + assert_eq!(bundle_notification.sender, alice_user_id); |
| 155 | + assert_eq!(bundle_notification.room_id, room_id); |
| 156 | + |
| 157 | + assert_next_matches_with_timeout!(room_key_stream, 1000, Ok(room_key_infos) => assert_eq!(room_key_infos.len(), 1)); |
| 158 | + |
| 159 | + let event = event_receiver.await.expect("We should have received Alice's event"); |
| 160 | + |
| 161 | + matrix_mock_server |
| 162 | + .mock_room_event() |
| 163 | + .room(room_id) |
| 164 | + .match_event_id() |
| 165 | + .ok(TimelineEvent::from_utd( |
| 166 | + event, |
| 167 | + UnableToDecryptInfo { session_id: None, reason: UnableToDecryptReason::Unknown }, |
| 168 | + )) |
| 169 | + .mock_once() |
| 170 | + .mount() |
| 171 | + .await; |
| 172 | + |
| 173 | + let event = bob_room |
| 174 | + .event(&event_id, None) |
| 175 | + .await |
| 176 | + .expect("Bob should be able to fetch the event Alice has sent"); |
| 177 | + |
| 178 | + assert_decrypted_message_eq!( |
| 179 | + event, |
| 180 | + "It's a secret to everybody", |
| 181 | + "The decrypted event should match the message Alice has sent" |
| 182 | + ); |
| 183 | +} |
0 commit comments