use std::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
};
use assert_matches::assert_matches;
use assert_matches2::assert_let;
use matrix_sdk_common::{
deserialized_responses::{
AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, TimelineEvent, TimelineEventKind,
UnableToDecryptInfo, UnableToDecryptReason, VerificationState,
},
linked_chunk::{
ChunkContent, ChunkIdentifier as CId, LinkedChunkId, Position, Update, lazy_loader,
},
};
use matrix_sdk_test::{ALICE, DEFAULT_TEST_ROOM_ID, event_factory::EventFactory};
use ruma::{
EventId, RoomId, event_id,
events::{
AnyMessageLikeEvent, AnyTimelineEvent, relation::RelationType,
room::message::RoomMessageEventContentWithoutRelation,
},
push::Action,
room_id,
};
use super::DynEventCacheStore;
use crate::event_cache::{Gap, store::DEFAULT_CHUNK_CAPACITY};
pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent {
make_test_event_with_event_id(room_id, content, None)
}
pub fn make_encrypted_test_event(room_id: &RoomId, session_id: &str) -> TimelineEvent {
let device_id = "DEVICEID";
let builder = EventFactory::new()
.encrypted("", "curve_key", device_id, session_id)
.room(room_id)
.sender(*ALICE);
let event = builder.into_raw();
let utd_info = UnableToDecryptInfo {
session_id: Some(session_id.to_owned()),
reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
};
TimelineEvent::from_utd(event, utd_info)
}
pub fn make_test_event_with_event_id(
room_id: &RoomId,
content: &str,
event_id: Option<&EventId>,
) -> TimelineEvent {
let encryption_info = Arc::new(EncryptionInfo {
sender: (*ALICE).into(),
sender_device: None,
forwarder: None,
algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
curve25519_key: "1337".to_owned(),
sender_claimed_keys: Default::default(),
session_id: Some("mysessionid9".to_owned()),
},
verification_state: VerificationState::Verified,
});
let mut builder = EventFactory::new().text_msg(content).room(room_id).sender(*ALICE);
if let Some(event_id) = event_id {
builder = builder.event_id(event_id);
}
let event = builder.into_raw();
TimelineEvent::from_decrypted(
DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info: None },
Some(vec![Action::Notify]),
)
}
#[track_caller]
pub fn check_test_event(event: &TimelineEvent, text: &str) {
let actions = event.push_actions().unwrap();
assert_eq!(actions.len(), 1);
assert_matches!(&actions[0], Action::Notify);
assert_matches!(&event.kind, TimelineEventKind::Decrypted(d) => {
assert_eq!(d.encryption_info.sender, *ALICE);
assert_matches!(&d.encryption_info.algorithm_info, AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => {
assert_eq!(curve25519_key, "1337");
});
let deserialized = d.event.deserialize().unwrap();
assert_matches!(deserialized, AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(msg)) => {
assert_eq!(msg.as_original().unwrap().content.body(), text);
});
});
}
#[allow(async_fn_in_trait)]
pub trait EventCacheStoreIntegrationTests {
async fn test_handle_updates_and_rebuild_linked_chunk(&self);
async fn test_linked_chunk_exists_before_referenced(&self);
async fn test_linked_chunk_allows_same_event_in_room_and_thread(&self);
async fn test_load_last_chunk(&self);
async fn test_load_last_chunk_with_a_cycle(&self);
async fn test_load_previous_chunk(&self);
async fn test_linked_chunk_incremental_loading(&self);
async fn test_linked_chunk_remove_chunk(&self);
async fn test_linked_chunk_replace_item(&self);
async fn test_linked_chunk_remove_item(&self);
async fn test_linked_chunk_detach_last_items(&self);
async fn test_linked_chunk_start_end_reattach_items(&self);
async fn test_linked_chunk_clear(&self);
async fn test_linked_chunk_clear_and_reinsert(&self);
async fn test_rebuild_empty_linked_chunk(&self);
async fn test_linked_chunk_multiple_rooms(&self);
async fn test_load_all_chunks_metadata(&self);
async fn test_clear_all_linked_chunks(&self);
async fn test_remove_room(&self);
async fn test_filter_duplicated_events(&self);
async fn test_filter_duplicate_events_no_events(&self);
async fn test_find_event(&self);
async fn test_find_event_when_event_in_room_and_thread(&self);
async fn test_find_event_relations(&self);
async fn test_find_event_relations_when_event_in_room_and_thread(&self);
async fn test_get_room_events(&self);
async fn test_get_room_events_filtered(&self);
async fn test_get_room_events_with_event_in_room_and_thread(&self);
async fn test_save_event(&self);
async fn test_save_event_updates_event_in_room_and_thread(&self);
async fn test_thread_vs_room_linked_chunk(&self);
}
impl EventCacheStoreIntegrationTests for DynEventCacheStore {
async fn test_handle_updates_and_rebuild_linked_chunk(&self) {
let room_id = room_id!("!r0:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![
make_test_event(room_id, "hello"),
make_test_event(room_id, "world"),
],
},
Update::NewGapChunk {
previous: Some(CId::new(0)),
new: CId::new(1),
next: None,
gap: Gap { token: "parmesan".to_owned() },
},
Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
Update::PushItems {
at: Position::new(CId::new(2), 0),
items: vec![make_test_event(room_id, "sup")],
},
],
)
.await
.unwrap();
let lc = lazy_loader::from_all_chunks::<3, _, _>(
self.load_all_chunks(linked_chunk_id).await.unwrap(),
)
.unwrap()
.unwrap();
let mut chunks = lc.chunks();
{
let first = chunks.next().unwrap();
assert_eq!(first.identifier(), CId::new(0));
assert_matches!(first.content(), ChunkContent::Items(events) => {
assert_eq!(events.len(), 2);
check_test_event(&events[0], "hello");
check_test_event(&events[1], "world");
});
}
{
let second = chunks.next().unwrap();
assert_eq!(second.identifier(), CId::new(1));
assert_matches!(second.content(), ChunkContent::Gap(gap) => {
assert_eq!(gap.token, "parmesan");
});
}
{
let third = chunks.next().unwrap();
assert_eq!(third.identifier(), CId::new(2));
assert_matches!(third.content(), ChunkContent::Items(events) => {
assert_eq!(events.len(), 1);
check_test_event(&events[0], "sup");
});
}
assert!(chunks.next().is_none());
}
async fn test_linked_chunk_exists_before_referenced(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![Update::NewItemsChunk {
previous: Some(CId::new(41)),
new: CId::new(42),
next: None,
}],
)
.await
.unwrap_err();
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![Update::NewItemsChunk {
previous: None,
new: CId::new(42),
next: Some(CId::new(43)),
}],
)
.await
.unwrap_err();
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![Update::NewGapChunk {
previous: Some(CId::new(41)),
new: CId::new(42),
next: None,
gap: Gap { token: "gap".to_owned() },
}],
)
.await
.unwrap_err();
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![Update::NewGapChunk {
previous: None,
new: CId::new(42),
next: Some(CId::new(43)),
gap: Gap { token: "gap".to_owned() },
}],
)
.await
.unwrap_err();
}
async fn test_linked_chunk_allows_same_event_in_room_and_thread(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let thread_root = event_id!("$thread_root");
let event_id = event_id!("$thread_reply");
let event = make_test_event_with_event_id(room_id, "thread reply", Some(event_id));
let room_linked_chunk_id = LinkedChunkId::Room(room_id);
let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
self.handle_linked_chunk_updates(
room_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
thread_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event] },
],
)
.await
.unwrap();
let room_chunks = self.load_all_chunks(room_linked_chunk_id).await.unwrap();
let thread_chunks = self.load_all_chunks(thread_linked_chunk_id).await.unwrap();
assert_eq!(room_chunks.len(), 1);
assert_eq!(thread_chunks.len(), 1);
assert_matches!(&room_chunks[0].content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_id().as_deref(), Some(event_id));
});
assert_matches!(&thread_chunks[0].content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_id().as_deref(), Some(event_id));
});
}
async fn test_load_all_chunks_metadata(&self) {
let room_id = room_id!("!r0:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![
make_test_event(room_id, "hello"),
make_test_event(room_id, "world"),
],
},
Update::NewGapChunk {
previous: Some(CId::new(0)),
new: CId::new(1),
next: None,
gap: Gap { token: "parmesan".to_owned() },
},
Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
Update::PushItems {
at: Position::new(CId::new(2), 0),
items: vec![make_test_event(room_id, "sup")],
},
Update::NewItemsChunk { previous: Some(CId::new(2)), new: CId::new(3), next: None },
],
)
.await
.unwrap();
let metas = self.load_all_chunks_metadata(linked_chunk_id).await.unwrap();
assert_eq!(metas.len(), 4);
assert_eq!(metas[0].identifier, CId::new(0));
assert_eq!(metas[0].previous, None);
assert_eq!(metas[0].next, Some(CId::new(1)));
assert_eq!(metas[0].num_items, 2);
assert_eq!(metas[1].identifier, CId::new(1));
assert_eq!(metas[1].previous, Some(CId::new(0)));
assert_eq!(metas[1].next, Some(CId::new(2)));
assert_eq!(metas[1].num_items, 0);
assert_eq!(metas[2].identifier, CId::new(2));
assert_eq!(metas[2].previous, Some(CId::new(1)));
assert_eq!(metas[2].next, Some(CId::new(3)));
assert_eq!(metas[2].num_items, 1);
assert_eq!(metas[3].identifier, CId::new(3));
assert_eq!(metas[3].previous, Some(CId::new(2)));
assert_eq!(metas[3].next, None);
assert_eq!(metas[3].num_items, 0);
}
async fn test_load_last_chunk(&self) {
let room_id = room_id!("!r0:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event = |msg: &str| make_test_event(room_id, msg);
{
let (last_chunk, chunk_identifier_generator) =
self.load_last_chunk(linked_chunk_id).await.unwrap();
assert!(last_chunk.is_none());
assert_eq!(chunk_identifier_generator.current(), 0);
}
{
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![event("saucisse de morteau"), event("comtƩ")],
},
],
)
.await
.unwrap();
let (last_chunk, chunk_identifier_generator) =
self.load_last_chunk(linked_chunk_id).await.unwrap();
assert_matches!(last_chunk, Some(last_chunk) => {
assert_eq!(last_chunk.identifier, 42);
assert!(last_chunk.previous.is_none());
assert!(last_chunk.next.is_none());
assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
assert_eq!(items.len(), 2);
check_test_event(&items[0], "saucisse de morteau");
check_test_event(&items[1], "comtƩ");
});
});
assert_eq!(chunk_identifier_generator.current(), 42);
}
{
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk {
previous: Some(CId::new(42)),
new: CId::new(7),
next: None,
},
Update::PushItems {
at: Position::new(CId::new(7), 0),
items: vec![event("fondue"), event("gruyĆØre"), event("mont d'or")],
},
],
)
.await
.unwrap();
let (last_chunk, chunk_identifier_generator) =
self.load_last_chunk(linked_chunk_id).await.unwrap();
assert_matches!(last_chunk, Some(last_chunk) => {
assert_eq!(last_chunk.identifier, 7);
assert_matches!(last_chunk.previous, Some(previous) => {
assert_eq!(previous, 42);
});
assert!(last_chunk.next.is_none());
assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
assert_eq!(items.len(), 3);
check_test_event(&items[0], "fondue");
check_test_event(&items[1], "gruyĆØre");
check_test_event(&items[2], "mont d'or");
});
});
assert_eq!(chunk_identifier_generator.current(), 42);
}
}
async fn test_load_last_chunk_with_a_cycle(&self) {
let room_id = room_id!("!r0:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::NewItemsChunk {
previous: Some(CId::new(0)),
new: CId::new(1),
next: Some(CId::new(0)),
},
],
)
.await
.unwrap();
self.load_last_chunk(linked_chunk_id).await.unwrap_err();
}
async fn test_load_previous_chunk(&self) {
let room_id = room_id!("!r0:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event = |msg: &str| make_test_event(room_id, msg);
{
let previous_chunk =
self.load_previous_chunk(linked_chunk_id, CId::new(153)).await.unwrap();
assert!(previous_chunk.is_none());
}
{
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
)
.await
.unwrap();
let previous_chunk =
self.load_previous_chunk(linked_chunk_id, CId::new(42)).await.unwrap();
assert!(previous_chunk.is_none());
}
{
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk {
previous: None,
new: CId::new(7),
next: Some(CId::new(42)),
},
Update::PushItems {
at: Position::new(CId::new(7), 0),
items: vec![event("brigand du jorat"), event("morbier")],
},
],
)
.await
.unwrap();
let previous_chunk =
self.load_previous_chunk(linked_chunk_id, CId::new(42)).await.unwrap();
assert_matches!(previous_chunk, Some(previous_chunk) => {
assert_eq!(previous_chunk.identifier, 7);
assert!(previous_chunk.previous.is_none());
assert_matches!(previous_chunk.next, Some(next) => {
assert_eq!(next, 42);
});
assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
assert_eq!(items.len(), 2);
check_test_event(&items[0], "brigand du jorat");
check_test_event(&items[1], "morbier");
});
});
}
}
async fn test_linked_chunk_incremental_loading(&self) {
let room_id = room_id!("!r0:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event = |msg: &str| make_test_event(room_id, msg);
{
let (last_chunk, chunk_identifier_generator) =
self.load_last_chunk(linked_chunk_id).await.unwrap();
assert!(last_chunk.is_none());
assert_eq!(chunk_identifier_generator.current(), 0);
}
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![event("a"), event("b")],
},
Update::NewGapChunk {
previous: Some(CId::new(0)),
new: CId::new(1),
next: None,
gap: Gap { token: "morbier".to_owned() },
},
Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
Update::PushItems {
at: Position::new(CId::new(2), 0),
items: vec![event("c"), event("d"), event("e")],
},
],
)
.await
.unwrap();
let mut linked_chunk = {
let (last_chunk, chunk_identifier_generator) =
self.load_last_chunk(linked_chunk_id).await.unwrap();
assert_eq!(chunk_identifier_generator.current(), 2);
let linked_chunk = lazy_loader::from_last_chunk::<DEFAULT_CHUNK_CAPACITY, _, _>(
last_chunk,
chunk_identifier_generator,
)
.unwrap() .unwrap();
let mut rchunks = linked_chunk.rchunks();
assert_matches!(rchunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 2);
assert_eq!(chunk.lazy_previous(), Some(CId::new(1)));
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
assert_eq!(events.len(), 3);
check_test_event(&events[0], "c");
check_test_event(&events[1], "d");
check_test_event(&events[2], "e");
});
});
assert!(rchunks.next().is_none());
linked_chunk
};
{
let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
let previous_chunk =
self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap();
lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
let mut rchunks = linked_chunk.rchunks();
assert_matches!(rchunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 2);
assert!(chunk.lazy_previous().is_none());
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
assert_eq!(events.len(), 3);
check_test_event(&events[0], "c");
check_test_event(&events[1], "d");
check_test_event(&events[2], "e");
});
});
assert_matches!(rchunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 1);
assert_eq!(chunk.lazy_previous(), Some(CId::new(0)));
assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
assert_eq!(gap.token, "morbier");
});
});
assert!(rchunks.next().is_none());
}
{
let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
let previous_chunk =
self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap();
lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
let mut rchunks = linked_chunk.rchunks();
assert_matches!(rchunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 2);
assert!(chunk.lazy_previous().is_none());
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
assert_eq!(events.len(), 3);
check_test_event(&events[0], "c");
check_test_event(&events[1], "d");
check_test_event(&events[2], "e");
});
});
assert_matches!(rchunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 1);
assert!(chunk.lazy_previous().is_none());
assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
assert_eq!(gap.token, "morbier");
});
});
assert_matches!(rchunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 0);
assert!(chunk.lazy_previous().is_none());
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
assert_eq!(events.len(), 2);
check_test_event(&events[0], "a");
check_test_event(&events[1], "b");
});
});
assert!(rchunks.next().is_none());
}
{
let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
let previous_chunk =
self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap();
assert!(previous_chunk.is_none());
}
{
let mut chunks = linked_chunk.chunks();
assert_matches!(chunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 0);
assert!(chunk.lazy_previous().is_none());
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
assert_eq!(events.len(), 2);
check_test_event(&events[0], "a");
check_test_event(&events[1], "b");
});
});
assert_matches!(chunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 1);
assert!(chunk.lazy_previous().is_none());
assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
assert_eq!(gap.token, "morbier");
});
});
assert_matches!(chunks.next(), Some(chunk) => {
assert_eq!(chunk.identifier(), 2);
assert!(chunk.lazy_previous().is_none());
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
assert_eq!(events.len(), 3);
check_test_event(&events[0], "c");
check_test_event(&events[1], "d");
check_test_event(&events[2], "e");
});
});
assert!(chunks.next().is_none());
}
}
async fn test_linked_chunk_remove_chunk(&self) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewGapChunk {
previous: None,
new: CId::new(42),
next: None,
gap: Gap { token: "raclette".to_owned() },
},
Update::NewGapChunk {
previous: Some(CId::new(42)),
new: CId::new(43),
next: None,
gap: Gap { token: "fondue".to_owned() },
},
Update::NewGapChunk {
previous: Some(CId::new(43)),
new: CId::new(44),
next: None,
gap: Gap { token: "tartiflette".to_owned() },
},
Update::RemoveChunk(CId::new(43)),
],
)
.await
.unwrap();
let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 2);
let c = chunks.remove(0);
assert_eq!(c.identifier, CId::new(42));
assert_eq!(c.previous, None);
assert_eq!(c.next, Some(CId::new(44)));
assert_matches!(c.content, ChunkContent::Gap(gap) => {
assert_eq!(gap.token, "raclette");
});
let c = chunks.remove(0);
assert_eq!(c.identifier, CId::new(44));
assert_eq!(c.previous, Some(CId::new(42)));
assert_eq!(c.next, None);
assert_matches!(c.content, ChunkContent::Gap(gap) => {
assert_eq!(gap.token, "tartiflette");
});
}
async fn test_linked_chunk_replace_item(&self) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event_id = event_id!("$world");
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![
make_test_event(room_id, "hello"),
make_test_event_with_event_id(room_id, "world", Some(event_id)),
],
},
Update::ReplaceItem {
at: Position::new(CId::new(42), 1),
item: make_test_event_with_event_id(room_id, "yolo", Some(event_id)),
},
],
)
.await
.unwrap();
let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, CId::new(42));
assert_eq!(c.previous, None);
assert_eq!(c.next, None);
assert_matches!(c.content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 2);
check_test_event(&events[0], "hello");
check_test_event(&events[1], "yolo");
});
}
async fn test_linked_chunk_remove_item(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![
make_test_event(room_id, "one"),
make_test_event(room_id, "two"),
make_test_event(room_id, "three"),
make_test_event(room_id, "four"),
make_test_event(room_id, "five"),
make_test_event(room_id, "six"),
],
},
Update::RemoveItem { at: Position::new(CId::new(42), 2) },
],
)
.await
.unwrap();
let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, CId::new(42));
assert_eq!(c.previous, None);
assert_eq!(c.next, None);
assert_matches!(c.content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 5);
check_test_event(&events[0], "one");
check_test_event(&events[1], "two");
check_test_event(&events[2], "four");
check_test_event(&events[3], "five");
check_test_event(&events[4], "six");
});
}
async fn test_linked_chunk_detach_last_items(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![
make_test_event(room_id, "hello"),
make_test_event(room_id, "world"),
make_test_event(room_id, "howdy"),
],
},
Update::DetachLastItems { at: Position::new(CId::new(42), 1) },
],
)
.await
.unwrap();
let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, CId::new(42));
assert_eq!(c.previous, None);
assert_eq!(c.next, None);
assert_matches!(c.content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 1);
check_test_event(&events[0], "hello");
});
}
async fn test_linked_chunk_start_end_reattach_items(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![
make_test_event(room_id, "hello"),
make_test_event(room_id, "world"),
make_test_event(room_id, "howdy"),
],
},
Update::StartReattachItems,
Update::EndReattachItems,
],
)
.await
.unwrap();
let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, CId::new(42));
assert_eq!(c.previous, None);
assert_eq!(c.next, None);
assert_matches!(c.content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 3);
check_test_event(&events[0], "hello");
check_test_event(&events[1], "world");
check_test_event(&events[2], "howdy");
});
}
async fn test_linked_chunk_clear(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event_0 = make_test_event(room_id, "hello");
let event_1 = make_test_event(room_id, "world");
let event_2 = make_test_event(room_id, "howdy");
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::NewGapChunk {
previous: Some(CId::new(42)),
new: CId::new(54),
next: None,
gap: Gap { token: "fondue".to_owned() },
},
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![event_0.clone(), event_1, event_2],
},
Update::Clear,
],
)
.await
.unwrap();
let chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
assert!(chunks.is_empty());
}
async fn test_linked_chunk_clear_and_reinsert(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event_0 = make_test_event(room_id, "hello");
let event_1 = make_test_event(room_id, "world");
let event_2 = make_test_event(room_id, "howdy");
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::NewGapChunk {
previous: Some(CId::new(42)),
new: CId::new(54),
next: None,
gap: Gap { token: "fondue".to_owned() },
},
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![event_0.clone(), event_1, event_2],
},
Update::Clear,
],
)
.await
.unwrap();
let chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
assert!(chunks.is_empty());
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::PushItems { at: Position::new(CId::new(42), 0), items: vec![event_0] },
],
)
.await
.unwrap();
}
async fn test_rebuild_empty_linked_chunk(&self) {
let linked_chunk = lazy_loader::from_all_chunks::<3, _, _>(
self.load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)).await.unwrap(),
)
.unwrap();
assert!(linked_chunk.is_none());
}
async fn test_linked_chunk_multiple_rooms(&self) {
let room1 = room_id!("!realcheeselovers:raclette.fr");
let linked_chunk_id1 = LinkedChunkId::Room(room1);
let room2 = room_id!("!realcheeselovers:fondue.ch");
let linked_chunk_id2 = LinkedChunkId::Room(room2);
self.handle_linked_chunk_updates(
linked_chunk_id1,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![
make_test_event(room1, "best cheese is raclette"),
make_test_event(room1, "obviously"),
],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
linked_chunk_id2,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
Update::PushItems {
at: Position::new(CId::new(42), 0),
items: vec![make_test_event(room1, "beaufort is the best")],
},
],
)
.await
.unwrap();
let mut chunks_room1 = self.load_all_chunks(linked_chunk_id1).await.unwrap();
assert_eq!(chunks_room1.len(), 1);
let c = chunks_room1.remove(0);
assert_matches!(c.content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 2);
check_test_event(&events[0], "best cheese is raclette");
check_test_event(&events[1], "obviously");
});
let mut chunks_room2 = self.load_all_chunks(linked_chunk_id2).await.unwrap();
assert_eq!(chunks_room2.len(), 1);
let c = chunks_room2.remove(0);
assert_matches!(c.content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 1);
check_test_event(&events[0], "beaufort is the best");
});
}
async fn test_clear_all_linked_chunks(&self) {
let r0 = room_id!("!r0:matrix.org");
let linked_chunk_id0 = LinkedChunkId::Room(r0);
let r1 = room_id!("!r1:matrix.org");
let linked_chunk_id1 = LinkedChunkId::Room(r1);
self.handle_linked_chunk_updates(
linked_chunk_id0,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
linked_chunk_id1,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::NewGapChunk {
previous: Some(CId::new(0)),
new: CId::new(1),
next: None,
gap: Gap { token: "bleu d'auvergne".to_owned() },
},
Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
Update::PushItems {
at: Position::new(CId::new(2), 0),
items: vec![make_test_event(r0, "yummy")],
},
],
)
.await
.unwrap();
assert!(
lazy_loader::from_all_chunks::<3, _, _>(
self.load_all_chunks(linked_chunk_id0).await.unwrap()
)
.unwrap()
.is_some()
);
assert!(
lazy_loader::from_all_chunks::<3, _, _>(
self.load_all_chunks(linked_chunk_id1).await.unwrap()
)
.unwrap()
.is_some()
);
self.clear_all_linked_chunks().await.unwrap();
assert!(
lazy_loader::from_all_chunks::<3, _, _>(
self.load_all_chunks(linked_chunk_id0).await.unwrap()
)
.unwrap()
.is_none()
);
assert!(
lazy_loader::from_all_chunks::<3, _, _>(
self.load_all_chunks(linked_chunk_id1).await.unwrap()
)
.unwrap()
.is_none()
);
}
async fn test_remove_room(&self) {
let r0 = room_id!("!r0:matrix.org");
let linked_chunk_id0 = LinkedChunkId::Room(r0);
let r1 = room_id!("!r1:matrix.org");
let linked_chunk_id1 = LinkedChunkId::Room(r1);
self.handle_linked_chunk_updates(
linked_chunk_id0,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
linked_chunk_id1,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![make_test_event(r0, "yummy")],
},
],
)
.await
.unwrap();
self.remove_room(r0).await.unwrap();
let r0_linked_chunk = self.load_all_chunks(linked_chunk_id0).await.unwrap();
assert!(r0_linked_chunk.is_empty());
let r1_linked_chunk = self.load_all_chunks(linked_chunk_id1).await.unwrap();
assert!(!r1_linked_chunk.is_empty());
}
async fn test_filter_duplicated_events(&self) {
let room_id = room_id!("!r0:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
let another_room_id = room_id!("!r1:matrix.org");
let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
let event = |msg: &str| make_test_event(room_id, msg);
let event_comte = event("comtƩ");
let event_brigand = event("brigand du jorat");
let event_raclette = event("raclette");
let event_morbier = event("morbier");
let event_gruyere = event("gruyĆØre");
let event_tome = event("tome");
let event_mont_dor = event("mont d'or");
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![event_comte.clone(), event_brigand.clone()],
},
Update::NewGapChunk {
previous: Some(CId::new(0)),
new: CId::new(1),
next: None,
gap: Gap { token: "brillat-savarin".to_owned() },
},
Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
Update::PushItems {
at: Position::new(CId::new(2), 0),
items: vec![event_morbier.clone(), event_mont_dor.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
another_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![event_tome.clone()],
},
],
)
.await
.unwrap();
let duplicated_events = BTreeMap::from_iter(
self.filter_duplicated_events(
linked_chunk_id,
vec![
event_comte.event_id().unwrap(),
event_raclette.event_id().unwrap(),
event_morbier.event_id().unwrap(),
event_gruyere.event_id().unwrap(),
event_tome.event_id().unwrap(),
event_mont_dor.event_id().unwrap(),
],
)
.await
.unwrap(),
);
assert_eq!(duplicated_events.len(), 3);
assert_eq!(
*duplicated_events.get(&event_comte.event_id().unwrap()).unwrap(),
Position::new(CId::new(0), 0)
);
assert_eq!(
*duplicated_events.get(&event_morbier.event_id().unwrap()).unwrap(),
Position::new(CId::new(2), 0)
);
assert_eq!(
*duplicated_events.get(&event_mont_dor.event_id().unwrap()).unwrap(),
Position::new(CId::new(2), 1)
);
}
async fn test_filter_duplicate_events_no_events(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let duplicates = self.filter_duplicated_events(linked_chunk_id, Vec::new()).await.unwrap();
assert!(duplicates.is_empty());
}
async fn test_find_event(&self) {
let room_id = room_id!("!r0:matrix.org");
let another_room_id = room_id!("!r1:matrix.org");
let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
let event = |msg: &str| make_test_event(room_id, msg);
let event_comte = event("comtƩ");
let event_gruyere = event("gruyĆØre");
self.handle_linked_chunk_updates(
LinkedChunkId::Room(room_id),
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![event_comte.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
another_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![event_gruyere.clone()],
},
],
)
.await
.unwrap();
let event = self
.find_event(room_id, event_comte.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.expect("failed to find an event");
assert_eq!(event.event_id(), event_comte.event_id());
assert!(
self.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.is_none()
);
self.clear_all_linked_chunks().await.expect("failed to clear all rooms chunks");
assert!(
self.find_event(room_id, event_comte.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.is_none()
);
}
async fn test_find_event_when_event_in_room_and_thread(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let thread_root = event_id!("$thread_root");
let room_event_id = event_id!("$room_event");
let room_event = make_test_event_with_event_id(room_id, "room event", Some(room_event_id));
let thread_event_id = event_id!("$thread_event");
let thread_event =
make_test_event_with_event_id(room_id, "thread event", Some(thread_event_id));
let room_and_thread_event_id = event_id!("$room_and_thread");
let room_and_thread_event = make_test_event_with_event_id(
room_id,
"room and thread",
Some(room_and_thread_event_id),
);
let room_linked_chunk_id = LinkedChunkId::Room(room_id);
let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
self.handle_linked_chunk_updates(
room_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems {
at: Position::new(CId::new(1), 0),
items: vec![room_event, room_and_thread_event.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
thread_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems {
at: Position::new(CId::new(1), 0),
items: vec![thread_event, room_and_thread_event],
},
],
)
.await
.unwrap();
assert_matches!(self.find_event(room_id, room_event_id).await, Ok(Some(event)) => {
assert_eq!(event.event_id().unwrap(), room_event_id)
});
assert_matches!(self.find_event(room_id, thread_event_id).await, Ok(Some(event)) => {
assert_eq!(event.event_id().unwrap(), thread_event_id)
});
assert_matches!(self.find_event(room_id, room_and_thread_event_id).await, Ok(Some(event)) => {
assert_eq!(event.event_id().unwrap(), room_and_thread_event_id);
});
}
async fn test_find_event_relations(&self) {
let room_id = room_id!("!r0:matrix.org");
let another_room_id = room_id!("!r1:matrix.org");
let f = EventFactory::new().room(room_id).sender(*ALICE);
let eid1 = event_id!("$event1:matrix.org");
let e1 = f.text_msg("comter").event_id(eid1).into_event();
let edit_eid1 = event_id!("$edit_event1:matrix.org");
let edit_e1 = f
.text_msg("* comtƩ")
.event_id(edit_eid1)
.edit(eid1, RoomMessageEventContentWithoutRelation::text_plain("comtƩ"))
.into_event();
let reaction_eid1 = event_id!("$reaction_event1:matrix.org");
let reaction_e1 = f.reaction(eid1, "š").event_id(reaction_eid1).into_event();
let eid2 = event_id!("$event2:matrix.org");
let e2 = f.text_msg("galette saucisse").event_id(eid2).into_event();
let f = f.room(another_room_id);
let eid3 = event_id!("$event3:matrix.org");
let e3 = f.text_msg("gruyĆØre").event_id(eid3).into_event();
let reaction_eid3 = event_id!("$reaction_event3:matrix.org");
let reaction_e3 = f.reaction(eid3, "š").event_id(reaction_eid3).into_event();
self.save_event(room_id, e1).await.unwrap();
self.save_event(room_id, edit_e1).await.unwrap();
self.save_event(room_id, reaction_e1.clone()).await.unwrap();
self.save_event(room_id, e2).await.unwrap();
self.save_event(another_room_id, e3).await.unwrap();
self.save_event(another_room_id, reaction_e3).await.unwrap();
let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
assert_eq!(relations.len(), 2);
assert!(
relations
.iter()
.any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none())
);
assert!(
relations
.iter()
.any(|(ev, pos)| ev.event_id().as_deref() == Some(reaction_eid1) && pos.is_none())
);
let relations = self
.find_event_relations(room_id, eid1, Some(&[RelationType::Replacement]))
.await
.unwrap();
assert_eq!(relations.len(), 1);
assert_eq!(relations[0].0.event_id().as_deref(), Some(edit_eid1));
let relations = self
.find_event_relations(
room_id,
eid1,
Some(&[RelationType::Replacement, RelationType::Annotation]),
)
.await
.unwrap();
assert_eq!(relations.len(), 2);
assert!(relations.iter().any(|r| r.0.event_id().as_deref() == Some(edit_eid1)));
assert!(relations.iter().any(|r| r.0.event_id().as_deref() == Some(reaction_eid1)));
let relations = self
.find_event_relations(another_room_id, eid1, Some(&[RelationType::Replacement]))
.await
.unwrap();
assert!(relations.is_empty());
self.handle_linked_chunk_updates(
LinkedChunkId::Room(room_id),
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems { at: Position::new(CId::new(0), 0), items: vec![reaction_e1] },
],
)
.await
.unwrap();
let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
assert!(relations.iter().any(|(ev, pos)| {
ev.event_id().as_deref() == Some(reaction_eid1)
&& *pos == Some(Position::new(CId::new(0), 0))
}));
assert!(
relations
.iter()
.any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none())
);
}
async fn test_find_event_relations_when_event_in_room_and_thread(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let thread_root = event_id!("$thread_root");
let event_id = event_id!("$event");
let event = make_test_event_with_event_id(room_id, "event", Some(event_id));
let extra_thread_event_id = event_id!("$extra_thread_event");
let extra_thread_event = make_test_event_with_event_id(
room_id,
"extra thread event",
Some(extra_thread_event_id),
);
let room_reaction_id = event_id!("$room_reaction");
let room_reaction = EventFactory::new()
.room(room_id)
.sender(*ALICE)
.reaction(event_id, "room")
.event_id(room_reaction_id)
.into_event();
let thread_reaction_id = event_id!("$thread_reaction");
let thread_reaction = EventFactory::new()
.room(room_id)
.sender(*ALICE)
.reaction(event_id, "thread")
.event_id(thread_reaction_id)
.into_event();
let room_and_thread_reaction_id = event_id!("$room_and_thread_reaction");
let room_and_thread_reaction = EventFactory::new()
.room(room_id)
.sender(*ALICE)
.reaction(event_id, "room and thread")
.event_id(room_and_thread_reaction_id)
.into_event();
let room_linked_chunk_id = LinkedChunkId::Room(room_id);
let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
self.handle_linked_chunk_updates(
room_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems {
at: Position::new(CId::new(1), 0),
items: vec![event.clone(), room_reaction, room_and_thread_reaction.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
thread_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems {
at: Position::new(CId::new(1), 0),
items: vec![
event.clone(),
extra_thread_event,
thread_reaction,
room_and_thread_reaction,
],
},
],
)
.await
.unwrap();
assert_matches!(self.find_event_relations(room_id, event_id, None).await, Ok(relations) => {
assert_eq!(relations.len(), 3);
let room_relation = relations
.iter()
.find(|relation| relation.0.event_id().unwrap() == room_reaction_id)
.unwrap();
assert_matches!(room_relation, (_, Some(position)) => {
assert_eq!(*position, Position::new(CId::new(1), 1));
});
let thread_relation = relations
.iter()
.find(|relation| relation.0.event_id().unwrap() == thread_reaction_id)
.unwrap();
assert_matches!(thread_relation, (_, None));
let room_and_thread_relation = relations
.iter()
.find(|relation| relation.0.event_id().unwrap() == room_and_thread_reaction_id)
.unwrap();
assert_matches!(room_and_thread_relation, (_, Some(position)) => {
assert_eq!(*position, Position::new(CId::new(1), 2));
});
});
}
async fn test_get_room_events(&self) {
let room_id = room_id!("!r0:matrix.org");
let another_room_id = room_id!("!r1:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
let event = |msg: &str| make_test_event(room_id, msg);
let event_comte = event("comtƩ");
let event_gruyere = event("gruyĆØre");
let event_stilton = event("stilton");
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![event_comte.clone(), event_gruyere.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
another_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![event_stilton.clone()],
},
],
)
.await
.unwrap();
let events = self
.get_room_events(room_id, None, None)
.await
.expect("failed to query for room events");
assert_eq!(events.len(), 2);
let got_ids: Vec<_> = events.into_iter().map(|ev| ev.event_id()).collect();
let expected_ids = vec![event_comte.event_id(), event_gruyere.event_id()];
for expected in expected_ids {
assert!(
got_ids.contains(&expected),
"Expected event {expected:?} not in got events: {got_ids:?}."
);
}
}
async fn test_get_room_events_filtered(&self) {
macro_rules! assert_expected_events {
($events:expr, [$($item:expr),* $(,)?]) => {{
let got_ids: BTreeSet<_> = $events.into_iter().map(|ev| ev.event_id().unwrap()).collect();
let expected_ids = BTreeSet::from([$($item.event_id().unwrap()),*]);
assert_eq!(got_ids, expected_ids);
}};
}
let room_id = room_id!("!r0:matrix.org");
let linked_chunk_id = LinkedChunkId::Room(room_id);
let another_room_id = room_id!("!r1:matrix.org");
let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
let event = |session_id: &str| make_encrypted_test_event(room_id, session_id);
let first_event = event("session_1");
let second_event = event("session_2");
let third_event = event("session_3");
let fourth_event = make_test_event(room_id, "It's a secret to everybody");
self.handle_linked_chunk_updates(
linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![first_event.clone(), second_event.clone(), fourth_event.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
another_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![third_event.clone()],
},
],
)
.await
.unwrap();
let events = self
.get_room_events(room_id, Some("m.room.encrypted"), None)
.await
.expect("failed to query for room events");
assert_eq!(events.len(), 2);
assert_expected_events!(events, [first_event, second_event]);
let events = self
.get_room_events(room_id, Some("m.room.encrypted"), Some("session_1"))
.await
.expect("failed to query for room events");
assert_eq!(events.len(), 1);
assert_expected_events!(events, [first_event]);
}
async fn test_get_room_events_with_event_in_room_and_thread(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let thread_root = event_id!("$thread_root");
let room_event_id = event_id!("$room_event");
let room_event = make_test_event_with_event_id(room_id, "room event", Some(room_event_id));
let thread_event_id = event_id!("$thread_event");
let thread_event =
make_test_event_with_event_id(room_id, "thread event", Some(thread_event_id));
let room_and_thread_event_id = event_id!("$room_and_thread");
let room_and_thread_event = make_test_event_with_event_id(
room_id,
"room and thread",
Some(room_and_thread_event_id),
);
let room_linked_chunk_id = LinkedChunkId::Room(room_id);
let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
self.handle_linked_chunk_updates(
room_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems {
at: Position::new(CId::new(1), 0),
items: vec![room_event, room_and_thread_event.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
thread_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems {
at: Position::new(CId::new(1), 0),
items: vec![thread_event, room_and_thread_event],
},
],
)
.await
.unwrap();
let expected_event_ids =
BTreeSet::from([room_event_id, thread_event_id, room_and_thread_event_id]);
assert_matches!(self.get_room_events(room_id, None, None).await, Ok(events) => {
assert_eq!(events.len(), 3);
assert!(events.iter().all(|event| {
expected_event_ids.contains(&event.event_id().unwrap().as_ref())
}));
});
}
async fn test_save_event(&self) {
let room_id = room_id!("!r0:matrix.org");
let another_room_id = room_id!("!r1:matrix.org");
let event = |msg: &str| make_test_event(room_id, msg);
let event_comte = event("comtƩ");
let event_gruyere = event("gruyĆØre");
self.save_event(room_id, event_comte.clone()).await.unwrap();
self.save_event(another_room_id, event_gruyere.clone()).await.unwrap();
let event = self
.find_event(room_id, event_comte.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.expect("failed to find an event");
assert_eq!(event.event_id(), event_comte.event_id());
let event = self
.find_event(another_room_id, event_gruyere.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.expect("failed to find an event");
assert_eq!(event.event_id(), event_gruyere.event_id());
assert!(
self.find_event(another_room_id, event_comte.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.is_none()
);
assert!(
self.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.is_none()
);
}
async fn test_save_event_updates_event_in_room_and_thread(&self) {
let room_id = *DEFAULT_TEST_ROOM_ID;
let thread_root = event_id!("$thread_root");
let event_id = event_id!("$event");
let event = make_test_event_with_event_id(room_id, "event", Some(event_id));
let room_linked_chunk_id = LinkedChunkId::Room(room_id);
let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
self.handle_linked_chunk_updates(
room_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
thread_linked_chunk_id,
vec![
Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
],
)
.await
.unwrap();
let updated_content = "updated content";
let updated = make_test_event_with_event_id(room_id, updated_content, Some(event_id));
self.save_event(room_id, updated).await.unwrap();
let room_chunks = self.load_all_chunks(room_linked_chunk_id).await.unwrap();
let thread_chunks = self.load_all_chunks(thread_linked_chunk_id).await.unwrap();
assert_eq!(room_chunks.len(), 1);
assert_eq!(thread_chunks.len(), 1);
assert_matches!(&room_chunks[0].content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_id().as_deref(), Some(event_id));
check_test_event(&events[0], updated_content);
});
assert_matches!(&thread_chunks[0].content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_id().as_deref(), Some(event_id));
check_test_event(&events[0], updated_content);
});
}
async fn test_thread_vs_room_linked_chunk(&self) {
let room_id = room_id!("!r0:matrix.org");
let event = |msg: &str| make_test_event(room_id, msg);
let thread1_ev = event("comtƩ");
let thread2_ev = event("gruyĆØre");
let thread2_ev2 = event("beaufort");
let room_ev = event("brillat savarin triple crĆØme");
let thread_root1 = event("thread1");
let thread_root2 = event("thread2");
self.handle_linked_chunk_updates(
LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap().as_ref()),
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![thread1_ev.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
LinkedChunkId::Thread(room_id, thread_root2.event_id().unwrap().as_ref()),
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![thread2_ev.clone(), thread2_ev2.clone()],
},
],
)
.await
.unwrap();
self.handle_linked_chunk_updates(
LinkedChunkId::Room(room_id),
vec![
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
Update::PushItems {
at: Position::new(CId::new(0), 0),
items: vec![room_ev.clone()],
},
],
)
.await
.unwrap();
self.find_event(room_id, thread2_ev.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.expect("failed to find thread1_ev");
self.find_event(room_id, thread2_ev.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.expect("failed to find thread2_ev");
self.find_event(room_id, thread2_ev2.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.expect("failed to find thread2_ev2");
self.find_event(room_id, room_ev.event_id().unwrap().as_ref())
.await
.expect("failed to query for finding an event")
.expect("failed to find room_ev");
let dups = self
.filter_duplicated_events(
LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap().as_ref()),
vec![thread1_ev.event_id().unwrap(), room_ev.event_id().unwrap()],
)
.await
.unwrap();
assert_eq!(dups.len(), 1);
assert_eq!(dups[0].0, thread1_ev.event_id().unwrap());
let all_chunks = self
.load_all_chunks(LinkedChunkId::Thread(
room_id,
thread_root2.event_id().unwrap().as_ref(),
))
.await
.unwrap();
assert_eq!(all_chunks.len(), 1);
assert_eq!(all_chunks[0].identifier, CId::new(0));
assert_let!(ChunkContent::Items(observed_items) = all_chunks[0].content.clone());
assert_eq!(observed_items.len(), 2);
assert_eq!(observed_items[0].event_id(), thread2_ev.event_id());
assert_eq!(observed_items[1].event_id(), thread2_ev2.event_id());
let metas = self
.load_all_chunks_metadata(LinkedChunkId::Thread(
room_id,
thread_root2.event_id().unwrap().as_ref(),
))
.await
.unwrap();
assert_eq!(metas.len(), 1);
assert_eq!(metas[0].identifier, CId::new(0));
assert_eq!(metas[0].num_items, 2);
let (last_chunk, _chunk_identifier_generator) = self
.load_last_chunk(LinkedChunkId::Thread(
room_id,
thread_root1.event_id().unwrap().as_ref(),
))
.await
.unwrap();
let last_chunk = last_chunk.unwrap();
assert_eq!(last_chunk.identifier, CId::new(0));
assert_let!(ChunkContent::Items(observed_items) = last_chunk.content);
assert_eq!(observed_items.len(), 1);
assert_eq!(observed_items[0].event_id(), thread1_ev.event_id());
}
}
#[allow(unused_macros, unused_extern_crates)]
#[macro_export]
macro_rules! event_cache_store_integration_tests {
() => {
mod event_cache_store_integration_tests {
use matrix_sdk_test::async_test;
use $crate::event_cache::store::{
EventCacheStoreIntegrationTests, IntoEventCacheStore,
};
use super::get_event_cache_store;
#[async_test]
async fn test_handle_updates_and_rebuild_linked_chunk() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
}
#[async_test]
async fn test_linked_chunk_exists_before_referenced() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_exists_before_referenced().await;
}
#[async_test]
async fn test_linked_chunk_allow_same_event_in_room_and_thread() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_allows_same_event_in_room_and_thread().await;
}
#[async_test]
async fn test_load_last_chunk() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_load_last_chunk().await;
}
#[async_test]
async fn test_load_last_chunk_with_a_cycle() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_load_last_chunk_with_a_cycle().await;
}
#[async_test]
async fn test_load_previous_chunk() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_load_previous_chunk().await;
}
#[async_test]
async fn test_linked_chunk_incremental_loading() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_incremental_loading().await;
}
#[async_test]
async fn test_linked_chunk_remove_chunk() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_remove_chunk().await;
}
#[async_test]
async fn test_linked_chunk_replace_item() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_replace_item().await;
}
#[async_test]
async fn test_linked_chunk_remove_item() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_remove_item().await;
}
#[async_test]
async fn test_linked_chunk_detach_last_items() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_detach_last_items().await;
}
#[async_test]
async fn test_linked_chunk_start_end_reattach_items() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_start_end_reattach_items().await;
}
#[async_test]
async fn test_linked_chunk_clear() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_clear().await;
}
#[async_test]
async fn test_linked_chunk_clear_and_reinsert() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_clear_and_reinsert().await;
}
#[async_test]
async fn test_rebuild_empty_linked_chunk() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_rebuild_empty_linked_chunk().await;
}
#[async_test]
async fn test_linked_chunk_multiple_rooms() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_linked_chunk_multiple_rooms().await;
}
#[async_test]
async fn test_load_all_chunks_metadata() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_load_all_chunks_metadata().await;
}
#[async_test]
async fn test_clear_all_linked_chunks() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_clear_all_linked_chunks().await;
}
#[async_test]
async fn test_remove_room() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_remove_room().await;
}
#[async_test]
async fn test_filter_duplicated_events() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_filter_duplicated_events().await;
}
#[async_test]
async fn test_filter_duplicate_events_no_events() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_filter_duplicate_events_no_events().await;
}
#[async_test]
async fn test_find_event() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_find_event().await;
}
#[async_test]
async fn test_find_event_when_event_in_room_and_thread() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_find_event_when_event_in_room_and_thread().await;
}
#[async_test]
async fn test_find_event_relations() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_find_event_relations().await;
}
#[async_test]
async fn test_find_event_relations_when_event_in_room_and_thread() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_find_event_relations_when_event_in_room_and_thread().await;
}
#[async_test]
async fn test_get_room_events() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_get_room_events().await;
}
#[async_test]
async fn test_get_room_events_filtered() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_get_room_events_filtered().await;
}
#[async_test]
async fn test_get_room_events_with_event_in_room_and_thread() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_get_room_events_with_event_in_room_and_thread().await;
}
#[async_test]
async fn test_save_event() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_save_event().await;
}
#[async_test]
async fn test_save_event_updates_event_in_room_and_thread() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_save_event_updates_event_in_room_and_thread().await;
}
#[async_test]
async fn test_thread_vs_room_linked_chunk() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_thread_vs_room_linked_chunk().await;
}
}
};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! event_cache_store_integration_tests_time {
() => {
mod event_cache_store_integration_tests_time {
use std::time::Duration;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use gloo_timers::future::sleep;
use matrix_sdk_test::async_test;
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
use tokio::time::sleep;
use $crate::event_cache::store::IntoEventCacheStore;
use super::get_event_cache_store;
#[async_test]
async fn test_lease_locks() {
let store = get_event_cache_store().await.unwrap().into_event_cache_store();
let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
assert_eq!(acquired0, Some(1));
let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
assert_eq!(acquired2, Some(1));
let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
assert_eq!(acquired3, Some(1));
let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
assert!(acquired4.is_none());
let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
assert!(acquired5.is_none());
sleep(Duration::from_millis(50)).await;
let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
assert!(acquired55.is_none());
sleep(Duration::from_millis(250)).await;
let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
assert_eq!(acquired6, Some(2));
sleep(Duration::from_millis(1)).await;
let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
assert_eq!(acquired7, Some(3));
sleep(Duration::from_millis(1)).await;
let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
assert_eq!(acquired8, Some(4));
let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
assert!(acquired9.is_none());
let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
assert_eq!(acquired10, Some(4)); }
}
};
}