diff --git a/Cargo.lock b/Cargo.lock index 67c4c76d3..b762c6bc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2464,10 +2464,12 @@ version = "0.1.0" dependencies = [ "anyhow", "assign", + "ctor", "matrix-sdk", "once_cell", "tempfile", "tokio", + "tracing-subscriber", ] [[package]] diff --git a/testing/matrix-sdk-integration-testing/Cargo.toml b/testing/matrix-sdk-integration-testing/Cargo.toml index 1085d0af4..5737e323a 100644 --- a/testing/matrix-sdk-integration-testing/Cargo.toml +++ b/testing/matrix-sdk-integration-testing/Cargo.toml @@ -8,7 +8,9 @@ publish = false [dev-dependencies] anyhow = "1" assign = "1" +ctor = "0.1.23" matrix-sdk = { path = "../../crates/matrix-sdk" } once_cell = "1.13.0" tempfile = "3.3.0" tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } +tracing-subscriber = "0.3.15" diff --git a/testing/matrix-sdk-integration-testing/assets/ci-start.sh b/testing/matrix-sdk-integration-testing/assets/ci-start.sh index 6f717938d..baadb5c76 100644 --- a/testing/matrix-sdk-integration-testing/assets/ci-start.sh +++ b/testing/matrix-sdk-integration-testing/assets/ci-start.sh @@ -8,6 +8,8 @@ echo " ====== Patching for CI ====== " sed -i 's/^#enable_registration_without_verification:.*$/enable_registration_without_verification: true/g' /data/homeserver.yaml sed -i 's/^#enable_registration:.*$/enable_registration: true/g' /data/homeserver.yaml echo """ +enable_registration: true +enable_registration_without_verification: true rc_message: per_second: 1000 @@ -16,6 +18,22 @@ rc_message: rc_registration: per_second: 1000 burst_count: 1000 + +rc_joins: + local: + per_second: 1000 + burst_count: 1000 + +rc_invites: + per_room: + per_second: 1000 + burst_count: 1000 + per_user: + per_second: 1000 + burst_count: 1000 + per_issuer: + per_second: 1000 + burst_count: 1000 rc_login: address: diff --git a/testing/matrix-sdk-integration-testing/src/tests.rs b/testing/matrix-sdk-integration-testing/src/tests.rs index 43e9d62be..31f4ea491 100644 --- a/testing/matrix-sdk-integration-testing/src/tests.rs +++ b/testing/matrix-sdk-integration-testing/src/tests.rs @@ -3,8 +3,8 @@ use std::{collections::HashMap, option_env}; use anyhow::Result; use assign::assign; use matrix_sdk::{ + config::RequestConfig, ruma::api::client::{account::register::v3::Request as RegistrationRequest, uiaa}, - store::make_store_config, Client, }; use once_cell::sync::Lazy; @@ -13,6 +13,11 @@ use tokio::sync::Mutex; static USERS: Lazy>> = Lazy::new(Mutex::default); +#[ctor::ctor] +fn init_logging() { + tracing_subscriber::FmtSubscriber::builder().with_test_writer().init(); +} + /// read the test configuration from the environment pub fn test_server_conf() -> (String, String) { ( @@ -21,7 +26,14 @@ pub fn test_server_conf() -> (String, String) { ) } -pub async fn get_client_for_user(username: String) -> Result { +/// The StateStore to use. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Store { + Memory, + Sled, +} + +pub async fn get_client_for_user(store: Store, username: String) -> Result { let mut users = USERS.lock().await; if let Some((client, _)) = users.get(&username) { return Ok(client.clone()); @@ -31,12 +43,15 @@ pub async fn get_client_for_user(username: String) -> Result { let tmp_dir = tempdir()?; - let client = Client::builder() + let mut builder = Client::builder() .user_agent("matrix-sdk-integation-tests") - .store_config(make_store_config(tmp_dir.path(), None)?) .homeserver_url(homeserver_url) - .build() - .await?; + .request_config(RequestConfig::new().disable_retry()); + builder = match store { + Store::Memory => builder, + Store::Sled => builder.sled_store(tmp_dir.path(), None)?, + }; + let client = builder.build().await?; // safe to assume we have not registered this user yet, but ignore if we did if let Err(resp) = client.register(RegistrationRequest::new()).await { @@ -59,3 +74,4 @@ pub async fn get_client_for_user(username: String) -> Result { } mod invitations; +mod repeated_join; diff --git a/testing/matrix-sdk-integration-testing/src/tests/invitations.rs b/testing/matrix-sdk-integration-testing/src/tests/invitations.rs index 14244943f..772a69f4a 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/invitations.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/invitations.rs @@ -6,12 +6,12 @@ use matrix_sdk::{ room::Room, ruma::api::client::room::create_room::v3::Request as CreateRoomRequest, }; -use super::get_client_for_user; +use super::{get_client_for_user, Store}; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_invitation_details() -> Result<()> { - let tamatoa = get_client_for_user("tamatoa".to_owned()).await?; - let sebastian = get_client_for_user("sebastian".to_owned()).await?; + let tamatoa = get_client_for_user(Store::Sled, "tamatoa".to_owned()).await?; + let sebastian = get_client_for_user(Store::Sled, "sebastian".to_owned()).await?; let invites = [sebastian.user_id().expect("sebastian has a userid!").to_owned()]; // create a room and invite sebastian; diff --git a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs new file mode 100644 index 000000000..7d918c7f7 --- /dev/null +++ b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs @@ -0,0 +1,144 @@ +use std::time::Duration; + +use anyhow::Result; +use assign::assign; +use matrix_sdk::{ + event_handler::Ctx, + room::Room, + ruma::{ + api::client::room::create_room::v3::Request as CreateRoomRequest, + events::room::member::{MembershipState, StrippedRoomMemberEvent}, + }, + Client, RoomType, +}; +use tokio::sync::mpsc; + +use super::{get_client_for_user, Store}; + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_repeated_join_leave() -> Result<()> { + let peter = get_client_for_user(Store::Memory, "peter".to_owned()).await?; + // FIXME: Run once with memory, once with sled + let karl = get_client_for_user(Store::Sled, "karl".to_owned()).await?; + let karl_id = karl.user_id().expect("karl has a userid!").to_owned(); + + // Create a room and invite karl. + let invites = [karl_id.clone()]; + let request = assign!(CreateRoomRequest::new(), { + invite: &invites, + is_direct: true, + }); + + // Sync after 1 second to so that create_room receives the event it is waiting + // for. + let peter_clone = peter.clone(); + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs(1)).await; + peter_clone.sync_once(Default::default()).await + }); + + let created_room = peter.create_room(request).await?; + let room_id = created_room.room_id(); + + // Sync karl once to ensure he got the invite. + karl.sync_once(Default::default()).await?; + + // Continuously sync karl from now on. + let karl_clone = karl.clone(); + let join_handle = tokio::spawn(async move { + karl_clone.sync(Default::default()).await; + }); + let (invite_signal_sender, mut invite_signal) = mpsc::channel::<()>(1); + karl.add_event_handler_context(invite_signal_sender); + karl.add_event_handler(signal_on_invite); + + for i in 0..3 { + println!("Iteration {i}"); + + // Test that karl has the expected state in its client. + assert!(karl.get_invited_room(room_id).is_some()); + assert!(karl.get_joined_room(room_id).is_none()); + assert!(karl.get_left_room(room_id).is_none()); + + let room = karl.get_room(room_id).expect("karl has the room"); + let membership = room.get_member_no_sync(&karl_id).await?.expect("karl was invited"); + assert_eq!(*membership.membership(), MembershipState::Invite); + + // Join the room + println!("Joining.."); + let room = + karl.get_invited_room(room_id).expect("karl has the room").accept_invitation().await?; + println!("Done"); + let membership = room.get_member_no_sync(&karl_id).await?.expect("karl joined"); + assert_eq!(*membership.membership(), MembershipState::Join); + + assert!(karl.get_invited_room(room_id).is_none()); + assert!(karl.get_joined_room(room_id).is_some()); + assert!(karl.get_left_room(room_id).is_none()); + + // Leave the room + println!("Leaving.."); + let room = room.leave().await?; + println!("Done"); + let membership = room.get_member_no_sync(&karl_id).await?.expect("karl left"); + assert_eq!(*membership.membership(), MembershipState::Leave); + + assert!(karl.get_invited_room(room_id).is_none()); + assert!(karl.get_joined_room(room_id).is_none()); + assert!(karl.get_left_room(room_id).is_some()); + + // Invite karl again and wait for karl to receive the invite. + println!("Inviting.."); + let room = peter.get_joined_room(room_id).expect("peter created the room!"); + room.invite_user_by_id(&karl_id).await?; + println!("Waiting to receive invite.."); + invite_signal.recv().await.expect("sender must be open"); + } + + // Stop the sync. + join_handle.abort(); + + // Now check the underlying state store that it also has the correct information + // (for when the client restarts). + let invited = karl.store().get_invited_user_ids(room_id).await?; + assert_eq!(invited.len(), 1); + assert_eq!(invited[0], karl_id); + + let joined = karl.store().get_joined_user_ids(room_id).await?; + assert!(!joined.contains(&karl_id)); + + let event = + karl.store().get_member_event(room_id, &karl_id).await?.expect("member event should exist"); + assert_eq!(*event.membership(), MembershipState::Invite); + + // Yay, test succeeded + Ok(()) +} + +async fn signal_on_invite( + event: StrippedRoomMemberEvent, + room: Room, + client: Client, + sender: Ctx>, +) { + let own_id = client.user_id().expect("client is logged in"); + if event.sender == own_id { + return; + } + + if room.room_type() != RoomType::Invited { + return; + } + + if event.content.membership != MembershipState::Invite { + return; + } + + let invited = &event.state_key; + if invited != own_id { + return; + } + + // Send signal that we received an invite. + sender.send(()).await.expect("receiver must be open"); +}