use assert_matches::assert_matches;
use matrix_sdk_base::{
event_cache::{
store::{
integration_tests::{check_test_event, make_test_event},
EventCacheStore,
},
Gap,
},
linked_chunk::{ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update},
};
use matrix_sdk_test::DEFAULT_TEST_ROOM_ID;
use ruma::room_id;
use crate::{
event_cache_store::{IndexeddbEventCacheStore, IndexeddbEventCacheStoreError},
transaction::TransactionError,
};
pub async fn test_linked_chunk_new_items_chunk(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![
Update::NewItemsChunk {
previous: None,
new: ChunkIdentifier::new(42),
next: None, },
Update::NewItemsChunk {
previous: Some(ChunkIdentifier::new(42)),
new: ChunkIdentifier::new(13),
next: Some(ChunkIdentifier::new(37)),
},
Update::NewItemsChunk {
previous: Some(ChunkIdentifier::new(13)),
new: ChunkIdentifier::new(37),
next: None,
},
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 3);
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::new(13));
assert_eq!(c.previous, Some(ChunkIdentifier::new(42)));
assert_eq!(c.next, Some(ChunkIdentifier::new(37)));
assert_matches!(c.content, ChunkContent::Items(events) => {
assert!(events.is_empty());
});
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::new(37));
assert_eq!(c.previous, Some(ChunkIdentifier::new(13)));
assert_eq!(c.next, None);
assert_matches!(c.content, ChunkContent::Items(events) => {
assert!(events.is_empty());
});
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::new(42));
assert_eq!(c.previous, None);
assert_eq!(c.next, Some(ChunkIdentifier::new(13)));
assert_matches!(c.content, ChunkContent::Items(events) => {
assert!(events.is_empty());
});
}
pub async fn test_add_gap_chunk_and_delete_it_immediately(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![Update::NewGapChunk {
previous: None,
new: ChunkIdentifier::new(1),
next: None,
gap: Gap { prev_token: "cheese".to_owned() },
}];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let updates = vec![
Update::NewGapChunk {
previous: Some(ChunkIdentifier::new(1)),
new: ChunkIdentifier::new(3),
next: None,
gap: Gap { prev_token: "milk".to_owned() },
},
Update::RemoveChunk(ChunkIdentifier::new(3)),
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
}
pub async fn test_linked_chunk_new_gap_chunk(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![Update::NewGapChunk {
previous: None,
new: ChunkIdentifier::new(42),
next: None,
gap: Gap { prev_token: "raclette".to_owned() },
}];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::new(42));
assert_eq!(c.previous, None);
assert_eq!(c.next, None);
assert_matches!(c.content, ChunkContent::Gap(gap) => {
assert_eq!(gap.prev_token, "raclette");
});
}
pub async fn test_linked_chunk_replace_item(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 0),
items: vec![make_test_event(room_id, "hello"), make_test_event(room_id, "world")],
},
Update::ReplaceItem {
at: Position::new(ChunkIdentifier::new(42), 1),
item: make_test_event(room_id, "yolo"),
},
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::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");
});
}
pub async fn test_linked_chunk_remove_chunk(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![
Update::NewGapChunk {
previous: None,
new: ChunkIdentifier::new(42),
next: None,
gap: Gap { prev_token: "raclette".to_owned() },
},
Update::NewGapChunk {
previous: Some(ChunkIdentifier::new(42)),
new: ChunkIdentifier::new(43),
next: None,
gap: Gap { prev_token: "fondue".to_owned() },
},
Update::NewGapChunk {
previous: Some(ChunkIdentifier::new(43)),
new: ChunkIdentifier::new(44),
next: None,
gap: Gap { prev_token: "tartiflette".to_owned() },
},
Update::RemoveChunk(ChunkIdentifier::new(43)),
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 2);
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::new(42));
assert_eq!(c.previous, None);
assert_eq!(c.next, Some(ChunkIdentifier::new(44)));
assert_matches!(c.content, ChunkContent::Gap(gap) => {
assert_eq!(gap.prev_token, "raclette");
});
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::new(44));
assert_eq!(c.previous, Some(ChunkIdentifier::new(42)));
assert_eq!(c.next, None);
assert_matches!(c.content, ChunkContent::Gap(gap) => {
assert_eq!(gap.prev_token, "tartiflette");
});
}
pub async fn test_linked_chunk_push_items(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 0),
items: vec![make_test_event(room_id, "hello"), make_test_event(room_id, "world")],
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 2),
items: vec![make_test_event(room_id, "who?")],
},
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::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], "who?");
});
}
pub async fn test_linked_chunk_remove_item(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 0),
items: vec![make_test_event(room_id, "hello"), make_test_event(room_id, "world")],
},
Update::RemoveItem { at: Position::new(ChunkIdentifier::new(42), 0) },
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::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], "world");
});
}
pub async fn test_linked_chunk_detach_last_items(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::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(ChunkIdentifier::new(42), 1) },
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::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");
});
}
pub async fn test_linked_chunk_start_end_reattach_items(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::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,
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert_eq!(chunks.len(), 1);
let c = chunks.remove(0);
assert_eq!(c.identifier, ChunkIdentifier::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");
});
}
pub async fn test_linked_chunk_clear(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::NewGapChunk {
previous: Some(ChunkIdentifier::new(42)),
new: ChunkIdentifier::new(54),
next: None,
gap: Gap { prev_token: "fondue".to_owned() },
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 0),
items: vec![
make_test_event(room_id, "hello"),
make_test_event(room_id, "world"),
make_test_event(room_id, "howdy"),
],
},
Update::Clear,
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert!(chunks.is_empty());
}
pub async fn test_linked_chunk_multiple_rooms(store: IndexeddbEventCacheStore) {
let room_id1 = room_id!("!realcheeselovers:raclette.fr");
let linked_chunk_id1 = LinkedChunkId::Room(room_id1);
let updates1 = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 0),
items: vec![
make_test_event(room_id1, "best cheese is raclette"),
make_test_event(room_id1, "obviously"),
],
},
];
store.handle_linked_chunk_updates(linked_chunk_id1, updates1).await.unwrap();
let room_id2 = room_id!("!realcheeselovers:fondue.ch");
let linked_chunk_id2 = LinkedChunkId::Room(room_id2);
let updates2 = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 0),
items: vec![make_test_event(room_id2, "beaufort is the best")],
},
];
store.handle_linked_chunk_updates(linked_chunk_id2, updates2).await.unwrap();
let mut chunks1 = store.load_all_chunks(linked_chunk_id1).await.unwrap();
assert_eq!(chunks1.len(), 1);
let c = chunks1.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 chunks2 = store.load_all_chunks(linked_chunk_id2).await.unwrap();
assert_eq!(chunks2.len(), 1);
let c = chunks2.remove(0);
assert_matches!(c.content, ChunkContent::Items(events) => {
assert_eq!(events.len(), 1);
check_test_event(&events[0], "beaufort is the best");
});
}
pub async fn test_linked_chunk_update_is_a_transaction(store: IndexeddbEventCacheStore) {
let linked_chunk_id = LinkedChunkId::Room(*DEFAULT_TEST_ROOM_ID);
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
];
let err = store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap_err();
assert_matches!(
err,
IndexeddbEventCacheStoreError::Transaction(TransactionError::DomException { .. })
);
let chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
assert!(chunks.is_empty());
}
pub async fn test_load_last_chunk(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event = |msg: &str| make_test_event(room_id, msg);
let (last_chunk, chunk_identifier_generator) =
store.load_last_chunk(linked_chunk_id).await.unwrap();
assert!(last_chunk.is_none());
assert_eq!(chunk_identifier_generator.current(), 0);
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 0),
items: vec![event("saucisse de morteau"), event("comté")],
},
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let (last_chunk, chunk_identifier_generator) =
store.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);
let updates = vec![
Update::NewItemsChunk {
previous: Some(ChunkIdentifier::new(42)),
new: ChunkIdentifier::new(7),
next: None,
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(7), 0),
items: vec![event("fondue"), event("gruyère"), event("mont d'or")],
},
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let (last_chunk, chunk_identifier_generator) =
store.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);
}
pub async fn test_load_previous_chunk(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event = |msg: &str| make_test_event(room_id, msg);
let previous_chunk =
store.load_previous_chunk(linked_chunk_id, ChunkIdentifier::new(153)).await.unwrap();
assert!(previous_chunk.is_none());
let updates =
vec![Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None }];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let previous_chunk =
store.load_previous_chunk(linked_chunk_id, ChunkIdentifier::new(42)).await.unwrap();
assert!(previous_chunk.is_none());
let updates = vec![
Update::NewItemsChunk {
previous: None,
new: ChunkIdentifier::new(7),
next: Some(ChunkIdentifier::new(42)),
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(7), 0),
items: vec![event("brigand du jorat"), event("morbier")],
},
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let previous_chunk =
store.load_previous_chunk(linked_chunk_id, ChunkIdentifier::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");
});
});
}
#[macro_export]
macro_rules! indexeddb_event_cache_store_integration_tests {
() => {
mod indexeddb_event_cache_store_integration_tests {
use matrix_sdk_test::async_test;
use super::get_event_cache_store;
#[async_test]
async fn test_linked_chunk_new_items_chunk() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_new_items_chunk(store).await
}
#[async_test]
async fn test_add_gap_chunk_and_delete_it_immediately() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_add_gap_chunk_and_delete_it_immediately(
store,
)
.await
}
#[async_test]
async fn test_linked_chunk_new_gap_chunk() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_new_gap_chunk(store).await
}
#[async_test]
async fn test_linked_chunk_replace_item() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_replace_item(store).await
}
#[async_test]
async fn test_linked_chunk_remove_chunk() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_remove_chunk(store).await
}
#[async_test]
async fn test_linked_chunk_push_items() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_push_items(store).await
}
#[async_test]
async fn test_linked_chunk_remove_item() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_remove_item(store).await
}
#[async_test]
async fn test_linked_chunk_detach_last_items() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_detach_last_items(store).await
}
#[async_test]
async fn test_linked_chunk_start_end_reattach_items() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_start_end_reattach_items(store)
.await
}
#[async_test]
async fn test_linked_chunk_clear() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_clear(store).await
}
#[async_test]
async fn test_linked_chunk_multiple_rooms() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_multiple_rooms(store).await
}
#[async_test]
async fn test_linked_chunk_update_is_a_transaction() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_linked_chunk_update_is_a_transaction(store)
.await
}
#[async_test]
async fn test_load_last_chunk() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_load_last_chunk(store)
.await
}
#[async_test]
async fn test_load_previous_chunk() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_load_previous_chunk(store)
.await
}
}
};
}