diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 000000000..45673424e --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,5 @@ + +[profile.default] +retries = 2 +# kill the slow tests if they still aren't up after 180s +slow-timeout = { period = "60s", terminate-after = 3 } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5c468f5e..03b3c4cf3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -441,12 +441,11 @@ jobs: with: python-version: 3.8 - - uses: gnunicorn/setup-matrix-synapse@main + - uses: michaelkaye/setup-matrix-synapse@main with: uploadLogs: true httpPort: 8228 disableRateLimiting: true - serverName: "matrix-sdk.rs" - name: Test uses: actions-rs/cargo@v1 diff --git a/Cargo.lock b/Cargo.lock index 0f3d73dfd..648290bff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2573,6 +2573,7 @@ dependencies = [ "once_cell", "tempfile", "tokio", + "tracing", "tracing-subscriber", ] diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 6eacb0705..073b3e732 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -327,10 +327,7 @@ impl BaseClient { ), ) => { room_info.handle_redaction(r); - // FIXME: Find the event in self.store (needs - // something like a get_event_by_id), redact it and - // put it back via StateChanges. See - // https://github.com/matrix-org/matrix-rust-sdk/issues/607 + changes.add_redaction(room_id, r.clone()); } #[cfg(feature = "e2e-encryption")] diff --git a/crates/matrix-sdk-base/src/store/integration_tests.rs b/crates/matrix-sdk-base/src/store/integration_tests.rs index 07064ff0e..b8ad3b431 100644 --- a/crates/matrix-sdk-base/src/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/store/integration_tests.rs @@ -59,6 +59,7 @@ macro_rules! statestore_integration_tests { }, power_levels::RoomPowerLevelsEventContent, MediaSource, + topic::{RoomTopicEventContent, OriginalRoomTopicEvent, RedactedRoomTopicEvent}, }, AnyEphemeralRoomEventContent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, @@ -78,7 +79,7 @@ macro_rules! statestore_integration_tests { }; use $crate::{ media::{MediaFormat, MediaRequest, MediaThumbnailSize}, - store::{Result as StoreResult, StateChanges, StateStore}, + store::{Result as StoreResult, StateChanges, StateStore, StateStoreExt}, RoomInfo, RoomType, }; @@ -147,8 +148,8 @@ macro_rules! statestore_integration_tests { let topic_json: &JsonValue = &test_json::TOPIC; let topic_raw = - serde_json::from_value::>(topic_json.clone()).unwrap(); - let topic_event = topic_raw.deserialize().unwrap(); + serde_json::from_value::>(topic_json.clone()).expect("can create sync-state-event for topic"); + let topic_event = topic_raw.deserialize().expect("can deserialize raw topic"); room.handle_state_event(&topic_event); changes.add_state_event(room_id, topic_event, topic_raw); @@ -284,6 +285,55 @@ macro_rules! statestore_integration_tests { }) } + #[async_test] + async fn test_topic_redaction() -> StoreResult<()> { + let room_id = room_id(); + let inner_store = get_store().await?; + + let store = Arc::new(inner_store); + populate_store(store.clone()).await?; + + assert!(store.get_sync_token().await?.is_some()); + assert_eq!( + store + .get_state_event_static::(room_id) + .await? + .expect("room topic found before redaction") + .deserialize_as::() + .expect("can deserialize room topic before redaction") + .content + .topic, + "😀" + ); + + let mut changes = StateChanges::default(); + + let redaction_json: &JsonValue = &test_json::TOPIC_REDACTION; + let redaction_evt = serde_json::from_value(redaction_json.clone()).expect("topic redaction event making works"); + + changes.add_redaction(room_id, redaction_evt); + store.save_changes(&changes).await?; + + match store + .get_state_event_static::(room_id) + .await? + .expect("room topic found before redaction") + .deserialize_as::() + { + Err(_) => { } // as expected + Ok(_) => panic!("Topic has not been redacted") + } + + let _ = store + .get_state_event_static::(room_id) + .await? + .expect("room topic found after redaction") + .deserialize_as::() + .expect("can deserialize room topic after redaction"); + + Ok(()) + } + #[async_test] async fn test_populate_store() -> StoreResult<()> { let room_id = room_id(); diff --git a/crates/matrix-sdk-base/src/store/memory_store.rs b/crates/matrix-sdk-base/src/store/memory_store.rs index 7535333dd..be80b2b73 100644 --- a/crates/matrix-sdk-base/src/store/memory_store.rs +++ b/crates/matrix-sdk-base/src/store/memory_store.rs @@ -26,15 +26,8 @@ use dashmap::{DashMap, DashSet}; use lru::LruCache; #[allow(unused_imports)] use matrix_sdk_common::{instant::Instant, locks::Mutex}; -#[cfg(feature = "experimental-timeline")] -use ruma::{ - canonical_json::redact_in_place, - events::{ - room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, - }, - CanonicalJsonObject, RoomVersionId, -}; use ruma::{ + canonical_json::redact, events::{ presence::PresenceEvent, receipt::{Receipt, ReceiptType}, @@ -43,20 +36,28 @@ use ruma::{ AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, }, serde::Raw, - EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, + CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, + RoomVersionId, UserId, }; -use tracing::info; +#[cfg(feature = "experimental-timeline")] +use ruma::{ + canonical_json::redact_in_place, + events::{ + room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, + }, +}; +use tracing::{info, warn}; #[cfg(feature = "experimental-timeline")] use super::BoxStream; -use super::{Result, RoomInfo, StateChanges, StateStore}; +use super::{Result, RoomInfo, StateChanges, StateStore, StoreError}; +#[cfg(feature = "experimental-timeline")] +use crate::deserialized_responses::SyncTimelineEvent; use crate::{ deserialized_responses::MemberEvent, media::{MediaRequest, UniqueKey}, MinimalRoomMemberEvent, }; -#[cfg(feature = "experimental-timeline")] -use crate::{deserialized_responses::SyncTimelineEvent, StoreError}; /// In-Memory, non-persistent implementation of the `StateStore` /// @@ -353,10 +354,18 @@ impl MemoryStore { } } + let make_room_version = |room_id| { + self.room_info + .get(room_id) + .and_then(|info| info.room_version().cloned()) + .unwrap_or_else(|| { + warn!(%room_id, "Unable to find the room version, assuming version 9"); + RoomVersionId::V9 + }) + }; + #[cfg(feature = "experimental-timeline")] for (room_id, timeline) in &changes.timeline { - use tracing::warn; - if timeline.sync { info!(%room_id, "Saving new timeline batch from sync response"); } else { @@ -407,16 +416,6 @@ impl MemoryStore { ..Default::default() }); - let make_room_version = || { - self.room_info - .get(room_id) - .and_then(|info| info.room_version().cloned()) - .unwrap_or_else(|| { - warn!(%room_id, "Unable to find the room version, assuming version 9"); - RoomVersionId::V9 - }) - }; - if timeline.sync { let mut room_version = None; for event in &timeline.events { @@ -433,7 +432,8 @@ impl MemoryStore { if let Some(mut full_event) = data.events.get_mut(&position.clone()) { let mut event_json: CanonicalJsonObject = full_event.event.deserialize_as()?; - let v = room_version.get_or_insert_with(make_room_version); + let v = + room_version.get_or_insert_with(|| make_room_version(room_id)); redact_in_place(&mut event_json, v) .map_err(StoreError::Redaction)?; @@ -463,6 +463,27 @@ impl MemoryStore { } } + for (room_id, redactions) in &changes.redactions { + let mut room_version = None; + if let Some(room) = self.room_state.get_mut(room_id) { + for mut ref_room_mu in room.iter_mut() { + for mut ref_evt_mu in ref_room_mu.value_mut().iter_mut() { + let raw_evt = ref_evt_mu.value_mut(); + if let Ok(Some(event_id)) = raw_evt.get_field::("event_id") { + if redactions.get(&event_id).is_some() { + let redacted = redact( + &raw_evt.deserialize_as::()?, + room_version.get_or_insert_with(|| make_room_version(room_id)), + ) + .map_err(StoreError::Redaction)?; + *raw_evt = Raw::new(&redacted)?.cast(); + } + } + } + } + } + } + info!("Saved changes in {:?}", now.elapsed()); Ok(()) diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 711ddaf8b..aeb4bb08b 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -46,7 +46,10 @@ use ruma::{ events::{ presence::PresenceEvent, receipt::{Receipt, ReceiptEventContent, ReceiptType}, - room::member::{StrippedRoomMemberEvent, SyncRoomMemberEvent}, + room::{ + member::{StrippedRoomMemberEvent, SyncRoomMemberEvent}, + redaction::OriginalSyncRoomRedactionEvent, + }, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent, EmptyStateKey, GlobalAccountDataEvent, GlobalAccountDataEventContent, GlobalAccountDataEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent, @@ -686,6 +689,10 @@ pub struct StateChanges { /// A map of `RoomId` to `ReceiptEventContent`. pub receipts: BTreeMap, + /// A map of `RoomId` to maps of `OwnedEventId` to be redacted by + /// `SyncRoomRedactionEvent`. + pub redactions: BTreeMap>, + /// A mapping of `RoomId` to a map of event type to a map of state key to /// `AnyStrippedStateEvent`. pub stripped_state: BTreeMap< @@ -777,6 +784,14 @@ impl StateChanges { .insert(event.state_key().to_owned(), raw_event); } + /// Redact an event in the room + pub fn add_redaction(&mut self, room_id: &RoomId, redaction: OriginalSyncRoomRedactionEvent) { + self.redactions + .entry(room_id.to_owned()) + .or_default() + .insert(redaction.redacts.clone(), redaction); + } + /// Update the `StateChanges` struct with the given room with a new /// `Notification`. pub fn add_notification(&mut self, room_id: &RoomId, notification: Notification) { diff --git a/crates/matrix-sdk-indexeddb/src/state_store.rs b/crates/matrix-sdk-indexeddb/src/state_store.rs index 33d68ca5f..3e99d59c9 100644 --- a/crates/matrix-sdk-indexeddb/src/state_store.rs +++ b/crates/matrix-sdk-indexeddb/src/state_store.rs @@ -36,15 +36,8 @@ use matrix_sdk_base::{ #[cfg(feature = "experimental-timeline")] use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream}; use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher}; -#[cfg(feature = "experimental-timeline")] -use ruma::{ - canonical_json::redact_in_place, - events::{ - room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, - }, - CanonicalJsonObject, RoomVersionId, -}; use ruma::{ + canonical_json::redact, events::{ presence::PresenceEvent, receipt::{Receipt, ReceiptType}, @@ -53,11 +46,19 @@ use ruma::{ GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, }, serde::Raw, - EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId, + CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, RoomVersionId, UserId, +}; +#[cfg(feature = "experimental-timeline")] +use ruma::{ + canonical_json::redact_in_place, + events::{ + room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, + }, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[cfg(feature = "experimental-timeline")] -use tracing::{info, warn}; +use tracing::info; +use tracing::warn; use wasm_bindgen::JsValue; use web_sys::IdbKeyRange; @@ -582,6 +583,10 @@ impl IndexeddbStateStore { stores.extend([KEYS::ROOM_STATE, KEYS::STRIPPED_ROOM_STATE]); } + if !changes.redactions.is_empty() { + stores.extend([KEYS::ROOM_STATE, KEYS::ROOM_INFOS]); + } + if !changes.room_infos.is_empty() || !changes.stripped_room_infos.is_empty() { stores.extend([KEYS::ROOM_INFOS, KEYS::STRIPPED_ROOM_INFOS]); } @@ -862,6 +867,53 @@ impl IndexeddbStateStore { } } + if !changes.redactions.is_empty() { + let state = tx.object_store(KEYS::ROOM_STATE)?; + let room_info = tx.object_store(KEYS::ROOM_INFOS)?; + + for (room_id, redactions) in &changes.redactions { + let range = self.encode_to_range(KEYS::ROOM_STATE, room_id)?; + let cursor = match state.open_cursor_with_range(&range)?.await? { + Some(c) => c, + _ => continue, + }; + + let mut room_version = None; + + while let Some(key) = cursor.key() { + let raw_evt = + self.deserialize_event::>(cursor.value())?; + if let Ok(Some(event_id)) = raw_evt.get_field::("event_id") { + if redactions.contains_key(&event_id) { + let version = { + if room_version.is_none() { + room_version.replace(room_info + .get(&self.encode_key(KEYS::ROOM_INFOS, room_id))? + .await? + .and_then(|f| self.deserialize_event::(f).ok()) + .and_then(|info| info.room_version().cloned()) + .unwrap_or_else(|| { + warn!(%room_id, "Unable to find the room version, assume version 9"); + RoomVersionId::V9 + }) + ); + } + room_version.as_ref().unwrap() + }; + + let redacted = + redact(&raw_evt.deserialize_as::()?, version) + .map_err(StoreError::Redaction)?; + state.put_key_val(&key, &self.serialize_event(&redacted)?)?; + } + } + + // move forward. + cursor.advance(1)?.await?; + } + } + } + #[cfg(feature = "experimental-timeline")] if !changes.timeline.is_empty() { let timeline_store = tx.object_store(KEYS::ROOM_TIMELINE)?; diff --git a/crates/matrix-sdk-sled/src/state_store.rs b/crates/matrix-sdk-sled/src/state_store.rs index cb542e9eb..53399c562 100644 --- a/crates/matrix-sdk-sled/src/state_store.rs +++ b/crates/matrix-sdk-sled/src/state_store.rs @@ -34,15 +34,8 @@ use matrix_sdk_base::{ #[cfg(feature = "experimental-timeline")] use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, store::BoxStream}; use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher}; -#[cfg(feature = "experimental-timeline")] -use ruma::{ - canonical_json::redact_in_place, - events::{ - room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, - }, - CanonicalJsonObject, RoomVersionId, -}; use ruma::{ + canonical_json::redact, events::{ presence::PresenceEvent, receipt::{Receipt, ReceiptType}, @@ -51,7 +44,15 @@ use ruma::{ GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, }, serde::Raw, - EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId, + CanonicalJsonObject, EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId, + RoomVersionId, UserId, +}; +#[cfg(feature = "experimental-timeline")] +use ruma::{ + canonical_json::redact_in_place, + events::{ + room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, + }, }; #[cfg(feature = "experimental-timeline")] use serde::Deserialize; @@ -61,7 +62,7 @@ use sled::{ Config, Db, Transactional, Tree, }; use tokio::task::spawn_blocking; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; #[cfg(feature = "crypto-store")] use super::OpenStoreError; @@ -773,6 +774,54 @@ impl SledStateStore { ret?; + if !changes.redactions.is_empty() { + let mut redactions_found = false; + let mut redactions_batch = sled::Batch::default(); + + let make_room_version = |room_id| { + self.room_info + .get(&self.encode_key(ROOM_INFO, room_id)) + .ok() + .flatten() + .map(|r| self.deserialize_value::(&r)) + .transpose() + .ok() + .flatten() + .and_then(|info| info.room_version().cloned()) + .unwrap_or_else(|| { + warn!(%room_id, "Unable to find the room version, assume version 9"); + RoomVersionId::V9 + }) + }; + + for (room_id, redactions) in &changes.redactions { + let key_prefix = self.encode_key(ROOM_STATE, room_id); + let mut room_version = None; + + // iterate through all saved state events and check whether they are among the + // redacted, if so apply redaction and save that to the batch + // applied all at once after. + for (key, evt) in self.room_state.scan_prefix(key_prefix).filter_map(|r| r.ok()) { + let raw_evt = self.deserialize_value::>(&evt)?; + if let Ok(Some(event_id)) = raw_evt.get_field::("event_id") { + if redactions.contains_key(&event_id) { + let redacted = redact( + &raw_evt.deserialize_as::()?, + room_version.get_or_insert_with(|| make_room_version(room_id)), + ) + .map_err(StoreError::Redaction)?; + redactions_found = true; + redactions_batch.insert(key, self.serialize_value(&redacted)?); + } + } + } + } + + if redactions_found { + self.room_state.apply_batch(redactions_batch)?; + } + } + let ret: Result<(), TransactionError> = (&self.room_user_receipts, &self.room_event_receipts, &self.presence).transaction( |(room_user_receipts, room_event_receipts, presence)| { @@ -1414,8 +1463,6 @@ impl SledStateStore { #[cfg(feature = "experimental-timeline")] async fn save_room_timeline(&self, changes: &StateChanges) -> Result<()> { - use tracing::warn; - let mut timeline_batch = sled::Batch::default(); let mut event_id_to_position_batch = sled::Batch::default(); let mut timeline_metadata_batch = sled::Batch::default(); diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 663bf2f5f..9719b5d3b 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -1862,6 +1862,7 @@ impl Client { res } + #[tracing::instrument(skip(self))] async fn send_inner( &self, request: Request, diff --git a/testing/matrix-sdk-integration-testing/Cargo.toml b/testing/matrix-sdk-integration-testing/Cargo.toml index eb1a21d6b..44e3eb710 100644 --- a/testing/matrix-sdk-integration-testing/Cargo.toml +++ b/testing/matrix-sdk-integration-testing/Cargo.toml @@ -13,4 +13,5 @@ matrix-sdk = { path = "../../crates/matrix-sdk" } once_cell = "1.13.0" tempfile = "3.3.0" tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } +tracing = "0.1.36" tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } diff --git a/testing/matrix-sdk-integration-testing/src/helpers.rs b/testing/matrix-sdk-integration-testing/src/helpers.rs new file mode 100644 index 000000000..07ce46d5b --- /dev/null +++ b/testing/matrix-sdk-integration-testing/src/helpers.rs @@ -0,0 +1,68 @@ +use std::{collections::HashMap, option_env}; + +use anyhow::Result; +use assign::assign; +use matrix_sdk::{ + ruma::api::client::{account::register::v3::Request as RegistrationRequest, uiaa}, + store::make_store_config, + Client, +}; +use once_cell::sync::Lazy; +use tempfile::{tempdir, TempDir}; +use tokio::sync::Mutex; + +static USERS: Lazy>> = Lazy::new(Mutex::default); + +#[ctor::ctor] +fn init_logging() { + use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::from_default_env()) + .with(tracing_subscriber::fmt::layer().with_test_writer()) + .init(); +} + +/// read the test configuration from the environment +pub fn test_server_conf() -> (String, String) { + ( + option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned(), + option_env!("HOMESERVER_DOMAIN").unwrap_or("matrix-sdk.rs").to_owned(), + ) +} + +pub async fn get_client_for_user(username: String) -> Result { + let mut users = USERS.lock().await; + if let Some((client, _)) = users.get(&username) { + return Ok(client.clone()); + } + + let (homeserver_url, _domain_name) = test_server_conf(); + + let tmp_dir = tempdir()?; + + let client = Client::builder() + .user_agent("matrix-sdk-integation-tests") + .store_config(make_store_config(tmp_dir.path(), None)?) + .homeserver_url(homeserver_url) + .build() + .await?; + // safe to assume we have not registered this user yet, but ignore if we did + + if let Err(resp) = client.register(RegistrationRequest::new()).await { + // FIXME: do actually check the registration types... + if let Some(_response) = resp.uiaa_response() { + let request = assign!(RegistrationRequest::new(), { + username: Some(username.as_ref()), + password: Some(username.as_ref()), + + auth: Some(uiaa::AuthData::Dummy(uiaa::Dummy::new())), + }); + // we don't care if this failed, then we just try to login anyways + let _ = client.register(request).await; + } + } + client.login_username(&username, &username).send().await?; + users.insert(username, (client.clone(), tmp_dir)); // keeping temp dir around so it doesn't get destroyed yet + + Ok(client) +} diff --git a/testing/matrix-sdk-integration-testing/src/lib.rs b/testing/matrix-sdk-integration-testing/src/lib.rs index 87c277195..5c74eb97b 100644 --- a/testing/matrix-sdk-integration-testing/src/lib.rs +++ b/testing/matrix-sdk-integration-testing/src/lib.rs @@ -1,2 +1,5 @@ #[cfg(test)] mod tests; + +#[cfg(test)] +mod helpers; diff --git a/testing/matrix-sdk-integration-testing/src/tests.rs b/testing/matrix-sdk-integration-testing/src/tests.rs index 8ffd1c19f..6ec7ef3b9 100644 --- a/testing/matrix-sdk-integration-testing/src/tests.rs +++ b/testing/matrix-sdk-integration-testing/src/tests.rs @@ -1,70 +1,2 @@ -use std::{collections::HashMap, option_env}; - -use anyhow::Result; -use assign::assign; -use matrix_sdk::{ - ruma::api::client::{account::register::v3::Request as RegistrationRequest, uiaa}, - store::make_store_config, - Client, -}; -use once_cell::sync::Lazy; -use tempfile::{tempdir, TempDir}; -use tokio::sync::Mutex; - -static USERS: Lazy>> = Lazy::new(Mutex::default); - -#[ctor::ctor] -fn init_logging() { - use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - tracing_subscriber::registry() - .with(tracing_subscriber::EnvFilter::from_default_env()) - .with(tracing_subscriber::fmt::layer().with_test_writer()) - .init(); -} - -/// read the test configuration from the environment -pub fn test_server_conf() -> (String, String) { - ( - option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned(), - option_env!("HOMESERVER_DOMAIN").unwrap_or("matrix-sdk.rs").to_owned(), - ) -} - -pub async fn get_client_for_user(username: String) -> Result { - let mut users = USERS.lock().await; - if let Some((client, _)) = users.get(&username) { - return Ok(client.clone()); - } - - let (homeserver_url, _domain_name) = test_server_conf(); - - let tmp_dir = tempdir()?; - - let client = Client::builder() - .user_agent("matrix-sdk-integation-tests") - .store_config(make_store_config(tmp_dir.path(), None)?) - .homeserver_url(homeserver_url) - .build() - .await?; - // safe to assume we have not registered this user yet, but ignore if we did - - if let Err(resp) = client.register(RegistrationRequest::new()).await { - // FIXME: do actually check the registration types... - if let Some(_response) = resp.uiaa_response() { - let request = assign!(RegistrationRequest::new(), { - username: Some(username.as_ref()), - password: Some(username.as_ref()), - - auth: Some(uiaa::AuthData::Dummy(uiaa::Dummy::new())), - }); - // we don't care if this failed, then we just try to login anyways - let _ = client.register(request).await; - } - } - client.login_username(&username, &username).send().await?; - users.insert(username, (client.clone(), tmp_dir)); // keeping temp dir around so it doesn't get destroyed yet - - Ok(client) -} - mod invitations; +mod redaction; diff --git a/testing/matrix-sdk-integration-testing/src/tests/invitations.rs b/testing/matrix-sdk-integration-testing/src/tests/invitations.rs index 23ddfe0d1..810508612 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/invitations.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/invitations.rs @@ -4,7 +4,7 @@ use matrix_sdk::{ room::Room, ruma::api::client::room::create_room::v3::Request as CreateRoomRequest, }; -use super::get_client_for_user; +use crate::helpers::get_client_for_user; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_invitation_details() -> Result<()> { diff --git a/testing/matrix-sdk-integration-testing/src/tests/redaction.rs b/testing/matrix-sdk-integration-testing/src/tests/redaction.rs new file mode 100644 index 000000000..066fbb2c6 --- /dev/null +++ b/testing/matrix-sdk-integration-testing/src/tests/redaction.rs @@ -0,0 +1,157 @@ +use anyhow::Result; +use assign::assign; +use matrix_sdk::{ + config::SyncSettings, + ruma::{ + api::client::room::create_room::v3::Request as CreateRoomRequest, + events::{ + room::name::{RoomNameEventContent, SyncRoomNameEvent}, + StateEventType, + }, + }, + Client, +}; + +use crate::helpers::get_client_for_user; + +async fn sync_once(client: &Client) -> Result<()> { + let settings = match client.sync_token().await { + Some(token) => SyncSettings::default().token(token), + None => SyncSettings::default(), + }; + client.sync_once(settings).await?; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_redacting_name() -> Result<()> { + let tamatoa = get_client_for_user("tamatoa".to_owned()).await?; + // create a room + let request = assign!(CreateRoomRequest::new(), { + is_direct: true, + }); + + let room_id = tamatoa.create_room(request).await?.room_id; + for _ in 0..=10 { + sync_once(&tamatoa).await?; + if tamatoa.get_joined_room(&room_id).is_some() { + break; + } + } + + let room = tamatoa.get_joined_room(&room_id).unwrap(); + // let's send a specific state event + + let content = RoomNameEventContent::new(Some("Inappropriate text".to_owned())); + + room.send_state_event(content).await?; + // sync up. + for _ in 0..=10 { + // we call sync up to ten times to give the server time to flush other + // messages over and send us the new state event + sync_once(&tamatoa).await?; + + if room.name().is_some() { + break; + } + } + + assert_eq!(room.name(), Some("Inappropriate text".to_owned())); + // check state event. + + let raw_event = + room.get_state_event(StateEventType::RoomName, "").await?.expect("Room Name not found"); + let room_name_event: SyncRoomNameEvent = raw_event.deserialize_as()?; + assert!( + room_name_event.as_original().expect("event exists").content.name.is_some(), + "Event not found" + ); + + room.redact(room_name_event.event_id(), None, None).await?; + // sync up. + for _ in 0..=10 { + // we call sync up to ten times to give the server time to flush other + // messages over and send us the new state ev + sync_once(&tamatoa).await?; + + if room.name().is_none() { + break; + } + } + + let raw_event = + room.get_state_event(StateEventType::RoomName, "").await?.expect("Room Name not found"); + let room_name_event: SyncRoomNameEvent = raw_event.deserialize_as()?; + // Name content has been redacted + assert!( + room_name_event.as_original().expect("event exists").content.name.is_none(), + "Event hasn't been redacted" + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_redacting_name_static() -> Result<()> { + let tamatoa = get_client_for_user("tamatoa".to_owned()).await?; + // create a room + let request = assign!(CreateRoomRequest::new(), { + is_direct: true, + }); + + let room_id = tamatoa.create_room(request).await?.room_id; + for _ in 0..=10 { + sync_once(&tamatoa).await?; + if tamatoa.get_joined_room(&room_id).is_some() { + break; + } + } + + let room = tamatoa.get_joined_room(&room_id).unwrap(); + + // let's send a specific state event + let content = RoomNameEventContent::new(Some("Inappropriate text".to_owned())); + + room.send_state_event(content).await?; + // sync up. + for _ in 0..=10 { + // we call sync up to ten times to give the server time to flush other + // messages over and send us the new state event + sync_once(&tamatoa).await?; + + if room.name().is_some() { + break; + } + } + + // check state event. + + let room_name_event: SyncRoomNameEvent = + room.get_state_event_static().await?.expect("Room Name not found").deserialize()?; + assert!( + room_name_event.as_original().expect("event exists").content.name.is_some(), + "Event not found" + ); + + room.redact(room_name_event.event_id(), None, None).await?; + // we sync up. + for _ in 0..=10 { + // we call sync up to ten times to give the server time to flush other + // messages over and send us the new state ev + sync_once(&tamatoa).await?; + + if room.name().is_none() { + break; + } + } + + let room_name_event: SyncRoomNameEvent = + room.get_state_event_static().await?.expect("Room Name not found").deserialize()?; + // Name content has been redacted + assert!( + room_name_event.as_original().expect("event exists").content.name.is_none(), + "Event hasn't been redacted" + ); + + Ok(()) +} diff --git a/testing/matrix-sdk-test/src/test_json/mod.rs b/testing/matrix-sdk-test/src/test_json/mod.rs index f44245a6f..b7472d36a 100644 --- a/testing/matrix-sdk-test/src/test_json/mod.rs +++ b/testing/matrix-sdk-test/src/test_json/mod.rs @@ -29,7 +29,7 @@ pub use sync_events::{ ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED, MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE, PUSH_RULES, REACTION, READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, TAG, - TOPIC, TYPING, + TOPIC, TOPIC_REDACTION, TYPING, }; /// An empty response. diff --git a/testing/matrix-sdk-test/src/test_json/sync_events.rs b/testing/matrix-sdk-test/src/test_json/sync_events.rs index 04eab8456..109451ac5 100644 --- a/testing/matrix-sdk-test/src/test_json/sync_events.rs +++ b/testing/matrix-sdk-test/src/test_json/sync_events.rs @@ -739,6 +739,7 @@ pub static TOPIC: Lazy = Lazy::new(|| { "origin_server_ts": 151957878, "sender": "@example:localhost", "state_key": "", + "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "type": "m.room.topic", "prev_content": { "topic": "test" @@ -763,3 +764,18 @@ pub static TYPING: Lazy = Lazy::new(|| { "type": "m.typing" }) }); + +pub static TOPIC_REDACTION: Lazy = Lazy::new(|| { + json!({ + "content": {}, + "redacts": "$151957878228ssqrJ:localhost", + "event_id": "$151957878228ssqrJ_REDACTION:localhost", + "origin_server_ts": 151957879, + "sender": "@example:localhost", + "type": "m.room.redaction", + "unsigned": { + "age": 1392990, + "prev_sender": "@example:localhost", + } + }) +});