//! Macro of integration tests for StateStore implementations. /// Macro building to allow your StateStore implementation to run the entire /// tests suite locally. /// /// You need to provide a `async fn get_store() -> StoreResult` /// providing a fresh store on the same level you invoke the macro. /// /// ## Usage Example: /// ```no_run /// # use matrix_sdk_base::store::{ /// # StateStore, /// # MemoryStore as MyStore, /// # Result as StoreResult, /// # }; /// /// #[cfg(test)] /// mod tests { /// use super::{MyStore, StateStore, StoreResult}; /// /// async fn get_store() -> StoreResult { /// Ok(MyStore::new()) /// } /// /// statestore_integration_tests!(); /// } /// ``` #[allow(unused_macros, unused_extern_crates)] #[macro_export] macro_rules! statestore_integration_tests { () => { mod statestore_integration_tests { use std::{ collections::{BTreeMap, BTreeSet}, sync::Arc, }; #[cfg(feature = "experimental-timeline")] use futures_util::StreamExt; use matrix_sdk_test::{async_test, test_json}; #[cfg(feature = "experimental-timeline")] use ruma::api::{ client::{ message::get_message_events::v3::Response as MessageResponse, sync::sync_events::v3::Response as SyncResponse, }, IncomingResponse, }; use ruma::{ api::client::media::get_content_thumbnail::v3::Method, event_id, events::{ presence::PresenceEvent, receipt::ReceiptType, room::{ member::{ MembershipState, OriginalSyncRoomMemberEvent, RoomMemberEventContent, RoomMemberUnsigned, StrippedRoomMemberEvent, SyncRoomMemberEvent, }, power_levels::RoomPowerLevelsEventContent, MediaSource, topic::{RoomTopicEventContent, OriginalRoomTopicEvent, RedactedRoomTopicEvent}, }, AnyEphemeralRoomEventContent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, }, mxc_uri, room_id, serde::Raw, uint, user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId, }; use serde_json::{json, Value as JsonValue}; #[cfg(feature = "experimental-timeline")] use $crate::{ deserialized_responses::{SyncTimelineEvent, TimelineEvent, TimelineSlice}, http::Response, }; use $crate::{ media::{MediaFormat, MediaRequest, MediaThumbnailSize}, store::{Result as StoreResult, StateChanges, StateStore, StateStoreExt}, RoomInfo, RoomType, }; use super::get_store; fn user_id() -> &'static UserId { user_id!("@example:localhost") } pub(crate) fn invited_user_id() -> &'static UserId { user_id!("@invited:localhost") } pub(crate) fn room_id() -> &'static RoomId { room_id!("!test:localhost") } pub(crate) fn stripped_room_id() -> &'static RoomId { room_id!("!stripped:localhost") } pub(crate) fn first_receipt_event_id() -> &'static EventId { event_id!("$example") } /// Populate the given `StateStore`. pub async fn populate_store(store: Arc) -> StoreResult<()> { let mut changes = StateChanges::default(); let user_id = user_id(); let invited_user_id = invited_user_id(); let room_id = room_id(); let stripped_room_id = stripped_room_id(); changes.sync_token = Some("t392-516_47314_0_7_1_1_1_11444_1".to_owned()); let presence_json: &JsonValue = &test_json::PRESENCE; let presence_raw = serde_json::from_value::>(presence_json.clone()).unwrap(); let presence_event = presence_raw.deserialize().unwrap(); changes.add_presence_event(presence_event, presence_raw); let pushrules_json: &JsonValue = &test_json::PUSH_RULES; let pushrules_raw = serde_json::from_value::>( pushrules_json.clone(), ) .unwrap(); let pushrules_event = pushrules_raw.deserialize().unwrap(); changes.add_account_data(pushrules_event, pushrules_raw); let mut room = RoomInfo::new(room_id, RoomType::Joined); room.mark_as_left(); let tag_json: &JsonValue = &test_json::TAG; let tag_raw = serde_json::from_value::>(tag_json.clone()) .unwrap(); let tag_event = tag_raw.deserialize().unwrap(); changes.add_room_account_data(room_id, tag_event, tag_raw); let name_json: &JsonValue = &test_json::NAME; let name_raw = serde_json::from_value::>(name_json.clone()).unwrap(); let name_event = name_raw.deserialize().unwrap(); room.handle_state_event(&name_event); changes.add_state_event(room_id, name_event, name_raw); let topic_json: &JsonValue = &test_json::TOPIC; let topic_raw = 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); let mut room_ambiguity_map = BTreeMap::new(); let mut room_profiles = BTreeMap::new(); let mut room_members = BTreeMap::new(); let member_json: &JsonValue = &test_json::MEMBER; let member_event: SyncRoomMemberEvent = serde_json::from_value(member_json.clone()).unwrap(); let displayname = member_event.as_original().unwrap().content.displayname.clone().unwrap(); room_ambiguity_map .insert(displayname.clone(), BTreeSet::from([user_id.to_owned()])); room_profiles.insert(user_id.to_owned(), (&member_event).into()); room_members.insert(user_id.to_owned(), member_event); let member_state_raw = serde_json::from_value::>(member_json.clone()).unwrap(); let member_state_event = member_state_raw.deserialize().unwrap(); changes.add_state_event(room_id, member_state_event, member_state_raw); let invited_member_json: &JsonValue = &test_json::MEMBER_INVITE; // FIXME: Should be stripped room member event let invited_member_event: SyncRoomMemberEvent = serde_json::from_value(invited_member_json.clone()).unwrap(); room_ambiguity_map .entry(displayname) .or_default() .insert(invited_user_id.to_owned()); room_profiles.insert(invited_user_id.to_owned(), (&invited_member_event).into()); room_members.insert(invited_user_id.to_owned(), invited_member_event); let invited_member_state_raw = serde_json::from_value::>(invited_member_json.clone()) .unwrap(); let invited_member_state_event = invited_member_state_raw.deserialize().unwrap(); changes.add_state_event( room_id, invited_member_state_event, invited_member_state_raw, ); let receipt_json: &JsonValue = &test_json::READ_RECEIPT; let receipt_event = serde_json::from_value::(receipt_json.clone()) .unwrap(); let receipt_content = match receipt_event.content() { AnyEphemeralRoomEventContent::Receipt(content) => content, _ => panic!(), }; changes.add_receipts(room_id, receipt_content); changes.ambiguity_maps.insert(room_id.to_owned(), room_ambiguity_map); changes.profiles.insert(room_id.to_owned(), room_profiles); changes.members.insert(room_id.to_owned(), room_members); changes.add_room(room); let mut stripped_room = RoomInfo::new(stripped_room_id, RoomType::Invited); let stripped_name_json: &JsonValue = &test_json::NAME_STRIPPED; let stripped_name_raw = serde_json::from_value::>( stripped_name_json.clone(), ) .unwrap(); let stripped_name_event = stripped_name_raw.deserialize().unwrap(); stripped_room.handle_stripped_state_event(&stripped_name_event); changes.stripped_state.insert( stripped_room_id.to_owned(), BTreeMap::from([( stripped_name_event.event_type(), BTreeMap::from([( stripped_name_event.state_key().to_owned(), stripped_name_raw.clone(), )]), )]), ); changes.add_stripped_room(stripped_room); let stripped_member_json: &JsonValue = &test_json::MEMBER_STRIPPED; let stripped_member_event = serde_json::from_value::(stripped_member_json.clone()) .unwrap(); changes.add_stripped_member(stripped_room_id, stripped_member_event); store.save_changes(&changes).await?; Ok(()) } fn power_level_event() -> Raw { let content = RoomPowerLevelsEventContent::default(); let event = json!({ "event_id": "$h29iv0s8:example.com", "content": content, "sender": user_id(), "type": "m.room.power_levels", "origin_server_ts": 0u64, "state_key": "", }); serde_json::from_value(event).unwrap() } fn stripped_membership_event() -> StrippedRoomMemberEvent { custom_stripped_membership_event(user_id()) } fn custom_stripped_membership_event(user_id: &UserId) -> StrippedRoomMemberEvent { StrippedRoomMemberEvent { content: RoomMemberEventContent::new(MembershipState::Join), sender: user_id.to_owned(), state_key: user_id.to_owned(), } } fn membership_event() -> SyncRoomMemberEvent { custom_membership_event(user_id(), event_id!("$h29iv0s8:example.com").to_owned()) } fn custom_membership_event( user_id: &UserId, event_id: OwnedEventId, ) -> SyncRoomMemberEvent { SyncRoomMemberEvent::Original(OriginalSyncRoomMemberEvent { event_id, content: RoomMemberEventContent::new(MembershipState::Join), sender: user_id.to_owned(), origin_server_ts: MilliSecondsSinceUnixEpoch(198u32.into()), state_key: user_id.to_owned(), unsigned: RoomMemberUnsigned::default(), }) } #[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(); let user_id = user_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!(store.get_presence_event(user_id).await?.is_some()); assert_eq!(store.get_room_infos().await?.len(), 1, "Expected to find 1 room info"); assert_eq!( store.get_stripped_room_infos().await?.len(), 1, "Expected to find 1 stripped room info" ); assert!(store .get_account_data_event(GlobalAccountDataEventType::PushRules) .await? .is_some()); assert!(store .get_state_event(room_id, StateEventType::RoomName, "") .await? .is_some()); assert_eq!( store.get_state_events(room_id, StateEventType::RoomTopic).await?.len(), 1, "Expected to find 1 room topic" ); assert!(store.get_profile(room_id, user_id).await?.is_some()); assert!(store.get_member_event(room_id, user_id).await?.is_some()); assert_eq!( store.get_user_ids(room_id).await?.len(), 2, "Expected to find 2 members for room" ); assert_eq!( store.get_invited_user_ids(room_id).await?.len(), 1, "Expected to find 1 invited user ids" ); assert_eq!( store.get_joined_user_ids(room_id).await?.len(), 1, "Expected to find 1 joined user ids" ); assert_eq!( store.get_users_with_display_name(room_id, "example").await?.len(), 2, "Expected to find 2 display names for room" ); assert!(store .get_room_account_data_event(room_id, RoomAccountDataEventType::Tag) .await? .is_some()); assert!(store .get_user_room_receipt_event(room_id, ReceiptType::Read, user_id) .await? .is_some()); assert_eq!( store .get_event_room_receipt_events( room_id, ReceiptType::Read, first_receipt_event_id() ) .await? .len(), 1, "Expected to find 1 read receipt" ); Ok(()) } #[async_test] async fn test_member_saving() { let store = get_store().await.unwrap(); let room_id = room_id!("!test_member_saving:localhost"); let user_id = user_id(); assert!(store.get_member_event(room_id, user_id).await.unwrap().is_none()); let mut changes = StateChanges::default(); changes .members .entry(room_id.to_owned()) .or_default() .insert(user_id.to_owned(), membership_event()); store.save_changes(&changes).await.unwrap(); assert!(store.get_member_event(room_id, user_id).await.unwrap().is_some()); let members = store.get_user_ids(room_id).await.unwrap(); assert!(!members.is_empty(), "We expected to find members for the room") } #[async_test] async fn test_filter_saving() { let store = get_store().await.unwrap(); let test_name = "filter_name"; let filter_id = "filter_id_1234"; assert_eq!(store.get_filter(test_name).await.unwrap(), None); store.save_filter(test_name, filter_id).await.unwrap(); assert_eq!(store.get_filter(test_name).await.unwrap(), Some(filter_id.to_owned())); } #[async_test] async fn test_sync_token_saving() { let mut changes = StateChanges::default(); let store = get_store().await.unwrap(); let sync_token = "t392-516_47314_0_7_1".to_owned(); changes.sync_token = Some(sync_token.clone()); assert_eq!(store.get_sync_token().await.unwrap(), None); store.save_changes(&changes).await.unwrap(); assert_eq!(store.get_sync_token().await.unwrap(), Some(sync_token)); } #[async_test] async fn test_stripped_member_saving() { let store = get_store().await.unwrap(); let room_id = room_id!("!test_stripped_member_saving:localhost"); let user_id = user_id(); assert!(store.get_member_event(room_id, user_id).await.unwrap().is_none()); let mut changes = StateChanges::default(); changes .stripped_members .entry(room_id.to_owned()) .or_default() .insert(user_id.to_owned(), stripped_membership_event()); store.save_changes(&changes).await.unwrap(); assert!(store.get_member_event(room_id, user_id).await.unwrap().is_some()); let members = store.get_user_ids(room_id).await.unwrap(); assert!(!members.is_empty(), "We expected to find members for the room") } #[async_test] async fn test_power_level_saving() { let store = get_store().await.unwrap(); let room_id = room_id!("!test_power_level_saving:localhost"); let raw_event = power_level_event(); let event = raw_event.deserialize().unwrap(); assert!(store .get_state_event(room_id, StateEventType::RoomPowerLevels, "") .await .unwrap() .is_none()); let mut changes = StateChanges::default(); changes.add_state_event(room_id, event, raw_event); store.save_changes(&changes).await.unwrap(); assert!(store .get_state_event(room_id, StateEventType::RoomPowerLevels, "") .await .unwrap() .is_some()); } #[async_test] async fn test_receipts_saving() { let store = get_store().await.expect("creating store failed"); let room_id = room_id!("!test_receipts_saving:localhost"); let first_event_id = event_id!("$1435641916114394fHBLK:matrix.org"); let second_event_id = event_id!("$fHBLK1435641916114394:matrix.org"); let first_receipt_event = serde_json::from_value(json!({ first_event_id: { "m.read": { user_id(): { "ts": 1436451550453u64 } } } })) .expect("json creation failed"); let second_receipt_event = serde_json::from_value(json!({ second_event_id: { "m.read": { user_id(): { "ts": 1436451551453u64 } } } })) .expect("json creation failed"); assert!(store .get_user_room_receipt_event(room_id, ReceiptType::Read, user_id()) .await .expect("failed to read user room receipt") .is_none()); assert!(store .get_event_room_receipt_events(room_id, ReceiptType::Read, &first_event_id) .await .expect("failed to read user room receipt for 1") .is_empty()); assert!(store .get_event_room_receipt_events(room_id, ReceiptType::Read, &second_event_id) .await .expect("failed to read user room receipt for 2") .is_empty()); let mut changes = StateChanges::default(); changes.add_receipts(room_id, first_receipt_event); store.save_changes(&changes).await.expect("writing changes fauked"); assert!(store .get_user_room_receipt_event(room_id, ReceiptType::Read, user_id()) .await .expect("failed to read user room receipt after save") .is_some()); assert_eq!( store .get_event_room_receipt_events(room_id, ReceiptType::Read, &first_event_id) .await .expect("failed to read user room receipt for 1 after save") .len(), 1, "Found a wrong number of receipts for 1 after save" ); assert!(store .get_event_room_receipt_events(room_id, ReceiptType::Read, &second_event_id) .await .expect("failed to read user room receipt for 2 after save") .is_empty()); let mut changes = StateChanges::default(); changes.add_receipts(room_id, second_receipt_event); store.save_changes(&changes).await.expect("Saving works"); assert!(store .get_user_room_receipt_event(room_id, ReceiptType::Read, user_id()) .await .expect("Getting user room receipts failed") .is_some()); assert!(store .get_event_room_receipt_events(room_id, ReceiptType::Read, &first_event_id) .await .expect("Getting event room receipt events for first event failed") .is_empty()); assert_eq!( store .get_event_room_receipt_events(room_id, ReceiptType::Read, &second_event_id) .await .expect("Getting event room receipt events for second event failed") .len(), 1, "Found a wrong number of receipts for second event after save" ); } #[async_test] async fn test_media_content() { let store = get_store().await.unwrap(); let uri = mxc_uri!("mxc://localhost/media"); let content: Vec = "somebinarydata".into(); let request_file = MediaRequest { source: MediaSource::Plain(uri.to_owned()), format: MediaFormat::File, }; let request_thumbnail = MediaRequest { source: MediaSource::Plain(uri.to_owned()), format: MediaFormat::Thumbnail(MediaThumbnailSize { method: Method::Crop, width: uint!(100), height: uint!(100), }), }; assert!( store.get_media_content(&request_file).await.unwrap().is_none(), "unexpected media found" ); assert!( store.get_media_content(&request_thumbnail).await.unwrap().is_none(), "media not found" ); store .add_media_content(&request_file, content.clone()) .await .expect("adding media failed"); assert!( store.get_media_content(&request_file).await.unwrap().is_some(), "media not found though added" ); store.remove_media_content(&request_file).await.expect("removing media failed"); assert!( store.get_media_content(&request_file).await.unwrap().is_none(), "media still there after removing" ); store .add_media_content(&request_file, content.clone()) .await .expect("adding media again failed"); assert!( store.get_media_content(&request_file).await.unwrap().is_some(), "media not found after adding again" ); store .add_media_content(&request_thumbnail, content.clone()) .await .expect("adding thumbnail failed"); assert!( store.get_media_content(&request_thumbnail).await.unwrap().is_some(), "thumbnail not found" ); store .remove_media_content_for_uri(uri) .await .expect("removing all media for uri failed"); assert!( store.get_media_content(&request_file).await.unwrap().is_none(), "media wasn't removed" ); assert!( store.get_media_content(&request_thumbnail).await.unwrap().is_none(), "thumbnail wasn't removed" ); } #[async_test] async fn test_custom_storage() -> StoreResult<()> { let key = "my_key"; let value = &[0, 1, 2, 3]; let store = get_store().await?; store.set_custom_value(key.as_bytes(), value.to_vec()).await?; let read = store.get_custom_value(key.as_bytes()).await?; assert_eq!(Some(value.as_ref()), read.as_deref()); Ok(()) } #[async_test] async fn test_persist_invited_room() -> StoreResult<()> { let inner_store = get_store().await?; let store = Arc::new(inner_store); populate_store(store.clone()).await?; assert_eq!(store.get_stripped_room_infos().await?.len(), 1); Ok(()) } #[async_test] async fn test_stripped_non_stripped() -> StoreResult<()> { let store = get_store().await.unwrap(); let room_id = room_id!("!test_stripped_non_stripped:localhost"); let user_id = user_id(); assert!(store.get_member_event(room_id, user_id).await.unwrap().is_none()); assert_eq!(store.get_room_infos().await.unwrap().len(), 0); assert_eq!(store.get_stripped_room_infos().await.unwrap().len(), 0); let mut changes = StateChanges::default(); changes .members .entry(room_id.to_owned()) .or_default() .insert(user_id.to_owned(), membership_event()); changes.add_room(RoomInfo::new(room_id, RoomType::Left)); store.save_changes(&changes).await.unwrap(); assert!(matches!( store.get_member_event(room_id, user_id).await.unwrap(), Some(matrix_sdk_common::deserialized_responses::MemberEvent::Sync(_)) )); assert_eq!(store.get_room_infos().await.unwrap().len(), 1); assert_eq!(store.get_stripped_room_infos().await.unwrap().len(), 0); let members = store.get_user_ids(room_id).await.unwrap(); assert_eq!(members, vec![user_id.to_owned()]); let mut changes = StateChanges::default(); changes.add_stripped_member(room_id, custom_stripped_membership_event(user_id)); changes.add_stripped_room(RoomInfo::new(room_id, RoomType::Invited)); store.save_changes(&changes).await.unwrap(); assert!(matches!( store.get_member_event(room_id, user_id).await.unwrap(), Some(matrix_sdk_common::deserialized_responses::MemberEvent::Stripped(_)) )); assert_eq!(store.get_room_infos().await.unwrap().len(), 0); assert_eq!(store.get_stripped_room_infos().await.unwrap().len(), 1); let members = store.get_user_ids(room_id).await.unwrap(); assert_eq!(members, vec![user_id.to_owned()]); Ok(()) } #[async_test] async fn test_room_removal() -> StoreResult<()> { let room_id = room_id(); let user_id = user_id(); let inner_store = get_store().await?; let stripped_room_id = stripped_room_id(); let store = Arc::new(inner_store); populate_store(store.clone()).await?; store.remove_room(room_id).await?; assert!(store.get_room_infos().await?.is_empty(), "room is still there"); assert_eq!(store.get_stripped_room_infos().await?.len(), 1); assert!(store .get_state_event(room_id, StateEventType::RoomName, "") .await? .is_none()); assert!( store.get_state_events(room_id, StateEventType::RoomTopic).await?.is_empty(), "still state events found" ); assert!(store.get_profile(room_id, user_id).await?.is_none()); assert!(store.get_member_event(room_id, user_id).await?.is_none()); assert!(store.get_user_ids(room_id).await?.is_empty(), "still user ids found"); assert!( store.get_invited_user_ids(room_id).await?.is_empty(), "still invited user ids found" ); assert!( store.get_joined_user_ids(room_id).await?.is_empty(), "still joined users found" ); assert!( store.get_users_with_display_name(room_id, "example").await?.is_empty(), "still display names found" ); assert!(store .get_room_account_data_event(room_id, RoomAccountDataEventType::Tag) .await? .is_none()); assert!(store .get_user_room_receipt_event(room_id, ReceiptType::Read, user_id) .await? .is_none()); assert!( store .get_event_room_receipt_events( room_id, ReceiptType::Read, first_receipt_event_id() ) .await? .is_empty(), "still event recepts in the store" ); store.remove_room(stripped_room_id).await?; assert!(store.get_room_infos().await?.is_empty(), "still room info found"); assert!( store.get_stripped_room_infos().await?.is_empty(), "still stripped room info found" ); Ok(()) } #[async_test] #[cfg(feature = "experimental-timeline")] async fn test_room_timeline() { let store = get_store().await.unwrap(); let mut stored_events = Vec::new(); let room_id = *test_json::DEFAULT_SYNC_ROOM_ID; // Before the first sync the timeline should be empty assert!( store.room_timeline(room_id).await.expect("failed to read timeline").is_none(), "TL wasn't empty" ); // Add sync response let sync = SyncResponse::try_from_http_response( Response::builder() .body( serde_json::to_vec(&*test_json::MORE_SYNC) .expect("Parsing MORE_SYNC failed"), ) .unwrap(), ) .unwrap(); let timeline = &sync.rooms.join[room_id].timeline; let events: Vec = timeline.events.iter().cloned().map(Into::into).collect(); stored_events.extend(events.iter().rev().cloned()); let timeline_slice = TimelineSlice::new( events, sync.next_batch.clone(), timeline.prev_batch.clone(), false, true, ); let mut changes = StateChanges::new(sync.next_batch.clone()); changes.add_timeline(room_id, timeline_slice); store.save_changes(&changes).await.expect("Saving room timeline failed"); check_timeline_events( room_id, &store, &stored_events, timeline.prev_batch.as_deref(), ) .await; // Add message response let messages = MessageResponse::try_from_http_response( Response::builder() .body( serde_json::to_vec(&*test_json::ROOM_MESSAGES_BATCH_1) .expect("Parsing ROOM_MESSAGES_BATCH_1 failed"), ) .unwrap(), ) .unwrap(); let events: Vec = messages .chunk .iter() .cloned() .map(|event| TimelineEvent { event, encryption_info: None }.into()) .collect(); stored_events.append(&mut events.clone()); let timeline_slice = TimelineSlice::new( events, messages.start.clone(), messages.end.clone(), false, false, ); let mut changes = StateChanges::default(); changes.add_timeline(room_id, timeline_slice); store.save_changes(&changes).await.expect("Saving room update timeline failed"); check_timeline_events(room_id, &store, &stored_events, messages.end.as_deref()) .await; // Add second message response let messages = MessageResponse::try_from_http_response( Response::builder() .body( serde_json::to_vec(&*test_json::ROOM_MESSAGES_BATCH_2) .expect("Parsing ROOM_MESSAGES_BATCH_2 failed"), ) .unwrap(), ) .unwrap(); let events: Vec = messages .chunk .iter() .cloned() .map(|event| TimelineEvent { event, encryption_info: None }.into()) .collect(); stored_events.append(&mut events.clone()); let timeline_slice = TimelineSlice::new( events, messages.start.clone(), messages.end.clone(), false, false, ); let mut changes = StateChanges::default(); changes.add_timeline(room_id, timeline_slice); store.save_changes(&changes).await.expect("Saving room update timeline 2 failed"); check_timeline_events(room_id, &store, &stored_events, messages.end.as_deref()) .await; // Add second sync response let sync = SyncResponse::try_from_http_response( Response::builder() .body(serde_json::to_vec(&*test_json::MORE_SYNC_2).unwrap()) .unwrap(), ) .unwrap(); let timeline = &sync.rooms.join[room_id].timeline; let events: Vec = timeline.events.iter().cloned().map(Into::into).collect(); let prev_stored_events = stored_events; stored_events = events.iter().rev().cloned().collect(); stored_events.extend(prev_stored_events); let timeline_slice = TimelineSlice::new( events, sync.next_batch.clone(), timeline.prev_batch.clone(), false, true, ); let mut changes = StateChanges::new(sync.next_batch.clone()); changes.add_timeline(room_id, timeline_slice); store.save_changes(&changes).await.expect("Saving room update timeline 3 failed"); check_timeline_events(room_id, &store, &stored_events, messages.end.as_deref()) .await; // Check if limited sync removes the stored timeline let end_token = Some("end token".to_owned()); let timeline_slice = TimelineSlice::new( Vec::new(), "start token".to_owned(), end_token.clone(), true, true, ); let mut changes = StateChanges::default(); changes.add_timeline(room_id, timeline_slice); store.save_changes(&changes).await.expect("Saving room update timeline 4 failed"); check_timeline_events(room_id, &store, &Vec::new(), end_token.as_deref()).await; } #[cfg(feature = "experimental-timeline")] async fn check_timeline_events( room_id: &RoomId, store: &dyn StateStore, stored_events: &[SyncTimelineEvent], expected_end_token: Option<&str>, ) { let (timeline_iter, end_token) = store.room_timeline(room_id).await.unwrap().unwrap(); assert_eq!(end_token.as_deref(), expected_end_token); let timeline = timeline_iter.collect::>>().await; let expected: Vec = stored_events .iter() .map(|a| a.event_id().expect("event id doesn't exist")) .collect(); let found: Vec = timeline .iter() .map(|a| { a.as_ref() .expect("object missing") .event_id() .clone() .expect("event id missing") }) .collect(); for (idx, (a, b)) in timeline.into_iter().zip(stored_events.iter()).enumerate() { assert_eq!( a.expect("not a value").event_id(), b.event_id(), "pos {idx} not equal - expected: {expected:#?}, but found {found:#?}", ); } } } }; }