diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c3bb757f..fb813b1a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -175,7 +175,7 @@ jobs: steps: - name: Checkout the repo uses: actions/checkout@v2 - + - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -184,24 +184,24 @@ jobs: components: clippy profile: minimal override: true - + - name: Install wasm-pack uses: jetli/wasm-pack-action@v0.3.0 with: version: latest - + - name: Load cache uses: Swatinem/rust-cache@v1 - + - name: Install nextest uses: taiki-e/install-action@nextest - + - name: Rust Check uses: actions-rs/cargo@v1 with: command: run args: -p xtask -- ci wasm ${{ matrix.cmd }} - + - name: Wasm-Pack test uses: actions-rs/cargo@v1 with: diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index ae298d19d..56d740b91 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -2193,62 +2193,18 @@ impl Client { // mockito (the http mocking library) is not supported for wasm32 #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod tests { - use matrix_sdk_test::async_test; + use std::time::Duration; + + use matrix_sdk_test::{async_test, test_json, EventBuilder, EventsJson}; #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - use std::{collections::BTreeMap, convert::TryInto, io::Cursor, str::FromStr, time::Duration}; - - use matrix_sdk_base::{ - media::{MediaFormat, MediaRequest, MediaThumbnailSize}, - DisplayName, - }; - #[cfg(feature = "experimental-timeline")] - use matrix_sdk_common::deserialized_responses::SyncRoomEvent; - use matrix_sdk_test::{test_json, EventBuilder, EventsJson}; use mockito::{mock, Matcher}; - use ruma::{ - api::{ - client::{ - self as client_api, - account::register::{v3::Request as RegistrationRequest, RegistrationKind}, - directory::{ - get_public_rooms, - get_public_rooms_filtered::{self, v3::Request as PublicRoomsFilterRequest}, - }, - media::get_content_thumbnail::v3::Method, - membership::Invite3pidInit, - session::get_login_types::v3::LoginType, - uiaa::{self, UiaaResponse}, - }, - error::{FromHttpResponseError, ServerError}, - MatrixVersion, - }, - assign, device_id, - directory::Filter, - event_id, - events::{ - room::{ - message::{ImageMessageEventContent, RoomMessageEventContent}, - ImageInfo, MediaSource, - }, - AnySyncStateEvent, StateEventType, - }, - mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, UserId, - }; - use serde_json::{json, Value as JsonValue}; + use ruma::{api::MatrixVersion, device_id, room_id, user_id, UserId}; use url::Url; use super::{Client, ClientBuilder, Session}; - use crate::{ - attachment::{ - AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, - Thumbnail, - }, - config::{RequestConfig, SyncSettings}, - error::RumaApiError, - HttpError, RoomMember, - }; + use crate::config::{RequestConfig, SyncSettings}; fn test_client_builder() -> ClientBuilder { let homeserver = Url::parse(&mockito::server_url()).unwrap(); @@ -2276,12 +2232,21 @@ pub(crate) mod tests { } #[async_test] - async fn set_homeserver() { - let client = no_retry_test_client().await; - let homeserver = Url::from_str("http://example.com/").unwrap(); - client.set_homeserver(homeserver.clone()).await; + async fn account_data() { + let client = logged_in_client().await; - assert_eq!(client.homeserver().await, homeserver); + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + let _response = client.sync_once(sync_settings).await.unwrap(); + + // let bc = &client.base_client; + // let ignored_users = bc.ignored_users.read().await; + // assert_eq!(1, ignored_users.len()) } #[async_test] @@ -2320,230 +2285,6 @@ pub(crate) mod tests { ); } - #[async_test] - async fn login() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = no_retry_test_client().await; - - let _m_types = mock("GET", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_TYPES.to_string()) - .create(); - - let can_password = client - .get_login_types() - .await - .unwrap() - .flows - .iter() - .any(|flow| matches!(flow, LoginType::Password(_))); - assert!(can_password); - - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); - - client.login_username("example", "wordpass").send().await.unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - - assert_eq!(client.homeserver().await, homeserver); - } - - #[async_test] - async fn login_with_discovery() { - let client = no_retry_test_client().await; - - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_WITH_DISCOVERY.to_string()) - .create(); - - client.login_username("example", "wordpass").send().await.unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - - assert_eq!(client.homeserver().await.as_str(), "https://example.org/"); - } - - #[async_test] - async fn login_no_discovery() { - let client = no_retry_test_client().await; - - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); - - client.login_username("example", "wordpass").send().await.unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - - assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap()); - } - - #[async_test] - #[cfg(feature = "sso-login")] - async fn login_with_sso() { - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); - - let _homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = no_retry_test_client().await; - let idp = crate::client::get_login_types::v3::IdentityProvider::new( - "some-id".to_owned(), - "idp-name".to_owned(), - ); - client - .login_sso(|sso_url| async move { - let sso_url = Url::parse(&sso_url).unwrap(); - - let (_, redirect) = - sso_url.query_pairs().find(|(key, _)| key == "redirectUrl").unwrap(); - - let mut redirect_url = Url::parse(&redirect).unwrap(); - redirect_url.set_query(Some("loginToken=tinytoken")); - - reqwest::get(redirect_url.to_string()).await.unwrap(); - - Ok(()) - }) - .identity_provider_id(&idp.id) - .send() - .await - .unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - } - - #[async_test] - async fn login_with_sso_token() { - let client = no_retry_test_client().await; - - let _m = mock("GET", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_TYPES.to_string()) - .create(); - - let can_sso = client - .get_login_types() - .await - .unwrap() - .flows - .iter() - .any(|flow| matches!(flow, LoginType::Sso(_))); - assert!(can_sso); - - let sso_url = client.get_sso_login_url("http://127.0.0.1:3030", None).await; - assert!(sso_url.is_ok()); - - let _m = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); - - client.login_token("averysmalltoken").send().await.unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - } - - #[async_test] - async fn devices() { - let client = logged_in_client().await; - - let _m = mock("GET", "/_matrix/client/r0/devices") - .with_status(200) - .with_body(test_json::DEVICES.to_string()) - .create(); - - assert!(client.devices().await.is_ok()); - } - - #[async_test] - async fn resolve_room_alias() { - let client = no_retry_test_client().await; - - let _m = mock("GET", "/_matrix/client/r0/directory/room/%23alias%3Aexample%2Eorg") - .with_status(200) - .with_body(test_json::GET_ALIAS.to_string()) - .create(); - - let alias = ruma::room_alias_id!("#alias:example.org"); - assert!(client.resolve_room_alias(alias).await.is_ok()); - } - - #[async_test] - async fn test_join_leave_room() { - let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .create(); - - let client = logged_in_client().await; - let session = client.session().unwrap().clone(); - - let room = client.get_joined_room(room_id); - assert!(room.is_none()); - - client.sync_once(SyncSettings::default()).await.unwrap(); - - let room = client.get_left_room(room_id); - assert!(room.is_none()); - - let room = client.get_joined_room(room_id); - assert!(room.is_some()); - - // test store reloads with correct room state from the state store - let joined_client = no_retry_test_client().await; - joined_client.restore_login(session).await.unwrap(); - - // joined room reloaded from state store - joined_client.sync_once(SyncSettings::default()).await.unwrap(); - let room = joined_client.get_joined_room(room_id); - assert!(room.is_some()); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::LEAVE_SYNC_EVENT.to_string()) - .create(); - - joined_client.sync_once(SyncSettings::default()).await.unwrap(); - - let room = joined_client.get_joined_room(room_id); - assert!(room.is_none()); - - let room = joined_client.get_left_room(room_id); - assert!(room.is_some()); - } - - #[async_test] - async fn account_data() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync_once(sync_settings).await.unwrap(); - - // let bc = &client.base_client; - // let ignored_users = bc.ignored_users.read().await; - // assert_eq!(1, ignored_users.len()) - } - #[async_test] async fn room_creation() { let client = logged_in_client().await; @@ -2562,928 +2303,6 @@ pub(crate) mod tests { assert!(room.is_some()); } - #[async_test] - async fn login_error() { - let client = no_retry_test_client().await; - - let _m = mock("POST", "/_matrix/client/r0/login") - .with_status(403) - .with_body(test_json::LOGIN_RESPONSE_ERR.to_string()) - .create(); - - if let Err(err) = client.login_username("example", "wordpass").send().await { - if let crate::Error::Http(HttpError::Api(FromHttpResponseError::Server( - ServerError::Known(RumaApiError::ClientApi(client_api::Error { - kind, - message, - status_code, - })), - ))) = err - { - if let client_api::error::ErrorKind::Forbidden = kind { - } else { - panic!("found the wrong `ErrorKind` {:?}, expected `Forbidden", kind); - } - assert_eq!(message, "Invalid password".to_owned()); - assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap()); - } else { - panic!("found the wrong `Error` type {:?}, expected `Error::RumaResponse", err); - } - } else { - panic!("this request should return an `Err` variant") - } - } - - #[async_test] - async fn register_error() { - let client = no_retry_test_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_owned())) - .with_status(403) - .with_body(test_json::REGISTRATION_RESPONSE_ERR.to_string()) - .create(); - - let user = assign!(RegistrationRequest::new(), { - username: Some("user"), - password: Some("password"), - auth: Some(uiaa::AuthData::FallbackAcknowledgement( - uiaa::FallbackAcknowledgement::new("foobar"), - )), - kind: RegistrationKind::User, - }); - - if let Err(err) = client.register(user).await { - if let HttpError::UiaaError(FromHttpResponseError::Server(ServerError::Known( - UiaaResponse::MatrixError(client_api::Error { kind, message, status_code }), - ))) = err - { - if let client_api::error::ErrorKind::Forbidden = kind { - } else { - panic!("found the wrong `ErrorKind` {:?}, expected `Forbidden", kind); - } - assert_eq!(message, "Invalid password".to_owned()); - assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap()); - } else { - panic!("found the wrong `Error` type {:#?}, expected `UiaaResponse`", err); - } - } else { - panic!("this request should return an `Err` variant") - } - } - - #[async_test] - async fn join_room_by_id() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/join".to_owned())) - .with_status(200) - .with_body(test_json::ROOM_ID.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let room_id = room_id!("!testroom:example.org"); - - assert_eq!( - // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId - // field - client.join_room_by_id(room_id).await.unwrap().room_id, - room_id - ); - } - - #[async_test] - async fn join_room_by_id_or_alias() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/join/".to_owned())) - .with_status(200) - .with_body(test_json::ROOM_ID.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let room_id = room_id!("!testroom:example.org").into(); - - assert_eq!( - // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId - // field - client - .join_room_by_id_or_alias(room_id, &["server.com".try_into().unwrap()]) - .await - .unwrap() - .room_id, - room_id!("!testroom:example.org") - ); - } - - #[async_test] - async fn invite_user_by_id() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) - .with_status(200) - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let user = user_id!("@example:localhost"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.invite_user_by_id(user).await.unwrap(); - } - - #[async_test] - async fn invite_user_by_3pid() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) - .with_status(200) - // empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.invite_user_by_3pid( - Invite3pidInit { - id_server: "example.org", - id_access_token: "IdToken", - medium: thirdparty::Medium::Email, - address: "address", - } - .into(), - ) - .await - .unwrap(); - } - - #[async_test] - async fn room_search_all() { - let client = no_retry_test_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) - .with_status(200) - .with_body(test_json::PUBLIC_ROOMS.to_string()) - .create(); - - let get_public_rooms::v3::Response { chunk, .. } = - client.public_rooms(Some(10), None, None).await.unwrap(); - assert_eq!(chunk.len(), 1); - } - - #[async_test] - async fn room_search_filtered() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) - .with_status(200) - .with_body(test_json::PUBLIC_ROOMS.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let generic_search_term = Some("cheese"); - let filter = assign!(Filter::new(), { generic_search_term }); - let request = assign!(PublicRoomsFilterRequest::new(), { filter }); - - let get_public_rooms_filtered::v3::Response { chunk, .. } = - client.public_rooms_filtered(request).await.unwrap(); - assert_eq!(chunk.len(), 1); - } - - #[async_test] - async fn leave_room() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/leave".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.leave().await.unwrap(); - } - - #[async_test] - async fn ban_user() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/ban".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let user = user_id!("@example:localhost"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.ban_user(user, None).await.unwrap(); - } - - #[async_test] - async fn kick_user() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/kick".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let user = user_id!("@example:localhost"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.kick_user(user, None).await.unwrap(); - } - - #[async_test] - async fn forget_room() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/forget".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::LEAVE_SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.forget().await.unwrap(); - } - - #[async_test] - async fn read_receipt() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/receipt".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let event_id = event_id!("$xxxxxx:example.org"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.read_receipt(event_id).await.unwrap(); - } - - #[async_test] - async fn read_marker() { - let client = logged_in_client().await; - - let _m = - mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/read_markers".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let event_id = event_id!("$xxxxxx:example.org"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.read_marker(event_id, None).await.unwrap(); - } - - #[async_test] - async fn typing_notice() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/typing".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.typing_notice(true).await.unwrap(); - } - - #[async_test] - async fn room_state_event_send() { - use ruma::events::room::member::{MembershipState, RoomMemberEventContent}; - - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/state/.*".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - - let room = client.get_joined_room(room_id).unwrap(); - - let avatar_url = mxc_uri!("mxc://example.org/avA7ar"); - let member_event = assign!(RoomMemberEventContent::new(MembershipState::Join), { - avatar_url: Some(avatar_url.to_owned()) - }); - let response = room.send_state_event(member_event, "").await.unwrap(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id); - } - - #[async_test] - async fn room_message_send() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let content = RoomMessageEventContent::text_plain("Hello world"); - let txn_id = TransactionId::new(); - let response = room.send(content, Some(&txn_id)).await.unwrap(); - - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn room_attachment_send() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ - "info": { - "mimetype": "image/jpeg" - } - }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let mut media = Cursor::new("Hello world"); - - let response = room - .send_attachment("image", &mime::IMAGE_JPEG, &mut media, AttachmentConfig::new()) - .await - .unwrap(); - - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn room_attachment_send_info() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ - "info": { - "mimetype": "image/jpeg", - "h": 600, - "w": 800, - } - }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let mut media = Cursor::new("Hello world"); - - let config = AttachmentConfig::new().info(AttachmentInfo::Image(BaseImageInfo { - height: Some(uint!(600)), - width: Some(uint!(800)), - size: None, - blurhash: None, - })); - - let response = - room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); - - upload_mock.assert(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn room_attachment_send_wrong_info() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ - "info": { - "mimetype": "image/jpeg", - "h": 600, - "w": 800, - } - }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let mut media = Cursor::new("Hello world"); - - let config = AttachmentConfig::new().info(AttachmentInfo::Video(BaseVideoInfo { - height: Some(uint!(600)), - width: Some(uint!(800)), - duration: Some(Duration::from_millis(3600)), - size: None, - blurhash: None, - })); - - let response = room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await; - - assert!(response.is_err()) - } - - #[async_test] - async fn room_attachment_send_info_thumbnail() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ - "info": { - "mimetype": "image/jpeg", - "h": 600, - "w": 800, - "thumbnail_info": { - "h": 360, - "w": 480, - "mimetype":"image/jpeg", - "size": 3600, - }, - "thumbnail_url": "mxc://example.com/AQwafuaFswefuhsfAFAgsw", - } - }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .expect(2) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let mut media = Cursor::new("Hello world"); - - let mut thumbnail_reader = Cursor::new("Thumbnail"); - - let config = AttachmentConfig::with_thumbnail(Thumbnail { - reader: &mut thumbnail_reader, - content_type: &mime::IMAGE_JPEG, - info: Some(BaseThumbnailInfo { - height: Some(uint!(360)), - width: Some(uint!(480)), - size: Some(uint!(3600)), - }), - }) - .info(AttachmentInfo::Image(BaseImageInfo { - height: Some(uint!(600)), - width: Some(uint!(800)), - size: None, - blurhash: None, - })); - - let response = - room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); - - upload_mock.assert(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn room_redact() { - let client = logged_in_client().await; - - let _m = - mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let event_id = event_id!("$xxxxxxxx:example.com"); - - let txn_id = TransactionId::new(); - let reason = Some("Indecent material"); - let response = room.redact(event_id, reason, Some(txn_id)).await.unwrap(); - - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn user_presence() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/members".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::MEMBERS.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - let members: Vec = room.active_members().await.unwrap(); - - assert_eq!(2, members.len()); - // assert!(room.power_levels.is_some()) - } - - #[async_test] - async fn calculate_room_names_from_summary() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::DEFAULT_SYNC_SUMMARY.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync_once(sync_settings).await.unwrap(); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - assert_eq!( - DisplayName::Calculated("example2".to_owned()), - room.display_name().await.unwrap() - ); - } - - #[async_test] - async fn invited_rooms() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::INVITE_SYNC.to_string()) - .create(); - - let _response = client.sync_once(SyncSettings::default()).await.unwrap(); - - assert!(client.joined_rooms().is_empty()); - assert!(client.left_rooms().is_empty()); - assert!(!client.invited_rooms().is_empty()); - - assert!(client.get_invited_room(room_id!("!696r7674:example.com")).is_some()); - } - - #[async_test] - async fn left_rooms() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::LEAVE_SYNC.to_string()) - .create(); - - let _response = client.sync_once(SyncSettings::default()).await.unwrap(); - - assert!(client.joined_rooms().is_empty()); - assert!(!client.left_rooms().is_empty()); - assert!(client.invited_rooms().is_empty()); - - assert!(client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).is_some()) - } - - #[async_test] - async fn sync() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let response = client.sync_once(sync_settings).await.unwrap(); - - assert_ne!(response.next_batch, ""); - - assert!(client.sync_token().await.is_some()); - } - - #[async_test] - async fn room_names() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .expect_at_least(1) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - assert_eq!(client.rooms().len(), 1); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - assert_eq!(DisplayName::Aliased("tutorial".to_owned()), room.display_name().await.unwrap()); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::INVITE_SYNC.to_string()) - .expect_at_least(1) - .create(); - - let _response = client.sync_once(SyncSettings::new()).await.unwrap(); - - assert_eq!(client.rooms().len(), 1); - let invited_room = client.get_invited_room(room_id!("!696r7674:example.com")).unwrap(); - - assert_eq!( - DisplayName::Named("My Room Name".to_owned()), - invited_room.display_name().await.unwrap() - ); - } - - #[async_test] - async fn delete_devices() { - let client = no_retry_test_client().await; - - let _m = mock("POST", "/_matrix/client/r0/delete_devices") - .with_status(401) - .with_body( - json!({ - "flows": [ - { - "stages": [ - "m.login.password" - ] - } - ], - "params": {}, - "session": "vBslorikviAjxzYBASOBGfPp" - }) - .to_string(), - ) - .create(); - - let _m = mock("POST", "/_matrix/client/r0/delete_devices") - .with_status(401) - // empty response - // TODO rename that response type. - .with_body(test_json::LOGOUT.to_string()) - .create(); - - let devices = &[device_id!("DEVICEID").to_owned()]; - - if let Err(e) = client.delete_devices(devices, None).await { - if let Some(info) = e.uiaa_response() { - let mut auth_parameters = BTreeMap::new(); - - let identifier = json!({ - "type": "m.id.user", - "user": "example", - }); - auth_parameters.insert("identifier".to_owned(), identifier); - auth_parameters.insert("password".to_owned(), "wordpass".into()); - - let auth_data = uiaa::AuthData::Password(assign!( - uiaa::Password::new( - uiaa::UserIdentifier::UserIdOrLocalpart("example"), - "wordpass", - ), { - session: info.session.as_deref(), - } - )); - - client.delete_devices(devices, Some(auth_data)).await.unwrap(); - } - } - } - #[async_test] async fn retry_limit_http_requests() { let client = test_client_builder() @@ -3551,819 +2370,4 @@ pub(crate) mod tests { panic!("this request should return an `Err` variant") } } - - #[async_test] - async fn get_media_content() { - let client = logged_in_client().await; - - let request = MediaRequest { - source: MediaSource::Plain(mxc_uri!("mxc://localhost/textfile").to_owned()), - format: MediaFormat::File, - }; - - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/download/localhost/textfile\?.*$".to_owned()), - ) - .with_status(200) - .with_body("Some very interesting text.") - .expect(2) - .create(); - - assert!(client.get_media_content(&request, true).await.is_ok()); - assert!(client.get_media_content(&request, true).await.is_ok()); - assert!(client.get_media_content(&request, false).await.is_ok()); - m.assert(); - } - - #[async_test] - async fn get_media_file() { - let client = logged_in_client().await; - - let event_content = ImageMessageEventContent::plain( - "filename.jpg".into(), - mxc_uri!("mxc://example.org/image").to_owned(), - Some(Box::new(assign!(ImageInfo::new(), { - height: Some(uint!(398)), - width: Some(uint!(394)), - mimetype: Some("image/jpeg".into()), - size: Some(uint!(31037)), - }))), - ); - - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/download/example%2Eorg/image\?.*$".to_owned()), - ) - .with_status(200) - .with_body("binaryjpegdata") - .create(); - - assert!(client.get_file(event_content.clone(), true).await.is_ok()); - assert!(client.get_file(event_content.clone(), true).await.is_ok()); - m.assert(); - - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/thumbnail/example%2Eorg/image\?.*$".to_owned()), - ) - .with_status(200) - .with_body("smallerbinaryjpegdata") - .create(); - - assert!(client - .get_thumbnail( - event_content, - MediaThumbnailSize { method: Method::Scale, width: uint!(100), height: uint!(100) }, - true - ) - .await - .is_ok()); - m.assert(); - } - - #[async_test] - async fn whoami() { - let client = logged_in_client().await; - - let _m = mock("GET", "/_matrix/client/r0/account/whoami") - .with_status(200) - .with_body(test_json::WHOAMI.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let user_id = user_id!("@joe:example.org"); - - assert_eq!(client.whoami().await.unwrap().user_id, user_id); - } - - #[async_test] - async fn test_state_event_getting() { - let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - - let session = Session { - access_token: "1234".to_owned(), - user_id: user_id!("@example:localhost").to_owned(), - device_id: device_id!("DEVICEID").to_owned(), - }; - - let sync = json!({ - "next_batch": "1234", - "rooms": { - "join": { - "!SVkFJHzfwvuaIEawgC:localhost": { - "state": { - "events": [ - { - "type": "m.custom.note", - "sender": "@example:localhost", - "content": { - "body": "Note 1", - }, - "state_key": "note.1", - "origin_server_ts": 1611853078727u64, - "unsigned": { - "replaces_state": "$2s9GcbVxbbFS3EZY9vN1zhavaDJnF32cAIGAxi99NuQ", - "age": 15458166523u64 - }, - "event_id": "$NVCTvrlxodf3ZGjJ6foxepEq8ysSkTq8wG0wKeQBVZg" - }, - { - "type": "m.custom.note", - "sender": "@example2:localhost", - "content": { - "body": "Note 2", - }, - "state_key": "note.2", - "origin_server_ts": 1611853078727u64, - "unsigned": { - "replaces_state": "$2s9GcbVxbbFS3EZY9vN1zhavaDJnF32cAIGAxi99NuQ", - "age": 15458166523u64 - }, - "event_id": "$NVCTvrlxodf3ZGjJ6foxepEq8ysSkTq8wG0wKeQBVZg" - }, - { - "type": "m.room.encryption", - "sender": "@example:localhost", - "content": { - "algorithm": "m.megolm.v1.aes-sha2" - }, - "state_key": "", - "origin_server_ts": 1586437448151u64, - "unsigned": { - "age": 40873797099u64 - }, - "event_id": "$vyG3wu1QdJSh5gc-09SwjXBXlXo8gS7s4QV_Yxha0Xw" - }, - ] - } - } - } - } - }); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(sync.to_string()) - .create(); - - let client = test_client_builder() - .request_config(RequestConfig::new().retry_limit(3)) - .build() - .await - .unwrap(); - client.restore_login(session.clone()).await.unwrap(); - - let room = client.get_joined_room(room_id); - assert!(room.is_none()); - - client.sync_once(SyncSettings::default()).await.unwrap(); - - let room = client.get_joined_room(room_id).unwrap(); - - let state_events = room.get_state_events(StateEventType::RoomEncryption).await.unwrap(); - assert_eq!(state_events.len(), 1); - - let state_events = room.get_state_events("m.custom.note".into()).await.unwrap(); - assert_eq!(state_events.len(), 2); - - let encryption_event = room - .get_state_event(StateEventType::RoomEncryption, "") - .await - .unwrap() - .unwrap() - .deserialize() - .unwrap(); - - matches::assert_matches!(encryption_event, AnySyncStateEvent::RoomEncryption(_)); - } - - // FIXME: removing timelines during reading the stream currently leaves to an - // inconsistent undefined state. This tests shows that, but because - // different implementations deal with problem in different, - // inconsistent manners, isn't activated. - //#[async_test] - #[allow(dead_code)] - #[cfg(feature = "experimental-timeline")] - async fn room_timeline_with_remove() { - let client = logged_in_client().await; - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _ = client.sync_once(sync_settings).await.unwrap(); - sync.assert(); - drop(sync); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - let (forward_stream, backward_stream) = room.timeline().await.unwrap(); - - // these two syncs lead to the store removing its existing timeline - // and replace them with new ones - let sync_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_1.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let sync_3 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_1".to_owned())); - let sync_settings = SyncSettings::new() - .timeout(Duration::from_millis(3000)) - .token("s526_47314_0_7_1_1_1_11444_1"); - let _ = client.sync_once(sync_settings).await.unwrap(); - sync_2.assert(); - let sync_settings = SyncSettings::new() - .timeout(Duration::from_millis(3000)) - .token("s526_47314_0_7_1_1_1_11444_2"); - let _ = client.sync_once(sync_settings).await.unwrap(); - sync_3.assert(); - - let expected_forward_events = vec![ - "$152037280074GZeOm:localhost", - "$editevid:localhost", - "$151957878228ssqrJ:localhost", - "$15275046980maRLj:localhost", - "$15275047031IXQRi:localhost", - "$098237280074GZeOm:localhost", - "$152037280074GZeOm2:localhost", - "$editevid2:localhost", - "$151957878228ssqrJ2:localhost", - "$15275046980maRLj2:localhost", - "$15275047031IXQRi2:localhost", - "$098237280074GZeOm2:localhost", - ]; - - use futures_util::StreamExt; - let forward_events = forward_stream - .take(expected_forward_events.len()) - .collect::>() - .await; - - for (r, e) in forward_events.into_iter().zip(expected_forward_events.iter()) { - assert_eq!(&r.event_id().unwrap().as_str(), e); - } - - let expected_backwards_events = vec![ - "$152037280074GZeOm:localhost", - "$1444812213350496Caaaf:example.com", - "$1444812213350496Cbbbf:example.com", - "$1444812213350496Ccccf:example.com", - "$1444812213350496Caaak:example.com", - "$1444812213350496Cbbbk:example.com", - "$1444812213350496Cccck:example.com", - ]; - - let backward_events = backward_stream - .take(expected_backwards_events.len()) - .collect::>>() - .await; - - for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { - assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); - } - - mocked_messages.assert(); - mocked_messages_2.assert(); - } - - #[async_test] - #[cfg(feature = "experimental-timeline")] - async fn room_timeline() { - let client = logged_in_client().await; - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::MORE_SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _ = client.sync_once(sync_settings).await.unwrap(); - sync.assert(); - drop(sync); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - let (forward_stream, backward_stream) = room.timeline().await.unwrap(); - - let sync_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_2".to_owned())); - let sync_settings = SyncSettings::new() - .timeout(Duration::from_millis(3000)) - .token("s526_47314_0_7_1_1_1_11444_2"); - let _ = client.sync_once(sync_settings).await.unwrap(); - sync_2.assert(); - - let expected_forward_events = vec![ - "$152037280074GZeOm2:localhost", - "$editevid2:localhost", - "$151957878228ssqrJ2:localhost", - "$15275046980maRLj2:localhost", - "$15275047031IXQRi2:localhost", - "$098237280074GZeOm2:localhost", - ]; - - use futures_util::StreamExt; - let forward_events = forward_stream - .take(expected_forward_events.len()) - .collect::>() - .await; - - for (r, e) in forward_events.into_iter().zip(expected_forward_events.iter()) { - assert_eq!(&r.event_id().unwrap().as_str(), e); - } - - let expected_backwards_events = vec![ - "$098237280074GZeOm:localhost", - "$15275047031IXQRi:localhost", - "$15275046980maRLj:localhost", - "$151957878228ssqrJ:localhost", - "$editevid:localhost", - "$152037280074GZeOm:localhost", - // ^^^ These come from the first sync before we asked for the timeline and thus - // where cached - // - // While the following are fetched over the network transparently to us after, - // when scrolling back in time: - "$1444812213350496Caaaf:example.com", - "$1444812213350496Cbbbf:example.com", - "$1444812213350496Ccccf:example.com", - "$1444812213350496Caaak:example.com", - "$1444812213350496Cbbbk:example.com", - "$1444812213350496Cccck:example.com", - ]; - - let backward_events = backward_stream - .take(expected_backwards_events.len()) - .collect::>>() - .await; - - for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { - assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); - } - - mocked_messages.assert(); - mocked_messages_2.assert(); - } - - #[async_test] - async fn room_permalink() { - fn sync_response(index: u8, room_timeline_events: &[JsonValue]) -> JsonValue { - json!({ - "device_one_time_keys_count": {}, - "next_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index + 1), - "device_lists": { - "changed": [], - "left": [] - }, - "account_data": { - "events": [] - }, - "rooms": { - "invite": {}, - "join": { - "!test_room:127.0.0.1": { - "summary": {}, - "account_data": { - "events": [] - }, - "ephemeral": { - "events": [] - }, - "state": { - "events": [] - }, - "timeline": { - "events": room_timeline_events, - "limited": false, - "prev_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index - 1), - }, - "unread_notifications": { - "highlight_count": 0, - "notification_count": 0, - } - } - }, - "leave": {} - }, - "to_device": { - "events": [] - }, - "presence": { - "events": [] - } - }) - } - - fn room_member_events(nb: usize, server: &str) -> Vec { - let mut events = Vec::with_capacity(nb); - for i in 0..nb { - let id = format!("${server}{i}"); - let user = format!("@user{i}:{server}"); - events.push(json!({ - "content": { - "membership": "join", - }, - "event_id": id, - "origin_server_ts": 151800140, - "sender": user, - "state_key": user, - "type": "m.room.member", - })) - } - events - } - - let client = logged_in_client().await; - let sync_settings = SyncSettings::new(); - - // Without elligible server - let mut sync_index = 1; - let res = sync_response( - sync_index, - &[ - json!({ - "content": { - "creator": "@creator:127.0.0.1", - "room_version": "6", - }, - "event_id": "$151957878228ekrDs", - "origin_server_ts": 15195787, - "sender": "@creator:localhost", - "state_key": "", - "type": "m.room.create", - }), - json!({ - "content": { - "membership": "join", - }, - "event_id": "$151800140517rfvjc", - "origin_server_ts": 151800140, - "sender": "@creator:127.0.0.1", - "state_key": "@creator:127.0.0.1", - "type": "m.room.member", - }), - ], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1" - ); - assert_eq!( - room.matrix_permalink(true).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?action=join" - ); - - // With a single elligible server - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "membership": "join", - }, - "event_id": "$151800140517rfvjc", - "origin_server_ts": 151800140, - "sender": "@example:localhost", - "state_key": "@example:localhost", - "type": "m.room.member", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=localhost" - ); - - // With two elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(15, "notarealhs")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=localhost" - ); - - // With three elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(5, "mymatrix")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" - ); - - // With four elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(10, "yourmatrix")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" - ); - - // With power levels - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "users": { - "@example:localhost": 50, - }, - }, - "event_id": "$15139375512JaHAW", - "origin_server_ts": 151393755, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.power_levels", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" - ); - - // With higher power levels - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "users": { - "@example:localhost": 50, - "@user0:mymatrix": 70, - }, - }, - "event_id": "$15139375512JaHAZ", - "origin_server_ts": 151393755, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.power_levels", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" - ); - - // With server ACLs - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "allow": ["*"], - "allow_ip_literals": true, - "deny": ["notarealhs"], - }, - "event_id": "$143273582443PhrSn", - "origin_server_ts": 1432735824, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.server_acl", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" - ); - - // With an alternative alias - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "alt_aliases": ["#alias:localhost"], - }, - "event_id": "$15139375513VdeRF", - "origin_server_ts": 151393755, - "sender": "@example:localhost", - "state_key": "", - "type": "m.room.canonical_alias", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%23alias%3Alocalhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:r/alias:localhost" - ); - - // With a canonical alias - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "alias": "#canonical:localhost", - "alt_aliases": ["#alias:localhost"], - }, - "event_id": "$15139375513VdeRF", - "origin_server_ts": 151393755, - "sender": "@example:localhost", - "state_key": "", - "type": "m.room.canonical_alias", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%23canonical%3Alocalhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:r/canonical:localhost" - ); - assert_eq!( - room.matrix_permalink(true).await.unwrap().to_string(), - "matrix:r/canonical:localhost?action=join" - ); - - let event_id = event_id!("$15139375512JaHAW"); - assert_eq!( - room.matrix_to_event_permalink(event_id).await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1/%2415139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" - ); - assert_eq!( - room.matrix_event_permalink(event_id).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1/e/15139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" - ); - } } diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs new file mode 100644 index 000000000..c7c3a7d52 --- /dev/null +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -0,0 +1,591 @@ +// mockito (the http mocking library) is not supported for wasm32 +#![cfg(not(target_arch = "wasm32"))] + +use std::{collections::BTreeMap, str::FromStr, time::Duration}; + +use matrix_sdk::{ + config::SyncSettings, + media::{MediaFormat, MediaRequest, MediaThumbnailSize}, + Error, HttpError, RumaApiError, +}; +use matrix_sdk_test::{async_test, test_json}; +use mockito::{mock, Matcher}; +use ruma::{ + api::{ + client::{ + self as client_api, + account::register::{v3::Request as RegistrationRequest, RegistrationKind}, + directory::{ + get_public_rooms, + get_public_rooms_filtered::{self, v3::Request as PublicRoomsFilterRequest}, + }, + media::get_content_thumbnail::v3::Method, + session::get_login_types::v3::LoginType, + uiaa::{self, UiaaResponse}, + }, + error::{FromHttpResponseError, ServerError}, + }, + assign, device_id, + directory::Filter, + events::room::{message::ImageMessageEventContent, ImageInfo, MediaSource}, + mxc_uri, room_id, uint, user_id, +}; +use serde_json::json; +use url::Url; + +use crate::{logged_in_client, no_retry_test_client}; + +#[async_test] +async fn set_homeserver() { + let client = no_retry_test_client().await; + let homeserver = Url::from_str("http://example.com/").unwrap(); + client.set_homeserver(homeserver.clone()).await; + + assert_eq!(client.homeserver().await, homeserver); +} + +#[async_test] +async fn login() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let client = no_retry_test_client().await; + + let _m_types = mock("GET", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN_TYPES.to_string()) + .create(); + + let can_password = client + .get_login_types() + .await + .unwrap() + .flows + .iter() + .any(|flow| matches!(flow, LoginType::Password(_))); + assert!(can_password); + + let _m_login = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN.to_string()) + .create(); + + client.login_username("example", "wordpass").send().await.unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); + + assert_eq!(client.homeserver().await, homeserver); +} + +#[async_test] +async fn login_with_discovery() { + let client = no_retry_test_client().await; + + let _m_login = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN_WITH_DISCOVERY.to_string()) + .create(); + + client.login_username("example", "wordpass").send().await.unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); + + assert_eq!(client.homeserver().await.as_str(), "https://example.org/"); +} + +#[async_test] +async fn login_no_discovery() { + let client = no_retry_test_client().await; + + let _m_login = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN.to_string()) + .create(); + + client.login_username("example", "wordpass").send().await.unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); + + assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap()); +} + +#[async_test] +#[cfg(feature = "sso-login")] +async fn login_with_sso() { + let _m_login = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN.to_string()) + .create(); + + let _homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let client = no_retry_test_client().await; + let idp = ruma::api::client::session::get_login_types::v3::IdentityProvider::new( + "some-id".to_owned(), + "idp-name".to_owned(), + ); + client + .login_sso(|sso_url| async move { + let sso_url = Url::parse(&sso_url).unwrap(); + + let (_, redirect) = + sso_url.query_pairs().find(|(key, _)| key == "redirectUrl").unwrap(); + + let mut redirect_url = Url::parse(&redirect).unwrap(); + redirect_url.set_query(Some("loginToken=tinytoken")); + + reqwest::get(redirect_url.to_string()).await.unwrap(); + + Ok(()) + }) + .identity_provider_id(&idp.id) + .send() + .await + .unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); +} + +#[async_test] +async fn login_with_sso_token() { + let client = no_retry_test_client().await; + + let _m = mock("GET", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN_TYPES.to_string()) + .create(); + + let can_sso = client + .get_login_types() + .await + .unwrap() + .flows + .iter() + .any(|flow| matches!(flow, LoginType::Sso(_))); + assert!(can_sso); + + let sso_url = client.get_sso_login_url("http://127.0.0.1:3030", None).await; + assert!(sso_url.is_ok()); + + let _m = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN.to_string()) + .create(); + + client.login_token("averysmalltoken").send().await.unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); +} + +#[async_test] +async fn login_error() { + let client = no_retry_test_client().await; + + let _m = mock("POST", "/_matrix/client/r0/login") + .with_status(403) + .with_body(test_json::LOGIN_RESPONSE_ERR.to_string()) + .create(); + + if let Err(err) = client.login_username("example", "wordpass").send().await { + if let Error::Http(HttpError::Api(FromHttpResponseError::Server(ServerError::Known( + RumaApiError::ClientApi(client_api::Error { kind, message, status_code }), + )))) = err + { + if let client_api::error::ErrorKind::Forbidden = kind { + } else { + panic!("found the wrong `ErrorKind` {:?}, expected `Forbidden", kind); + } + assert_eq!(message, "Invalid password".to_owned()); + assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap()); + } else { + panic!("found the wrong `Error` type {:?}, expected `Error::RumaResponse", err); + } + } else { + panic!("this request should return an `Err` variant") + } +} + +#[async_test] +async fn register_error() { + let client = no_retry_test_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_owned())) + .with_status(403) + .with_body(test_json::REGISTRATION_RESPONSE_ERR.to_string()) + .create(); + + let user = assign!(RegistrationRequest::new(), { + username: Some("user"), + password: Some("password"), + auth: Some(uiaa::AuthData::FallbackAcknowledgement( + uiaa::FallbackAcknowledgement::new("foobar"), + )), + kind: RegistrationKind::User, + }); + + if let Err(err) = client.register(user).await { + if let HttpError::UiaaError(FromHttpResponseError::Server(ServerError::Known( + UiaaResponse::MatrixError(client_api::Error { kind, message, status_code }), + ))) = err + { + if let client_api::error::ErrorKind::Forbidden = kind { + } else { + panic!("found the wrong `ErrorKind` {:?}, expected `Forbidden", kind); + } + assert_eq!(message, "Invalid password".to_owned()); + assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap()); + } else { + panic!("found the wrong `Error` type {:#?}, expected `UiaaResponse`", err); + } + } else { + panic!("this request should return an `Err` variant") + } +} + +#[async_test] +async fn sync() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let response = client.sync_once(sync_settings).await.unwrap(); + + assert_ne!(response.next_batch, ""); + + assert!(client.sync_token().await.is_some()); +} + +#[async_test] +async fn devices() { + let client = logged_in_client().await; + + let _m = mock("GET", "/_matrix/client/r0/devices") + .with_status(200) + .with_body(test_json::DEVICES.to_string()) + .create(); + + assert!(client.devices().await.is_ok()); +} + +#[async_test] +async fn delete_devices() { + let client = no_retry_test_client().await; + + let _m = mock("POST", "/_matrix/client/r0/delete_devices") + .with_status(401) + .with_body( + json!({ + "flows": [ + { + "stages": [ + "m.login.password" + ] + } + ], + "params": {}, + "session": "vBslorikviAjxzYBASOBGfPp" + }) + .to_string(), + ) + .create(); + + let _m = mock("POST", "/_matrix/client/r0/delete_devices") + .with_status(401) + // empty response + // TODO rename that response type. + .with_body(test_json::LOGOUT.to_string()) + .create(); + + let devices = &[device_id!("DEVICEID").to_owned()]; + + if let Err(e) = client.delete_devices(devices, None).await { + if let Some(info) = e.uiaa_response() { + let mut auth_parameters = BTreeMap::new(); + + let identifier = json!({ + "type": "m.id.user", + "user": "example", + }); + auth_parameters.insert("identifier".to_owned(), identifier); + auth_parameters.insert("password".to_owned(), "wordpass".into()); + + let auth_data = uiaa::AuthData::Password(assign!( + uiaa::Password::new( + uiaa::UserIdentifier::UserIdOrLocalpart("example"), + "wordpass", + ), { + session: info.session.as_deref(), + } + )); + + client.delete_devices(devices, Some(auth_data)).await.unwrap(); + } + } +} + +#[async_test] +async fn resolve_room_alias() { + let client = no_retry_test_client().await; + + let _m = mock("GET", "/_matrix/client/r0/directory/room/%23alias%3Aexample%2Eorg") + .with_status(200) + .with_body(test_json::GET_ALIAS.to_string()) + .create(); + + let alias = ruma::room_alias_id!("#alias:example.org"); + assert!(client.resolve_room_alias(alias).await.is_ok()); +} + +#[async_test] +async fn join_leave_room() { + let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::SYNC.to_string()) + .create(); + + let client = logged_in_client().await; + let session = client.session().unwrap().clone(); + + let room = client.get_joined_room(room_id); + assert!(room.is_none()); + + client.sync_once(SyncSettings::default()).await.unwrap(); + + let room = client.get_left_room(room_id); + assert!(room.is_none()); + + let room = client.get_joined_room(room_id); + assert!(room.is_some()); + + // test store reloads with correct room state from the state store + let joined_client = no_retry_test_client().await; + joined_client.restore_login(session).await.unwrap(); + + // joined room reloaded from state store + joined_client.sync_once(SyncSettings::default()).await.unwrap(); + let room = joined_client.get_joined_room(room_id); + assert!(room.is_some()); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::LEAVE_SYNC_EVENT.to_string()) + .create(); + + joined_client.sync_once(SyncSettings::default()).await.unwrap(); + + let room = joined_client.get_joined_room(room_id); + assert!(room.is_none()); + + let room = joined_client.get_left_room(room_id); + assert!(room.is_some()); +} + +#[async_test] +async fn join_room_by_id() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/join".to_owned())) + .with_status(200) + .with_body(test_json::ROOM_ID.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let room_id = room_id!("!testroom:example.org"); + + assert_eq!( + // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId + // field + client.join_room_by_id(room_id).await.unwrap().room_id, + room_id + ); +} + +#[async_test] +async fn join_room_by_id_or_alias() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/join/".to_owned())) + .with_status(200) + .with_body(test_json::ROOM_ID.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let room_id = room_id!("!testroom:example.org").into(); + + assert_eq!( + // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId + // field + client + .join_room_by_id_or_alias(room_id, &["server.com".try_into().unwrap()]) + .await + .unwrap() + .room_id, + room_id!("!testroom:example.org") + ); +} + +#[async_test] +async fn room_search_all() { + let client = no_retry_test_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) + .with_status(200) + .with_body(test_json::PUBLIC_ROOMS.to_string()) + .create(); + + let get_public_rooms::v3::Response { chunk, .. } = + client.public_rooms(Some(10), None, None).await.unwrap(); + assert_eq!(chunk.len(), 1); +} + +#[async_test] +async fn room_search_filtered() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) + .with_status(200) + .with_body(test_json::PUBLIC_ROOMS.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let generic_search_term = Some("cheese"); + let filter = assign!(Filter::new(), { generic_search_term }); + let request = assign!(PublicRoomsFilterRequest::new(), { filter }); + + let get_public_rooms_filtered::v3::Response { chunk, .. } = + client.public_rooms_filtered(request).await.unwrap(); + assert_eq!(chunk.len(), 1); +} + +#[async_test] +async fn invited_rooms() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::INVITE_SYNC.to_string()) + .create(); + + let _response = client.sync_once(SyncSettings::default()).await.unwrap(); + + assert!(client.joined_rooms().is_empty()); + assert!(client.left_rooms().is_empty()); + assert!(!client.invited_rooms().is_empty()); + + assert!(client.get_invited_room(room_id!("!696r7674:example.com")).is_some()); +} + +#[async_test] +async fn left_rooms() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::LEAVE_SYNC.to_string()) + .create(); + + let _response = client.sync_once(SyncSettings::default()).await.unwrap(); + + assert!(client.joined_rooms().is_empty()); + assert!(!client.left_rooms().is_empty()); + assert!(client.invited_rooms().is_empty()); + + assert!(client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).is_some()) +} + +#[async_test] +async fn get_media_content() { + let client = logged_in_client().await; + + let request = MediaRequest { + source: MediaSource::Plain(mxc_uri!("mxc://localhost/textfile").to_owned()), + format: MediaFormat::File, + }; + + let m = mock( + "GET", + Matcher::Regex(r"^/_matrix/media/r0/download/localhost/textfile\?.*$".to_owned()), + ) + .with_status(200) + .with_body("Some very interesting text.") + .expect(2) + .create(); + + assert!(client.get_media_content(&request, true).await.is_ok()); + assert!(client.get_media_content(&request, true).await.is_ok()); + assert!(client.get_media_content(&request, false).await.is_ok()); + m.assert(); +} + +#[async_test] +async fn get_media_file() { + let client = logged_in_client().await; + + let event_content = ImageMessageEventContent::plain( + "filename.jpg".into(), + mxc_uri!("mxc://example.org/image").to_owned(), + Some(Box::new(assign!(ImageInfo::new(), { + height: Some(uint!(398)), + width: Some(uint!(394)), + mimetype: Some("image/jpeg".into()), + size: Some(uint!(31037)), + }))), + ); + + let m = mock( + "GET", + Matcher::Regex(r"^/_matrix/media/r0/download/example%2Eorg/image\?.*$".to_owned()), + ) + .with_status(200) + .with_body("binaryjpegdata") + .create(); + + assert!(client.get_file(event_content.clone(), true).await.is_ok()); + assert!(client.get_file(event_content.clone(), true).await.is_ok()); + m.assert(); + + let m = mock( + "GET", + Matcher::Regex(r"^/_matrix/media/r0/thumbnail/example%2Eorg/image\?.*$".to_owned()), + ) + .with_status(200) + .with_body("smallerbinaryjpegdata") + .create(); + + assert!(client + .get_thumbnail( + event_content, + MediaThumbnailSize { method: Method::Scale, width: uint!(100), height: uint!(100) }, + true + ) + .await + .is_ok()); + m.assert(); +} + +#[async_test] +async fn whoami() { + let client = logged_in_client().await; + + let _m = mock("GET", "/_matrix/client/r0/account/whoami") + .with_status(200) + .with_body(test_json::WHOAMI.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let user_id = user_id!("@joe:example.org"); + + assert_eq!(client.whoami().await.unwrap().user_id, user_id); +} diff --git a/crates/matrix-sdk/tests/integration/main.rs b/crates/matrix-sdk/tests/integration/main.rs new file mode 100644 index 000000000..a2c8ad143 --- /dev/null +++ b/crates/matrix-sdk/tests/integration/main.rs @@ -0,0 +1,31 @@ +use matrix_sdk::{config::RequestConfig, Client, ClientBuilder, Session}; +use ruma::{api::MatrixVersion, device_id, user_id}; +use url::Url; + +mod client; +mod room; + +fn test_client_builder() -> ClientBuilder { + let homeserver = Url::parse(&mockito::server_url()).unwrap(); + Client::builder().homeserver_url(homeserver).server_versions([MatrixVersion::V1_0]) +} + +async fn no_retry_test_client() -> Client { + test_client_builder() + .request_config(RequestConfig::new().disable_retry()) + .build() + .await + .unwrap() +} + +async fn logged_in_client() -> Client { + let session = Session { + access_token: "1234".to_owned(), + user_id: user_id!("@example:localhost").to_owned(), + device_id: device_id!("DEVICEID").to_owned(), + }; + let client = no_retry_test_client().await; + client.restore_login(session).await.unwrap(); + + client +} diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs new file mode 100644 index 000000000..910e4064d --- /dev/null +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -0,0 +1,823 @@ +use std::time::Duration; + +use matrix_sdk::{ + config::{RequestConfig, SyncSettings}, + DisplayName, RoomMember, Session, +}; +use matrix_sdk_test::{async_test, test_json}; +use mockito::{mock, Matcher}; +use ruma::{ + device_id, event_id, + events::{AnySyncStateEvent, StateEventType}, + room_id, user_id, +}; +use serde_json::{json, Value as JsonValue}; + +use crate::{logged_in_client, test_client_builder}; + +#[async_test] +async fn user_presence() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/members".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::MEMBERS.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + let members: Vec = room.active_members().await.unwrap(); + + assert_eq!(2, members.len()); + // assert!(room.power_levels.is_some()) +} + +#[async_test] +async fn calculate_room_names_from_summary() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::DEFAULT_SYNC_SUMMARY.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + let _response = client.sync_once(sync_settings).await.unwrap(); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + assert_eq!(DisplayName::Calculated("example2".to_owned()), room.display_name().await.unwrap()); +} + +#[async_test] +async fn room_names() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .expect_at_least(1) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + assert_eq!(client.rooms().len(), 1); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + assert_eq!(DisplayName::Aliased("tutorial".to_owned()), room.display_name().await.unwrap()); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::INVITE_SYNC.to_string()) + .expect_at_least(1) + .create(); + + let _response = client.sync_once(SyncSettings::new()).await.unwrap(); + + assert_eq!(client.rooms().len(), 1); + let invited_room = client.get_invited_room(room_id!("!696r7674:example.com")).unwrap(); + + assert_eq!( + DisplayName::Named("My Room Name".to_owned()), + invited_room.display_name().await.unwrap() + ); +} + +#[async_test] +async fn test_state_event_getting() { + let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); + + let session = Session { + access_token: "1234".to_owned(), + user_id: user_id!("@example:localhost").to_owned(), + device_id: device_id!("DEVICEID").to_owned(), + }; + + let sync = json!({ + "next_batch": "1234", + "rooms": { + "join": { + "!SVkFJHzfwvuaIEawgC:localhost": { + "state": { + "events": [ + { + "type": "m.custom.note", + "sender": "@example:localhost", + "content": { + "body": "Note 1", + }, + "state_key": "note.1", + "origin_server_ts": 1611853078727u64, + "unsigned": { + "replaces_state": "$2s9GcbVxbbFS3EZY9vN1zhavaDJnF32cAIGAxi99NuQ", + "age": 15458166523u64 + }, + "event_id": "$NVCTvrlxodf3ZGjJ6foxepEq8ysSkTq8wG0wKeQBVZg" + }, + { + "type": "m.custom.note", + "sender": "@example2:localhost", + "content": { + "body": "Note 2", + }, + "state_key": "note.2", + "origin_server_ts": 1611853078727u64, + "unsigned": { + "replaces_state": "$2s9GcbVxbbFS3EZY9vN1zhavaDJnF32cAIGAxi99NuQ", + "age": 15458166523u64 + }, + "event_id": "$NVCTvrlxodf3ZGjJ6foxepEq8ysSkTq8wG0wKeQBVZg" + }, + { + "type": "m.room.encryption", + "sender": "@example:localhost", + "content": { + "algorithm": "m.megolm.v1.aes-sha2" + }, + "state_key": "", + "origin_server_ts": 1586437448151u64, + "unsigned": { + "age": 40873797099u64 + }, + "event_id": "$vyG3wu1QdJSh5gc-09SwjXBXlXo8gS7s4QV_Yxha0Xw" + }, + ] + } + } + } + } + }); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(sync.to_string()) + .create(); + + let client = test_client_builder() + .request_config(RequestConfig::new().retry_limit(3)) + .build() + .await + .unwrap(); + client.restore_login(session.clone()).await.unwrap(); + + let room = client.get_joined_room(room_id); + assert!(room.is_none()); + + client.sync_once(SyncSettings::default()).await.unwrap(); + + let room = client.get_joined_room(room_id).unwrap(); + + let state_events = room.get_state_events(StateEventType::RoomEncryption).await.unwrap(); + assert_eq!(state_events.len(), 1); + + let state_events = room.get_state_events("m.custom.note".into()).await.unwrap(); + assert_eq!(state_events.len(), 2); + + let encryption_event = room + .get_state_event(StateEventType::RoomEncryption, "") + .await + .unwrap() + .unwrap() + .deserialize() + .unwrap(); + + matches::assert_matches!(encryption_event, AnySyncStateEvent::RoomEncryption(_)); +} + +// FIXME: removing timelines during reading the stream currently leaves to an +// inconsistent undefined state. This tests shows that, but because +// different implementations deal with problem in different, +// inconsistent manners, isn't activated. +//#[async_test] +#[allow(dead_code)] +#[cfg(feature = "experimental-timeline")] +async fn room_timeline_with_remove() { + let client = logged_in_client().await; + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _ = client.sync_once(sync_settings).await.unwrap(); + sync.assert(); + drop(sync); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + let (forward_stream, backward_stream) = room.timeline().await.unwrap(); + + // these two syncs lead to the store removing its existing timeline + // and replace them with new ones + let sync_2 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_1.*".to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::MORE_SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let sync_3 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::MORE_SYNC_2.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let mocked_messages = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" + .to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let mocked_messages_2 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" + .to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_1".to_owned())); + let sync_settings = SyncSettings::new() + .timeout(Duration::from_millis(3000)) + .token("s526_47314_0_7_1_1_1_11444_1"); + let _ = client.sync_once(sync_settings).await.unwrap(); + sync_2.assert(); + let sync_settings = SyncSettings::new() + .timeout(Duration::from_millis(3000)) + .token("s526_47314_0_7_1_1_1_11444_2"); + let _ = client.sync_once(sync_settings).await.unwrap(); + sync_3.assert(); + + let expected_forward_events = vec![ + "$152037280074GZeOm:localhost", + "$editevid:localhost", + "$151957878228ssqrJ:localhost", + "$15275046980maRLj:localhost", + "$15275047031IXQRi:localhost", + "$098237280074GZeOm:localhost", + "$152037280074GZeOm2:localhost", + "$editevid2:localhost", + "$151957878228ssqrJ2:localhost", + "$15275046980maRLj2:localhost", + "$15275047031IXQRi2:localhost", + "$098237280074GZeOm2:localhost", + ]; + + use futures_util::StreamExt; + use matrix_sdk::deserialized_responses::SyncRoomEvent; + let forward_events = + forward_stream.take(expected_forward_events.len()).collect::>().await; + + for (r, e) in forward_events.into_iter().zip(expected_forward_events.iter()) { + assert_eq!(&r.event_id().unwrap().as_str(), e); + } + + let expected_backwards_events = vec![ + "$152037280074GZeOm:localhost", + "$1444812213350496Caaaf:example.com", + "$1444812213350496Cbbbf:example.com", + "$1444812213350496Ccccf:example.com", + "$1444812213350496Caaak:example.com", + "$1444812213350496Cbbbk:example.com", + "$1444812213350496Cccck:example.com", + ]; + + let backward_events = backward_stream + .take(expected_backwards_events.len()) + .collect::>>() + .await; + + for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { + assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); + } + + mocked_messages.assert(); + mocked_messages_2.assert(); +} + +#[async_test] +#[cfg(feature = "experimental-timeline")] +async fn room_timeline() { + let client = logged_in_client().await; + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::MORE_SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _ = client.sync_once(sync_settings).await.unwrap(); + sync.assert(); + drop(sync); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + let (forward_stream, backward_stream) = room.timeline().await.unwrap(); + + let sync_2 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::MORE_SYNC_2.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let mocked_messages = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" + .to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let mocked_messages_2 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" + .to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_2".to_owned())); + let sync_settings = SyncSettings::new() + .timeout(Duration::from_millis(3000)) + .token("s526_47314_0_7_1_1_1_11444_2"); + let _ = client.sync_once(sync_settings).await.unwrap(); + sync_2.assert(); + + let expected_forward_events = vec![ + "$152037280074GZeOm2:localhost", + "$editevid2:localhost", + "$151957878228ssqrJ2:localhost", + "$15275046980maRLj2:localhost", + "$15275047031IXQRi2:localhost", + "$098237280074GZeOm2:localhost", + ]; + + use futures_util::StreamExt; + use matrix_sdk::deserialized_responses::SyncRoomEvent; + let forward_events = + forward_stream.take(expected_forward_events.len()).collect::>().await; + + for (r, e) in forward_events.into_iter().zip(expected_forward_events.iter()) { + assert_eq!(&r.event_id().unwrap().as_str(), e); + } + + let expected_backwards_events = vec![ + "$098237280074GZeOm:localhost", + "$15275047031IXQRi:localhost", + "$15275046980maRLj:localhost", + "$151957878228ssqrJ:localhost", + "$editevid:localhost", + "$152037280074GZeOm:localhost", + // ^^^ These come from the first sync before we asked for the timeline and thus + // where cached + // + // While the following are fetched over the network transparently to us after, + // when scrolling back in time: + "$1444812213350496Caaaf:example.com", + "$1444812213350496Cbbbf:example.com", + "$1444812213350496Ccccf:example.com", + "$1444812213350496Caaak:example.com", + "$1444812213350496Cbbbk:example.com", + "$1444812213350496Cccck:example.com", + ]; + + let backward_events = backward_stream + .take(expected_backwards_events.len()) + .collect::>>() + .await; + + for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { + assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); + } + + mocked_messages.assert(); + mocked_messages_2.assert(); +} + +#[async_test] +async fn room_permalink() { + fn sync_response(index: u8, room_timeline_events: &[JsonValue]) -> JsonValue { + json!({ + "device_one_time_keys_count": {}, + "next_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index + 1), + "device_lists": { + "changed": [], + "left": [] + }, + "account_data": { + "events": [] + }, + "rooms": { + "invite": {}, + "join": { + "!test_room:127.0.0.1": { + "summary": {}, + "account_data": { + "events": [] + }, + "ephemeral": { + "events": [] + }, + "state": { + "events": [] + }, + "timeline": { + "events": room_timeline_events, + "limited": false, + "prev_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index - 1), + }, + "unread_notifications": { + "highlight_count": 0, + "notification_count": 0, + } + } + }, + "leave": {} + }, + "to_device": { + "events": [] + }, + "presence": { + "events": [] + } + }) + } + + fn room_member_events(nb: usize, server: &str) -> Vec { + let mut events = Vec::with_capacity(nb); + for i in 0..nb { + let id = format!("${server}{i}"); + let user = format!("@user{i}:{server}"); + events.push(json!({ + "content": { + "membership": "join", + }, + "event_id": id, + "origin_server_ts": 151800140, + "sender": user, + "state_key": user, + "type": "m.room.member", + })) + } + events + } + + let client = logged_in_client().await; + let sync_settings = SyncSettings::new(); + + // Without elligible server + let mut sync_index = 1; + let res = sync_response( + sync_index, + &[ + json!({ + "content": { + "creator": "@creator:127.0.0.1", + "room_version": "6", + }, + "event_id": "$151957878228ekrDs", + "origin_server_ts": 15195787, + "sender": "@creator:localhost", + "state_key": "", + "type": "m.room.create", + }), + json!({ + "content": { + "membership": "join", + }, + "event_id": "$151800140517rfvjc", + "origin_server_ts": 151800140, + "sender": "@creator:127.0.0.1", + "state_key": "@creator:127.0.0.1", + "type": "m.room.member", + }), + ], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1" + ); + assert_eq!( + room.matrix_permalink(true).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?action=join" + ); + + // With a single elligible server + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "membership": "join", + }, + "event_id": "$151800140517rfvjc", + "origin_server_ts": 151800140, + "sender": "@example:localhost", + "state_key": "@example:localhost", + "type": "m.room.member", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=localhost" + ); + + // With two elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(15, "notarealhs")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=localhost" + ); + + // With three elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(5, "mymatrix")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" + ); + + // With four elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(10, "yourmatrix")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" + ); + + // With power levels + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "users": { + "@example:localhost": 50, + }, + }, + "event_id": "$15139375512JaHAW", + "origin_server_ts": 151393755, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.power_levels", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" + ); + + // With higher power levels + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "users": { + "@example:localhost": 50, + "@user0:mymatrix": 70, + }, + }, + "event_id": "$15139375512JaHAZ", + "origin_server_ts": 151393755, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.power_levels", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" + ); + + // With server ACLs + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "allow": ["*"], + "allow_ip_literals": true, + "deny": ["notarealhs"], + }, + "event_id": "$143273582443PhrSn", + "origin_server_ts": 1432735824, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.server_acl", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" + ); + + // With an alternative alias + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "alt_aliases": ["#alias:localhost"], + }, + "event_id": "$15139375513VdeRF", + "origin_server_ts": 151393755, + "sender": "@example:localhost", + "state_key": "", + "type": "m.room.canonical_alias", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%23alias%3Alocalhost" + ); + assert_eq!(room.matrix_permalink(false).await.unwrap().to_string(), "matrix:r/alias:localhost"); + + // With a canonical alias + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "alias": "#canonical:localhost", + "alt_aliases": ["#alias:localhost"], + }, + "event_id": "$15139375513VdeRF", + "origin_server_ts": 151393755, + "sender": "@example:localhost", + "state_key": "", + "type": "m.room.canonical_alias", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%23canonical%3Alocalhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:r/canonical:localhost" + ); + assert_eq!( + room.matrix_permalink(true).await.unwrap().to_string(), + "matrix:r/canonical:localhost?action=join" + ); + + let event_id = event_id!("$15139375512JaHAW"); + assert_eq!( + room.matrix_to_event_permalink(event_id).await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1/%2415139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" + ); + assert_eq!( + room.matrix_event_permalink(event_id).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1/e/15139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" + ); +} diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs new file mode 100644 index 000000000..63c2431ea --- /dev/null +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -0,0 +1,569 @@ +use std::{io::Cursor, time::Duration}; + +use matrix_sdk::{ + attachment::{ + AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, + Thumbnail, + }, + config::SyncSettings, +}; +use matrix_sdk_test::{async_test, test_json}; +use mockito::{mock, Matcher}; +use ruma::{ + api::client::membership::Invite3pidInit, assign, event_id, + events::room::message::RoomMessageEventContent, mxc_uri, room_id, thirdparty, uint, user_id, + TransactionId, +}; +use serde_json::json; + +use crate::logged_in_client; + +#[async_test] +async fn invite_user_by_id() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) + .with_status(200) + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let user = user_id!("@example:localhost"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.invite_user_by_id(user).await.unwrap(); +} + +#[async_test] +async fn invite_user_by_3pid() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) + .with_status(200) + // empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.invite_user_by_3pid( + Invite3pidInit { + id_server: "example.org", + id_access_token: "IdToken", + medium: thirdparty::Medium::Email, + address: "address", + } + .into(), + ) + .await + .unwrap(); +} + +#[async_test] +async fn leave_room() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/leave".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.leave().await.unwrap(); +} + +#[async_test] +async fn ban_user() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/ban".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let user = user_id!("@example:localhost"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.ban_user(user, None).await.unwrap(); +} + +#[async_test] +async fn kick_user() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/kick".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let user = user_id!("@example:localhost"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.kick_user(user, None).await.unwrap(); +} + +#[async_test] +async fn read_receipt() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/receipt".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let event_id = event_id!("$xxxxxx:example.org"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.read_receipt(event_id).await.unwrap(); +} + +#[async_test] +async fn read_marker() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/read_markers".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let event_id = event_id!("$xxxxxx:example.org"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.read_marker(event_id, None).await.unwrap(); +} + +#[async_test] +async fn typing_notice() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/typing".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.typing_notice(true).await.unwrap(); +} + +#[async_test] +async fn room_state_event_send() { + use ruma::events::room::member::{MembershipState, RoomMemberEventContent}; + + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/state/.*".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); + + let room = client.get_joined_room(room_id).unwrap(); + + let avatar_url = mxc_uri!("mxc://example.org/avA7ar"); + let member_event = assign!(RoomMemberEventContent::new(MembershipState::Join), { + avatar_url: Some(avatar_url.to_owned()) + }); + let response = room.send_state_event(member_event, "").await.unwrap(); + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id); +} + +#[async_test] +async fn room_message_send() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let content = RoomMessageEventContent::text_plain("Hello world"); + let txn_id = TransactionId::new(); + let response = room.send(content, Some(&txn_id)).await.unwrap(); + + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} + +#[async_test] +async fn room_attachment_send() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .match_body(Matcher::PartialJson(json!({ + "info": { + "mimetype": "image/jpeg" + } + }))) + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) + .with_status(200) + .match_header("content-type", "image/jpeg") + .with_body( + json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }) + .to_string(), + ) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let mut media = Cursor::new("Hello world"); + + let response = room + .send_attachment("image", &mime::IMAGE_JPEG, &mut media, AttachmentConfig::new()) + .await + .unwrap(); + + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} + +#[async_test] +async fn room_attachment_send_info() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .match_body(Matcher::PartialJson(json!({ + "info": { + "mimetype": "image/jpeg", + "h": 600, + "w": 800, + } + }))) + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) + .with_status(200) + .match_header("content-type", "image/jpeg") + .with_body( + json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }) + .to_string(), + ) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let mut media = Cursor::new("Hello world"); + + let config = AttachmentConfig::new().info(AttachmentInfo::Image(BaseImageInfo { + height: Some(uint!(600)), + width: Some(uint!(800)), + size: None, + blurhash: None, + })); + + let response = + room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); + + upload_mock.assert(); + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} + +#[async_test] +async fn room_attachment_send_wrong_info() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .match_body(Matcher::PartialJson(json!({ + "info": { + "mimetype": "image/jpeg", + "h": 600, + "w": 800, + } + }))) + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) + .with_status(200) + .match_header("content-type", "image/jpeg") + .with_body( + json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }) + .to_string(), + ) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let mut media = Cursor::new("Hello world"); + + let config = AttachmentConfig::new().info(AttachmentInfo::Video(BaseVideoInfo { + height: Some(uint!(600)), + width: Some(uint!(800)), + duration: Some(Duration::from_millis(3600)), + size: None, + blurhash: None, + })); + + let response = room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await; + + assert!(response.is_err()) +} + +#[async_test] +async fn room_attachment_send_info_thumbnail() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .match_body(Matcher::PartialJson(json!({ + "info": { + "mimetype": "image/jpeg", + "h": 600, + "w": 800, + "thumbnail_info": { + "h": 360, + "w": 480, + "mimetype":"image/jpeg", + "size": 3600, + }, + "thumbnail_url": "mxc://example.com/AQwafuaFswefuhsfAFAgsw", + } + }))) + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) + .with_status(200) + .match_header("content-type", "image/jpeg") + .with_body( + json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }) + .to_string(), + ) + .expect(2) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let mut media = Cursor::new("Hello world"); + + let mut thumbnail_reader = Cursor::new("Thumbnail"); + + let config = AttachmentConfig::with_thumbnail(Thumbnail { + reader: &mut thumbnail_reader, + content_type: &mime::IMAGE_JPEG, + info: Some(BaseThumbnailInfo { + height: Some(uint!(360)), + width: Some(uint!(480)), + size: Some(uint!(3600)), + }), + }) + .info(AttachmentInfo::Image(BaseImageInfo { + height: Some(uint!(600)), + width: Some(uint!(800)), + size: None, + blurhash: None, + })); + + let response = + room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); + + upload_mock.assert(); + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} + +#[async_test] +async fn room_redact() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let event_id = event_id!("$xxxxxxxx:example.com"); + + let txn_id = TransactionId::new(); + let reason = Some("Indecent material"); + let response = room.redact(event_id, reason, Some(txn_id)).await.unwrap(); + + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} diff --git a/crates/matrix-sdk/tests/integration/room/left.rs b/crates/matrix-sdk/tests/integration/room/left.rs new file mode 100644 index 000000000..9bc48d2f0 --- /dev/null +++ b/crates/matrix-sdk/tests/integration/room/left.rs @@ -0,0 +1,34 @@ +use std::time::Duration; + +use matrix_sdk::config::SyncSettings; +use matrix_sdk_test::{async_test, test_json}; +use mockito::{mock, Matcher}; +use ruma::room_id; + +use crate::logged_in_client; + +#[async_test] +async fn forget_room() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/forget".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::LEAVE_SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.forget().await.unwrap(); +} diff --git a/crates/matrix-sdk/tests/integration/room/mod.rs b/crates/matrix-sdk/tests/integration/room/mod.rs new file mode 100644 index 000000000..b9e0e2c78 --- /dev/null +++ b/crates/matrix-sdk/tests/integration/room/mod.rs @@ -0,0 +1,3 @@ +mod common; +mod joined; +mod left;