test(sdk): De-duplicate shared history integration tests

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
This commit is contained in:
Skye Elliot
2026-03-04 14:19:59 +00:00
parent c62da4a026
commit c864fac823
@@ -1,21 +1,53 @@
use futures_util::{FutureExt, StreamExt};
use matrix_sdk::{
assert_decrypted_message_eq, assert_next_matches_with_timeout,
Client, assert_decrypted_message_eq, assert_next_matches_with_timeout,
deserialized_responses::{TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason},
encryption::EncryptionSettings,
test_utils::mocks::MatrixMockServer,
};
use matrix_sdk_base::crypto::types::events::room::encrypted::EncryptedToDeviceEvent;
use matrix_sdk_test::{
InvitedRoomBuilder, JoinedRoomBuilder, async_test, event_factory::EventFactory,
};
use ruma::{
RoomVersionId, device_id, event_id, events::room::message::RoomMessageEventContent, mxc_uri,
room_id, user_id,
OwnedEventId, RoomVersionId, device_id, event_id,
events::{AnySyncTimelineEvent, room::message::RoomMessageEventContent},
mxc_uri, room_id,
serde::Raw,
user_id,
};
use tempfile::tempdir;
#[async_test]
async fn test_shared_history_out_of_order() {
/// Helper struct to collect test data together.
struct Test {
matrix_mock_server: MatrixMockServer,
alice: Client,
bob: Client,
bob_room: matrix_sdk::Room,
/// The encrypted event Alice sent before Bob joined.
event_id: OwnedEventId,
/// The raw bytes of the uploaded key bundle.
bundle: Vec<u8>,
/// The captured to-device event carrying bundle info.
bundle_info: Raw<EncryptedToDeviceEvent>,
/// Receiver for the original event sent by Alice.
event_receiver: tokio::sync::oneshot::Receiver<Raw<AnySyncTimelineEvent>>,
}
/// Sets up the shared-history scenario up to the point where Bob has joined the
/// room and the bundle info to-device event is ready to be delivered.
///
/// Both tests below share this identical preamble:
///
/// - Server + Alice + Bob clients created
/// - E2EE identities exchanged
/// - Alice creates an encrypted room and sends a message
/// - Alice invites Bob, triggering key bundle upload
/// - Bob syncs the invite and joins
/// - Bundle details are verified
async fn setup_shared_history(
bob_builder_fn: impl FnOnce(matrix_sdk::ClientBuilder) -> matrix_sdk::ClientBuilder,
) -> Test {
let room_id = room_id!("!test:localhost");
let mxid = mxc_uri!("mxc://localhost/12345");
@@ -44,7 +76,7 @@ async fn test_shared_history_out_of_order() {
let bob = matrix_mock_server
.client_builder_for_crypto_end_to_end(bob_user_id, bob_device_id)
.on_builder(|builder| {
builder
bob_builder_fn(builder)
.with_enable_share_history_on_invite(true)
.with_encryption_settings(encryption_settings)
})
@@ -106,12 +138,6 @@ async fn test_shared_history_out_of_order() {
room.invite_user_by_id(bob_user_id).await.expect("We should be able to invite Bob");
let bundle = receiver.await.expect("We should have received a bundle now.");
let mut bundle_stream = bob
.encryption()
.historic_room_key_stream()
.await
.expect("We should be able to get the bundle stream");
let bob_member_event = event_factory.member(alice_user_id).invited(bob_user_id);
matrix_mock_server
@@ -143,6 +169,33 @@ async fn test_shared_history_out_of_order() {
);
let bundle_info = bundle_info.await;
Test { matrix_mock_server, alice, bob, bob_room, event_id, bundle, bundle_info, event_receiver }
}
#[async_test]
async fn test_shared_history_out_of_order() {
let room_id = room_id!("!test:localhost");
let alice_user_id = user_id!("@alice:localhost");
let alice_device_id = device_id!("ALICEDEVICE");
let Test {
matrix_mock_server,
bob,
bob_room,
event_id,
bundle,
bundle_info,
event_receiver,
..
} = setup_shared_history(|builder| builder).await;
let mut bundle_stream = bob
.encryption()
.historic_room_key_stream()
.await
.expect("We should be able to get the bundle stream");
matrix_mock_server
.mock_authed_media_download()
.expect_any_access_token()
@@ -217,132 +270,21 @@ async fn test_shared_history_out_of_order() {
#[async_test]
async fn test_shared_history_crash_before_import() {
let room_id = room_id!("!test:localhost");
let mxid = mxc_uri!("mxc://localhost/12345");
let alice_user_id = user_id!("@alice:localhost");
let alice_device_id = device_id!("ALICEDEVICE");
let bob_user_id = user_id!("@bob:localhost");
let bob_device_id = device_id!("BOBDEVICE");
let matrix_mock_server = MatrixMockServer::new().await;
matrix_mock_server.mock_crypto_endpoints_preset().await;
matrix_mock_server.mock_invite_user_by_id().ok().mock_once().mount().await;
let encryption_settings =
EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() };
let alice = matrix_mock_server
.client_builder_for_crypto_end_to_end(alice_user_id, alice_device_id)
.on_builder(|builder| {
builder
.with_enable_share_history_on_invite(true)
.with_encryption_settings(encryption_settings)
})
.build()
.await;
// Use a common store path for Bob so we can persist invite acceptance details
// over the crash.
let bob_sqlite_path = tempdir().unwrap();
let encryption_settings =
EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() };
let bob = matrix_mock_server
.client_builder_for_crypto_end_to_end(bob_user_id, bob_device_id)
.on_builder(|builder| {
builder
.sqlite_store(bob_sqlite_path.path(), None)
.with_enable_share_history_on_invite(true)
.with_encryption_settings(encryption_settings)
})
.build()
.await;
let Test {
matrix_mock_server, alice, bob, event_id, bundle, bundle_info, event_receiver, ..
} = setup_shared_history(|builder| builder.sqlite_store(bob_sqlite_path.path(), None)).await;
matrix_mock_server.exchange_e2ee_identities(&alice, &bob).await;
let event_factory = EventFactory::new().room(room_id).sender(alice_user_id);
let alice_member_event = event_factory.member(alice_user_id).into_raw();
matrix_mock_server
.mock_sync()
.ok_and_run(&alice, |builder| {
builder.add_joined_room(
JoinedRoomBuilder::new(room_id)
.add_state_event(event_factory.create(alice_user_id, RoomVersionId::V1))
.add_state_event(event_factory.room_encryption()),
);
})
.await;
let room =
alice.get_room(room_id).expect("Alice should have access to the room now that we synced");
let event_id = event_id!("$some_id");
let (event_receiver, mock) =
matrix_mock_server.mock_room_send().ok_with_capture(event_id, alice_user_id);
mock.mock_once().named("send").mount().await;
matrix_mock_server
.mock_get_members()
.ok(vec![alice_member_event.clone()])
.mock_once()
.mount()
.await;
let event_id = room
.send(RoomMessageEventContent::text_plain("It's a secret to everybody"))
.await
.expect("We should be able to send an initial message")
.response
.event_id;
matrix_mock_server
.mock_authenticated_media_config()
.ok_default()
.mock_once()
.named("media_config")
.mount()
.await;
let (receiver, upload_mock) = matrix_mock_server.mock_upload().ok_with_capture(mxid);
upload_mock.mock_once().mount().await;
let (_guard, bundle_info) = matrix_mock_server.mock_capture_put_to_device(alice_user_id).await;
room.invite_user_by_id(bob_user_id).await.expect("We should be able to invite Bob");
let bundle = receiver.await.expect("We should have received a bundle now.");
let bob_member_event = event_factory.member(alice_user_id).invited(bob_user_id);
matrix_mock_server
.mock_sync()
.ok_and_run(&bob, |builder| {
builder.add_invited_room(
InvitedRoomBuilder::new(room_id)
.add_state_event(alice_member_event.cast())
.add_state_event(bob_member_event),
);
})
.await;
let bob_room = bob.get_room(room_id).expect("Bob should have access to the invited room");
matrix_mock_server.mock_room_join(room_id).ok().mock_once().named("join").mount().await;
bob_room.join().await.expect("Bob should be able to join the room");
// Bob should have persisted the room bundle state.
let details = bob
.get_pending_key_bundle_details_for_room(room_id)
.await
.expect("Bob should be able to get the pending key bundle details for the room")
.expect("We should have stored invite acceptance details");
assert_eq!(
details.inviter,
alice.user_id().unwrap(),
"We should have recorded that Alice has invited us"
);
let bundle_info = bundle_info.await;
matrix_mock_server
.mock_authed_media_download()
.expect_any_access_token()
@@ -380,6 +322,9 @@ async fn test_shared_history_crash_before_import() {
.flatten()
.expect("We should have been notified about the received bundle");
assert_eq!(bundle_notification.sender, alice_user_id);
assert_eq!(bundle_notification.room_id, room_id);
// .. but crashes before they finish importing the bundle.
drop(bob);
@@ -409,9 +354,6 @@ async fn test_shared_history_crash_before_import() {
"The persisted pending key bundle details should be identical"
);
assert_eq!(bundle_notification.sender, alice_user_id);
assert_eq!(bundle_notification.room_id, room_id);
// Bob's restarted client should successfully import the room keys.
let mut room_key_stream = bob_restarted
.encryption()