1use assert_matches::assert_matches;
18use async_trait::async_trait;
19use matrix_sdk_common::{
20 deserialized_responses::{
21 AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, TimelineEvent, TimelineEventKind,
22 VerificationState,
23 },
24 linked_chunk::{
25 ChunkContent, ChunkIdentifier as CId, LinkedChunk, LinkedChunkBuilder, Position, RawChunk,
26 Update,
27 },
28};
29use matrix_sdk_test::{event_factory::EventFactory, ALICE, DEFAULT_TEST_ROOM_ID};
30use ruma::{
31 api::client::media::get_content_thumbnail::v3::Method, events::room::MediaSource, mxc_uri,
32 push::Action, room_id, uint, RoomId,
33};
34
35use super::{media::IgnoreMediaRetentionPolicy, DynEventCacheStore};
36use crate::{
37 event_cache::{Event, Gap},
38 media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings},
39};
40
41pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent {
46 let encryption_info = EncryptionInfo {
47 sender: (*ALICE).into(),
48 sender_device: None,
49 algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
50 curve25519_key: "1337".to_owned(),
51 sender_claimed_keys: Default::default(),
52 },
53 verification_state: VerificationState::Verified,
54 };
55
56 let event = EventFactory::new()
57 .text_msg(content)
58 .room(room_id)
59 .sender(*ALICE)
60 .into_raw_timeline()
61 .cast();
62
63 TimelineEvent {
64 kind: TimelineEventKind::Decrypted(DecryptedRoomEvent {
65 event,
66 encryption_info,
67 unsigned_encryption_info: None,
68 }),
69 push_actions: Some(vec![Action::Notify]),
70 }
71}
72
73#[track_caller]
78pub fn check_test_event(event: &TimelineEvent, text: &str) {
79 let actions = event.push_actions.as_ref().unwrap();
81 assert_eq!(actions.len(), 1);
82 assert_matches!(&actions[0], Action::Notify);
83
84 assert_matches!(&event.kind, TimelineEventKind::Decrypted(d) => {
86 assert_eq!(d.encryption_info.sender, *ALICE);
88 assert_matches!(&d.encryption_info.algorithm_info, AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => {
89 assert_eq!(curve25519_key, "1337");
90 });
91
92 let deserialized = d.event.deserialize().unwrap();
94 assert_matches!(deserialized, ruma::events::AnyMessageLikeEvent::RoomMessage(msg) => {
95 assert_eq!(msg.as_original().unwrap().content.body(), text);
96 });
97 });
98}
99
100#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
105#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
106pub trait EventCacheStoreIntegrationTests {
107 async fn test_media_content(&self);
109
110 async fn test_replace_media_key(&self);
112
113 async fn test_handle_updates_and_rebuild_linked_chunk(&self);
116
117 async fn test_rebuild_empty_linked_chunk(&self);
120
121 async fn test_clear_all_rooms_chunks(&self);
123
124 async fn test_remove_room(&self);
126}
127
128fn rebuild_linked_chunk(raws: Vec<RawChunk<Event, Gap>>) -> Option<LinkedChunk<3, Event, Gap>> {
129 LinkedChunkBuilder::from_raw_parts(raws).build().unwrap()
130}
131
132#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
133#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
134impl EventCacheStoreIntegrationTests for DynEventCacheStore {
135 async fn test_media_content(&self) {
136 let uri = mxc_uri!("mxc://localhost/media");
137 let request_file = MediaRequestParameters {
138 source: MediaSource::Plain(uri.to_owned()),
139 format: MediaFormat::File,
140 };
141 let request_thumbnail = MediaRequestParameters {
142 source: MediaSource::Plain(uri.to_owned()),
143 format: MediaFormat::Thumbnail(MediaThumbnailSettings::with_method(
144 Method::Crop,
145 uint!(100),
146 uint!(100),
147 )),
148 };
149
150 let other_uri = mxc_uri!("mxc://localhost/media-other");
151 let request_other_file = MediaRequestParameters {
152 source: MediaSource::Plain(other_uri.to_owned()),
153 format: MediaFormat::File,
154 };
155
156 let content: Vec<u8> = "hello".into();
157 let thumbnail_content: Vec<u8> = "world".into();
158 let other_content: Vec<u8> = "foo".into();
159
160 assert!(
162 self.get_media_content(&request_file).await.unwrap().is_none(),
163 "unexpected media found"
164 );
165 assert!(
166 self.get_media_content(&request_thumbnail).await.unwrap().is_none(),
167 "media not found"
168 );
169
170 self.add_media_content(&request_file, content.clone(), IgnoreMediaRetentionPolicy::No)
172 .await
173 .expect("adding media failed");
174
175 assert_eq!(
177 self.get_media_content(&request_file).await.unwrap().as_ref(),
178 Some(&content),
179 "media not found though added"
180 );
181 assert_eq!(
182 self.get_media_content_for_uri(uri).await.unwrap().as_ref(),
183 Some(&content),
184 "media not found by URI though added"
185 );
186
187 self.remove_media_content(&request_file).await.expect("removing media failed");
189
190 assert!(
192 self.get_media_content(&request_file).await.unwrap().is_none(),
193 "media still there after removing"
194 );
195 assert!(
196 self.get_media_content_for_uri(uri).await.unwrap().is_none(),
197 "media still found by URI after removing"
198 );
199
200 self.add_media_content(&request_file, content.clone(), IgnoreMediaRetentionPolicy::No)
202 .await
203 .expect("adding media again failed");
204
205 assert_eq!(
206 self.get_media_content(&request_file).await.unwrap().as_ref(),
207 Some(&content),
208 "media not found after adding again"
209 );
210
211 self.add_media_content(
213 &request_thumbnail,
214 thumbnail_content.clone(),
215 IgnoreMediaRetentionPolicy::No,
216 )
217 .await
218 .expect("adding thumbnail failed");
219
220 assert_eq!(
222 self.get_media_content(&request_thumbnail).await.unwrap().as_ref(),
223 Some(&thumbnail_content),
224 "thumbnail not found"
225 );
226
227 assert!(
229 self.get_media_content_for_uri(uri).await.unwrap().is_some(),
230 "media not found by URI though two where added"
231 );
232
233 self.add_media_content(
235 &request_other_file,
236 other_content.clone(),
237 IgnoreMediaRetentionPolicy::No,
238 )
239 .await
240 .expect("adding other media failed");
241
242 assert_eq!(
244 self.get_media_content(&request_other_file).await.unwrap().as_ref(),
245 Some(&other_content),
246 "other file not found"
247 );
248 assert_eq!(
249 self.get_media_content_for_uri(other_uri).await.unwrap().as_ref(),
250 Some(&other_content),
251 "other file not found by URI"
252 );
253
254 self.remove_media_content_for_uri(uri).await.expect("removing all media for uri failed");
256
257 assert!(
258 self.get_media_content(&request_file).await.unwrap().is_none(),
259 "media wasn't removed"
260 );
261 assert!(
262 self.get_media_content(&request_thumbnail).await.unwrap().is_none(),
263 "thumbnail wasn't removed"
264 );
265 assert!(
266 self.get_media_content(&request_other_file).await.unwrap().is_some(),
267 "other media was removed"
268 );
269 assert!(
270 self.get_media_content_for_uri(uri).await.unwrap().is_none(),
271 "media found by URI wasn't removed"
272 );
273 assert!(
274 self.get_media_content_for_uri(other_uri).await.unwrap().is_some(),
275 "other media found by URI was removed"
276 );
277 }
278
279 async fn test_replace_media_key(&self) {
280 let uri = mxc_uri!("mxc://sendqueue.local/tr4n-s4ct-10n1-d");
281 let req = MediaRequestParameters {
282 source: MediaSource::Plain(uri.to_owned()),
283 format: MediaFormat::File,
284 };
285
286 let content = "hello".as_bytes().to_owned();
287
288 assert!(self.get_media_content(&req).await.unwrap().is_none(), "unexpected media found");
290
291 self.add_media_content(&req, content.clone(), IgnoreMediaRetentionPolicy::No)
293 .await
294 .expect("adding media failed");
295
296 assert_eq!(self.get_media_content(&req).await.unwrap().unwrap(), b"hello");
298
299 let new_uri = mxc_uri!("mxc://matrix.org/tr4n-s4ct-10n1-d");
301 let new_req = MediaRequestParameters {
302 source: MediaSource::Plain(new_uri.to_owned()),
303 format: MediaFormat::File,
304 };
305 self.replace_media_key(&req, &new_req)
306 .await
307 .expect("replacing the media request key failed");
308
309 assert!(
311 self.get_media_content(&req).await.unwrap().is_none(),
312 "unexpected media found with the old key"
313 );
314
315 assert_eq!(self.get_media_content(&new_req).await.unwrap().unwrap(), b"hello");
317 }
318
319 async fn test_handle_updates_and_rebuild_linked_chunk(&self) {
320 let room_id = room_id!("!r0:matrix.org");
321
322 self.handle_linked_chunk_updates(
323 room_id,
324 vec![
325 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
327 Update::PushItems {
329 at: Position::new(CId::new(0), 0),
330 items: vec![
331 make_test_event(room_id, "hello"),
332 make_test_event(room_id, "world"),
333 ],
334 },
335 Update::NewGapChunk {
337 previous: Some(CId::new(0)),
338 new: CId::new(1),
339 next: None,
340 gap: Gap { prev_token: "parmesan".to_owned() },
341 },
342 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
344 Update::PushItems {
346 at: Position::new(CId::new(2), 0),
347 items: vec![make_test_event(room_id, "sup")],
348 },
349 ],
350 )
351 .await
352 .unwrap();
353
354 let raws = self.reload_linked_chunk(room_id).await.unwrap();
356 let lc = rebuild_linked_chunk(raws).expect("linked chunk not empty");
357
358 let mut chunks = lc.chunks();
359
360 {
361 let first = chunks.next().unwrap();
362 assert_eq!(first.identifier(), CId::new(0));
365
366 assert_matches!(first.content(), ChunkContent::Items(events) => {
367 assert_eq!(events.len(), 2);
368 check_test_event(&events[0], "hello");
369 check_test_event(&events[1], "world");
370 });
371 }
372
373 {
374 let second = chunks.next().unwrap();
375 assert_eq!(second.identifier(), CId::new(1));
376
377 assert_matches!(second.content(), ChunkContent::Gap(gap) => {
378 assert_eq!(gap.prev_token, "parmesan");
379 });
380 }
381
382 {
383 let third = chunks.next().unwrap();
384 assert_eq!(third.identifier(), CId::new(2));
385
386 assert_matches!(third.content(), ChunkContent::Items(events) => {
387 assert_eq!(events.len(), 1);
388 check_test_event(&events[0], "sup");
389 });
390 }
391
392 assert!(chunks.next().is_none());
393 }
394
395 async fn test_rebuild_empty_linked_chunk(&self) {
396 let raw_parts = self.reload_linked_chunk(&DEFAULT_TEST_ROOM_ID).await.unwrap();
398 assert!(rebuild_linked_chunk(raw_parts).is_none());
399 }
400
401 async fn test_clear_all_rooms_chunks(&self) {
402 let r0 = room_id!("!r0:matrix.org");
403 let r1 = room_id!("!r1:matrix.org");
404
405 self.handle_linked_chunk_updates(
407 r0,
408 vec![
409 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
411 Update::PushItems {
413 at: Position::new(CId::new(0), 0),
414 items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
415 },
416 ],
417 )
418 .await
419 .unwrap();
420
421 self.handle_linked_chunk_updates(
423 r1,
424 vec![
425 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
427 Update::NewGapChunk {
429 previous: Some(CId::new(0)),
430 new: CId::new(1),
431 next: None,
432 gap: Gap { prev_token: "bleu d'auvergne".to_owned() },
433 },
434 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
436 Update::PushItems {
438 at: Position::new(CId::new(2), 0),
439 items: vec![make_test_event(r0, "yummy")],
440 },
441 ],
442 )
443 .await
444 .unwrap();
445
446 assert!(rebuild_linked_chunk(self.reload_linked_chunk(r0).await.unwrap()).is_some());
448 assert!(rebuild_linked_chunk(self.reload_linked_chunk(r1).await.unwrap()).is_some());
449
450 self.clear_all_rooms_chunks().await.unwrap();
452
453 assert!(rebuild_linked_chunk(self.reload_linked_chunk(r0).await.unwrap()).is_none());
455 assert!(rebuild_linked_chunk(self.reload_linked_chunk(r1).await.unwrap()).is_none());
456 }
457
458 async fn test_remove_room(&self) {
459 let r0 = room_id!("!r0:matrix.org");
460 let r1 = room_id!("!r1:matrix.org");
461
462 self.handle_linked_chunk_updates(
464 r0,
465 vec![
466 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
468 Update::PushItems {
470 at: Position::new(CId::new(0), 0),
471 items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
472 },
473 ],
474 )
475 .await
476 .unwrap();
477
478 self.handle_linked_chunk_updates(
480 r1,
481 vec![
482 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
484 Update::PushItems {
486 at: Position::new(CId::new(0), 0),
487 items: vec![make_test_event(r0, "yummy")],
488 },
489 ],
490 )
491 .await
492 .unwrap();
493
494 self.remove_room(r0).await.unwrap();
496
497 let r0_linked_chunk = self.reload_linked_chunk(r0).await.unwrap();
499 assert!(r0_linked_chunk.is_empty());
500
501 let r1_linked_chunk = self.reload_linked_chunk(r1).await.unwrap();
503 assert!(!r1_linked_chunk.is_empty());
504 }
505}
506
507#[allow(unused_macros, unused_extern_crates)]
535#[macro_export]
536macro_rules! event_cache_store_integration_tests {
537 () => {
538 mod event_cache_store_integration_tests {
539 use matrix_sdk_test::async_test;
540 use $crate::event_cache::store::{
541 EventCacheStoreIntegrationTests, IntoEventCacheStore,
542 };
543
544 use super::get_event_cache_store;
545
546 #[async_test]
547 async fn test_media_content() {
548 let event_cache_store =
549 get_event_cache_store().await.unwrap().into_event_cache_store();
550 event_cache_store.test_media_content().await;
551 }
552
553 #[async_test]
554 async fn test_replace_media_key() {
555 let event_cache_store =
556 get_event_cache_store().await.unwrap().into_event_cache_store();
557 event_cache_store.test_replace_media_key().await;
558 }
559
560 #[async_test]
561 async fn test_handle_updates_and_rebuild_linked_chunk() {
562 let event_cache_store =
563 get_event_cache_store().await.unwrap().into_event_cache_store();
564 event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
565 }
566
567 #[async_test]
568 async fn test_rebuild_empty_linked_chunk() {
569 let event_cache_store =
570 get_event_cache_store().await.unwrap().into_event_cache_store();
571 event_cache_store.test_rebuild_empty_linked_chunk().await;
572 }
573
574 #[async_test]
575 async fn test_clear_all_rooms_chunks() {
576 let event_cache_store =
577 get_event_cache_store().await.unwrap().into_event_cache_store();
578 event_cache_store.test_clear_all_rooms_chunks().await;
579 }
580
581 #[async_test]
582 async fn test_remove_room() {
583 let event_cache_store =
584 get_event_cache_store().await.unwrap().into_event_cache_store();
585 event_cache_store.test_remove_room().await;
586 }
587 }
588 };
589}
590
591#[allow(unused_macros)]
594#[macro_export]
595macro_rules! event_cache_store_integration_tests_time {
596 () => {
597 #[cfg(not(target_arch = "wasm32"))]
598 mod event_cache_store_integration_tests_time {
599 use std::time::Duration;
600
601 use matrix_sdk_test::async_test;
602 use $crate::event_cache::store::IntoEventCacheStore;
603
604 use super::get_event_cache_store;
605
606 #[async_test]
607 async fn test_lease_locks() {
608 let store = get_event_cache_store().await.unwrap().into_event_cache_store();
609
610 let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
611 assert!(acquired0);
612
613 let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
615 assert!(acquired2);
616
617 let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
619 assert!(acquired3);
620
621 let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
623 assert!(!acquired4);
624
625 let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
627 assert!(!acquired5);
628
629 tokio::time::sleep(Duration::from_millis(50)).await;
631
632 let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
634 assert!(!acquired55);
635
636 tokio::time::sleep(Duration::from_millis(250)).await;
638
639 let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
641 assert!(acquired6);
642
643 tokio::time::sleep(Duration::from_millis(1)).await;
644
645 let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
647 assert!(acquired7);
648
649 tokio::time::sleep(Duration::from_millis(1)).await;
650
651 let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
653 assert!(acquired8);
654
655 let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
657 assert!(!acquired9);
658
659 let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
661 assert!(acquired10);
662 }
663 }
664 };
665}