matrix_sdk_base/event_cache/store/
integration_tests.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Trait and macro of integration tests for `EventCacheStore` implementations.
16
17use std::sync::Arc;
18
19use assert_matches::assert_matches;
20use matrix_sdk_common::{
21    deserialized_responses::{
22        AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, TimelineEvent, TimelineEventKind,
23        VerificationState,
24    },
25    linked_chunk::{
26        lazy_loader, ChunkContent, ChunkIdentifier as CId, LinkedChunkId, Position, 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,
32    event_id,
33    events::{
34        relation::RelationType,
35        room::{message::RoomMessageEventContentWithoutRelation, MediaSource},
36    },
37    mxc_uri,
38    push::Action,
39    room_id, uint, EventId, RoomId,
40};
41
42use super::{media::IgnoreMediaRetentionPolicy, DynEventCacheStore};
43use crate::{
44    event_cache::{store::DEFAULT_CHUNK_CAPACITY, Gap},
45    media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings},
46};
47
48/// Create a test event with all data filled, for testing that linked chunk
49/// correctly stores event data.
50///
51/// Keep in sync with [`check_test_event`].
52pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent {
53    make_test_event_with_event_id(room_id, content, None)
54}
55
56/// Same as [`make_test_event`], with an extra event id.
57pub fn make_test_event_with_event_id(
58    room_id: &RoomId,
59    content: &str,
60    event_id: Option<&EventId>,
61) -> TimelineEvent {
62    let encryption_info = Arc::new(EncryptionInfo {
63        sender: (*ALICE).into(),
64        sender_device: None,
65        algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
66            curve25519_key: "1337".to_owned(),
67            sender_claimed_keys: Default::default(),
68            session_id: Some("mysessionid9".to_owned()),
69        },
70        verification_state: VerificationState::Verified,
71    });
72
73    let mut builder = EventFactory::new().text_msg(content).room(room_id).sender(*ALICE);
74    if let Some(event_id) = event_id {
75        builder = builder.event_id(event_id);
76    }
77    let event = builder.into_raw_timeline().cast();
78
79    TimelineEvent::from_decrypted(
80        DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info: None },
81        Some(vec![Action::Notify]),
82    )
83}
84
85/// Check that an event created with [`make_test_event`] contains the expected
86/// data.
87///
88/// Keep in sync with [`make_test_event`].
89#[track_caller]
90pub fn check_test_event(event: &TimelineEvent, text: &str) {
91    // Check push actions.
92    let actions = event.push_actions().unwrap();
93    assert_eq!(actions.len(), 1);
94    assert_matches!(&actions[0], Action::Notify);
95
96    // Check content.
97    assert_matches!(&event.kind, TimelineEventKind::Decrypted(d) => {
98        // Check encryption fields.
99        assert_eq!(d.encryption_info.sender, *ALICE);
100        assert_matches!(&d.encryption_info.algorithm_info, AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => {
101            assert_eq!(curve25519_key, "1337");
102        });
103
104        // Check event.
105        let deserialized = d.event.deserialize().unwrap();
106        assert_matches!(deserialized, ruma::events::AnyMessageLikeEvent::RoomMessage(msg) => {
107            assert_eq!(msg.as_original().unwrap().content.body(), text);
108        });
109    });
110}
111
112/// `EventCacheStore` integration tests.
113///
114/// This trait is not meant to be used directly, but will be used with the
115/// `event_cache_store_integration_tests!` macro.
116#[allow(async_fn_in_trait)]
117pub trait EventCacheStoreIntegrationTests {
118    /// Test media content storage.
119    async fn test_media_content(&self);
120
121    /// Test replacing a MXID.
122    async fn test_replace_media_key(&self);
123
124    /// Test handling updates to a linked chunk and reloading these updates from
125    /// the store.
126    async fn test_handle_updates_and_rebuild_linked_chunk(&self);
127
128    /// Test loading a linked chunk incrementally (chunk by chunk) from the
129    /// store.
130    async fn test_linked_chunk_incremental_loading(&self);
131
132    /// Test that rebuilding a linked chunk from an empty store doesn't return
133    /// anything.
134    async fn test_rebuild_empty_linked_chunk(&self);
135
136    /// Test that clear all the rooms' linked chunks works.
137    async fn test_clear_all_linked_chunks(&self);
138
139    /// Test that removing a room from storage empties all associated data.
140    async fn test_remove_room(&self);
141
142    /// Test that filtering duplicated events works as expected.
143    async fn test_filter_duplicated_events(&self);
144
145    /// Test that an event can be found or not.
146    async fn test_find_event(&self);
147
148    /// Test that finding event relations works as expected.
149    async fn test_find_event_relations(&self);
150
151    /// Test that saving an event works as expected.
152    async fn test_save_event(&self);
153}
154
155impl EventCacheStoreIntegrationTests for DynEventCacheStore {
156    async fn test_media_content(&self) {
157        let uri = mxc_uri!("mxc://localhost/media");
158        let request_file = MediaRequestParameters {
159            source: MediaSource::Plain(uri.to_owned()),
160            format: MediaFormat::File,
161        };
162        let request_thumbnail = MediaRequestParameters {
163            source: MediaSource::Plain(uri.to_owned()),
164            format: MediaFormat::Thumbnail(MediaThumbnailSettings::with_method(
165                Method::Crop,
166                uint!(100),
167                uint!(100),
168            )),
169        };
170
171        let other_uri = mxc_uri!("mxc://localhost/media-other");
172        let request_other_file = MediaRequestParameters {
173            source: MediaSource::Plain(other_uri.to_owned()),
174            format: MediaFormat::File,
175        };
176
177        let content: Vec<u8> = "hello".into();
178        let thumbnail_content: Vec<u8> = "world".into();
179        let other_content: Vec<u8> = "foo".into();
180
181        // Media isn't present in the cache.
182        assert!(
183            self.get_media_content(&request_file).await.unwrap().is_none(),
184            "unexpected media found"
185        );
186        assert!(
187            self.get_media_content(&request_thumbnail).await.unwrap().is_none(),
188            "media not found"
189        );
190
191        // Let's add the media.
192        self.add_media_content(&request_file, content.clone(), IgnoreMediaRetentionPolicy::No)
193            .await
194            .expect("adding media failed");
195
196        // Media is present in the cache.
197        assert_eq!(
198            self.get_media_content(&request_file).await.unwrap().as_ref(),
199            Some(&content),
200            "media not found though added"
201        );
202        assert_eq!(
203            self.get_media_content_for_uri(uri).await.unwrap().as_ref(),
204            Some(&content),
205            "media not found by URI though added"
206        );
207
208        // Let's remove the media.
209        self.remove_media_content(&request_file).await.expect("removing media failed");
210
211        // Media isn't present in the cache.
212        assert!(
213            self.get_media_content(&request_file).await.unwrap().is_none(),
214            "media still there after removing"
215        );
216        assert!(
217            self.get_media_content_for_uri(uri).await.unwrap().is_none(),
218            "media still found by URI after removing"
219        );
220
221        // Let's add the media again.
222        self.add_media_content(&request_file, content.clone(), IgnoreMediaRetentionPolicy::No)
223            .await
224            .expect("adding media again failed");
225
226        assert_eq!(
227            self.get_media_content(&request_file).await.unwrap().as_ref(),
228            Some(&content),
229            "media not found after adding again"
230        );
231
232        // Let's add the thumbnail media.
233        self.add_media_content(
234            &request_thumbnail,
235            thumbnail_content.clone(),
236            IgnoreMediaRetentionPolicy::No,
237        )
238        .await
239        .expect("adding thumbnail failed");
240
241        // Media's thumbnail is present.
242        assert_eq!(
243            self.get_media_content(&request_thumbnail).await.unwrap().as_ref(),
244            Some(&thumbnail_content),
245            "thumbnail not found"
246        );
247
248        // We get a file with the URI, we don't know which one.
249        assert!(
250            self.get_media_content_for_uri(uri).await.unwrap().is_some(),
251            "media not found by URI though two where added"
252        );
253
254        // Let's add another media with a different URI.
255        self.add_media_content(
256            &request_other_file,
257            other_content.clone(),
258            IgnoreMediaRetentionPolicy::No,
259        )
260        .await
261        .expect("adding other media failed");
262
263        // Other file is present.
264        assert_eq!(
265            self.get_media_content(&request_other_file).await.unwrap().as_ref(),
266            Some(&other_content),
267            "other file not found"
268        );
269        assert_eq!(
270            self.get_media_content_for_uri(other_uri).await.unwrap().as_ref(),
271            Some(&other_content),
272            "other file not found by URI"
273        );
274
275        // Let's remove media based on URI.
276        self.remove_media_content_for_uri(uri).await.expect("removing all media for uri failed");
277
278        assert!(
279            self.get_media_content(&request_file).await.unwrap().is_none(),
280            "media wasn't removed"
281        );
282        assert!(
283            self.get_media_content(&request_thumbnail).await.unwrap().is_none(),
284            "thumbnail wasn't removed"
285        );
286        assert!(
287            self.get_media_content(&request_other_file).await.unwrap().is_some(),
288            "other media was removed"
289        );
290        assert!(
291            self.get_media_content_for_uri(uri).await.unwrap().is_none(),
292            "media found by URI wasn't removed"
293        );
294        assert!(
295            self.get_media_content_for_uri(other_uri).await.unwrap().is_some(),
296            "other media found by URI was removed"
297        );
298    }
299
300    async fn test_replace_media_key(&self) {
301        let uri = mxc_uri!("mxc://sendqueue.local/tr4n-s4ct-10n1-d");
302        let req = MediaRequestParameters {
303            source: MediaSource::Plain(uri.to_owned()),
304            format: MediaFormat::File,
305        };
306
307        let content = "hello".as_bytes().to_owned();
308
309        // Media isn't present in the cache.
310        assert!(self.get_media_content(&req).await.unwrap().is_none(), "unexpected media found");
311
312        // Add the media.
313        self.add_media_content(&req, content.clone(), IgnoreMediaRetentionPolicy::No)
314            .await
315            .expect("adding media failed");
316
317        // Sanity-check: media is found after adding it.
318        assert_eq!(self.get_media_content(&req).await.unwrap().unwrap(), b"hello");
319
320        // Replacing a media request works.
321        let new_uri = mxc_uri!("mxc://matrix.org/tr4n-s4ct-10n1-d");
322        let new_req = MediaRequestParameters {
323            source: MediaSource::Plain(new_uri.to_owned()),
324            format: MediaFormat::File,
325        };
326        self.replace_media_key(&req, &new_req)
327            .await
328            .expect("replacing the media request key failed");
329
330        // Finding with the previous request doesn't work anymore.
331        assert!(
332            self.get_media_content(&req).await.unwrap().is_none(),
333            "unexpected media found with the old key"
334        );
335
336        // Finding with the new request does work.
337        assert_eq!(self.get_media_content(&new_req).await.unwrap().unwrap(), b"hello");
338    }
339
340    async fn test_handle_updates_and_rebuild_linked_chunk(&self) {
341        let room_id = room_id!("!r0:matrix.org");
342        let linked_chunk_id = LinkedChunkId::Room(room_id);
343
344        self.handle_linked_chunk_updates(
345            linked_chunk_id,
346            vec![
347                // new chunk
348                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
349                // new items on 0
350                Update::PushItems {
351                    at: Position::new(CId::new(0), 0),
352                    items: vec![
353                        make_test_event(room_id, "hello"),
354                        make_test_event(room_id, "world"),
355                    ],
356                },
357                // a gap chunk
358                Update::NewGapChunk {
359                    previous: Some(CId::new(0)),
360                    new: CId::new(1),
361                    next: None,
362                    gap: Gap { prev_token: "parmesan".to_owned() },
363                },
364                // another items chunk
365                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
366                // new items on 2
367                Update::PushItems {
368                    at: Position::new(CId::new(2), 0),
369                    items: vec![make_test_event(room_id, "sup")],
370                },
371            ],
372        )
373        .await
374        .unwrap();
375
376        // The linked chunk is correctly reloaded.
377        let lc = lazy_loader::from_all_chunks::<3, _, _>(
378            self.load_all_chunks(linked_chunk_id).await.unwrap(),
379        )
380        .unwrap()
381        .unwrap();
382
383        let mut chunks = lc.chunks();
384
385        {
386            let first = chunks.next().unwrap();
387            // Note: we can't assert the previous/next chunks, as these fields and their
388            // getters are private.
389            assert_eq!(first.identifier(), CId::new(0));
390
391            assert_matches!(first.content(), ChunkContent::Items(events) => {
392                assert_eq!(events.len(), 2);
393                check_test_event(&events[0], "hello");
394                check_test_event(&events[1], "world");
395            });
396        }
397
398        {
399            let second = chunks.next().unwrap();
400            assert_eq!(second.identifier(), CId::new(1));
401
402            assert_matches!(second.content(), ChunkContent::Gap(gap) => {
403                assert_eq!(gap.prev_token, "parmesan");
404            });
405        }
406
407        {
408            let third = chunks.next().unwrap();
409            assert_eq!(third.identifier(), CId::new(2));
410
411            assert_matches!(third.content(), ChunkContent::Items(events) => {
412                assert_eq!(events.len(), 1);
413                check_test_event(&events[0], "sup");
414            });
415        }
416
417        assert!(chunks.next().is_none());
418    }
419
420    async fn test_linked_chunk_incremental_loading(&self) {
421        let room_id = room_id!("!r0:matrix.org");
422        let linked_chunk_id = LinkedChunkId::Room(room_id);
423        let event = |msg: &str| make_test_event(room_id, msg);
424
425        // Load the last chunk, but none exists yet.
426        {
427            let (last_chunk, chunk_identifier_generator) =
428                self.load_last_chunk(linked_chunk_id).await.unwrap();
429
430            assert!(last_chunk.is_none());
431            assert_eq!(chunk_identifier_generator.current(), 0);
432        }
433
434        self.handle_linked_chunk_updates(
435            linked_chunk_id,
436            vec![
437                // new chunk for items
438                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
439                // new items on 0
440                Update::PushItems {
441                    at: Position::new(CId::new(0), 0),
442                    items: vec![event("a"), event("b")],
443                },
444                // new chunk for a gap
445                Update::NewGapChunk {
446                    previous: Some(CId::new(0)),
447                    new: CId::new(1),
448                    next: None,
449                    gap: Gap { prev_token: "morbier".to_owned() },
450                },
451                // new chunk for items
452                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
453                // new items on 2
454                Update::PushItems {
455                    at: Position::new(CId::new(2), 0),
456                    items: vec![event("c"), event("d"), event("e")],
457                },
458            ],
459        )
460        .await
461        .unwrap();
462
463        // Load the last chunk.
464        let mut linked_chunk = {
465            let (last_chunk, chunk_identifier_generator) =
466                self.load_last_chunk(linked_chunk_id).await.unwrap();
467
468            assert_eq!(chunk_identifier_generator.current(), 2);
469
470            let linked_chunk = lazy_loader::from_last_chunk::<DEFAULT_CHUNK_CAPACITY, _, _>(
471                last_chunk,
472                chunk_identifier_generator,
473            )
474            .unwrap() // unwrap the `Result`
475            .unwrap(); // unwrap the `Option`
476
477            let mut rchunks = linked_chunk.rchunks();
478
479            // A unique chunk.
480            assert_matches!(rchunks.next(), Some(chunk) => {
481                assert_eq!(chunk.identifier(), 2);
482                assert_eq!(chunk.lazy_previous(), Some(CId::new(1)));
483
484                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
485                    assert_eq!(events.len(), 3);
486                    check_test_event(&events[0], "c");
487                    check_test_event(&events[1], "d");
488                    check_test_event(&events[2], "e");
489                });
490            });
491
492            assert!(rchunks.next().is_none());
493
494            linked_chunk
495        };
496
497        // Load the previous chunk: this is a gap.
498        {
499            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
500            let previous_chunk =
501                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap();
502
503            lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
504
505            let mut rchunks = linked_chunk.rchunks();
506
507            // The last chunk.
508            assert_matches!(rchunks.next(), Some(chunk) => {
509                assert_eq!(chunk.identifier(), 2);
510                assert!(chunk.lazy_previous().is_none());
511
512                // Already asserted, but let's be sure nothing breaks.
513                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
514                    assert_eq!(events.len(), 3);
515                    check_test_event(&events[0], "c");
516                    check_test_event(&events[1], "d");
517                    check_test_event(&events[2], "e");
518                });
519            });
520
521            // The new chunk.
522            assert_matches!(rchunks.next(), Some(chunk) => {
523                assert_eq!(chunk.identifier(), 1);
524                assert_eq!(chunk.lazy_previous(), Some(CId::new(0)));
525
526                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
527                    assert_eq!(gap.prev_token, "morbier");
528                });
529            });
530
531            assert!(rchunks.next().is_none());
532        }
533
534        // Load the previous chunk: these are items.
535        {
536            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
537            let previous_chunk =
538                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap();
539
540            lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
541
542            let mut rchunks = linked_chunk.rchunks();
543
544            // The last chunk.
545            assert_matches!(rchunks.next(), Some(chunk) => {
546                assert_eq!(chunk.identifier(), 2);
547                assert!(chunk.lazy_previous().is_none());
548
549                // Already asserted, but let's be sure nothing breaks.
550                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
551                    assert_eq!(events.len(), 3);
552                    check_test_event(&events[0], "c");
553                    check_test_event(&events[1], "d");
554                    check_test_event(&events[2], "e");
555                });
556            });
557
558            // Its previous chunk.
559            assert_matches!(rchunks.next(), Some(chunk) => {
560                assert_eq!(chunk.identifier(), 1);
561                assert!(chunk.lazy_previous().is_none());
562
563                // Already asserted, but let's be sure nothing breaks.
564                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
565                    assert_eq!(gap.prev_token, "morbier");
566                });
567            });
568
569            // The new chunk.
570            assert_matches!(rchunks.next(), Some(chunk) => {
571                assert_eq!(chunk.identifier(), 0);
572                assert!(chunk.lazy_previous().is_none());
573
574                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
575                    assert_eq!(events.len(), 2);
576                    check_test_event(&events[0], "a");
577                    check_test_event(&events[1], "b");
578                });
579            });
580
581            assert!(rchunks.next().is_none());
582        }
583
584        // Load the previous chunk: there is none.
585        {
586            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
587            let previous_chunk =
588                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap();
589
590            assert!(previous_chunk.is_none());
591        }
592
593        // One last check: a round of assert by using the forwards chunk iterator
594        // instead of the backwards chunk iterator.
595        {
596            let mut chunks = linked_chunk.chunks();
597
598            // The first chunk.
599            assert_matches!(chunks.next(), Some(chunk) => {
600                assert_eq!(chunk.identifier(), 0);
601                assert!(chunk.lazy_previous().is_none());
602
603                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
604                    assert_eq!(events.len(), 2);
605                    check_test_event(&events[0], "a");
606                    check_test_event(&events[1], "b");
607                });
608            });
609
610            // The second chunk.
611            assert_matches!(chunks.next(), Some(chunk) => {
612                assert_eq!(chunk.identifier(), 1);
613                assert!(chunk.lazy_previous().is_none());
614
615                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
616                    assert_eq!(gap.prev_token, "morbier");
617                });
618            });
619
620            // The third and last chunk.
621            assert_matches!(chunks.next(), Some(chunk) => {
622                assert_eq!(chunk.identifier(), 2);
623                assert!(chunk.lazy_previous().is_none());
624
625                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
626                    assert_eq!(events.len(), 3);
627                    check_test_event(&events[0], "c");
628                    check_test_event(&events[1], "d");
629                    check_test_event(&events[2], "e");
630                });
631            });
632
633            assert!(chunks.next().is_none());
634        }
635    }
636
637    async fn test_rebuild_empty_linked_chunk(&self) {
638        // When I rebuild a linked chunk from an empty store, it's empty.
639        let linked_chunk = lazy_loader::from_all_chunks::<3, _, _>(
640            self.load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)).await.unwrap(),
641        )
642        .unwrap();
643        assert!(linked_chunk.is_none());
644    }
645
646    async fn test_clear_all_linked_chunks(&self) {
647        let r0 = room_id!("!r0:matrix.org");
648        let linked_chunk_id0 = LinkedChunkId::Room(r0);
649        let r1 = room_id!("!r1:matrix.org");
650        let linked_chunk_id1 = LinkedChunkId::Room(r1);
651
652        // Add updates for the first room.
653        self.handle_linked_chunk_updates(
654            linked_chunk_id0,
655            vec![
656                // new chunk
657                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
658                // new items on 0
659                Update::PushItems {
660                    at: Position::new(CId::new(0), 0),
661                    items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
662                },
663            ],
664        )
665        .await
666        .unwrap();
667
668        // Add updates for the second room.
669        self.handle_linked_chunk_updates(
670            linked_chunk_id1,
671            vec![
672                // Empty items chunk.
673                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
674                // a gap chunk
675                Update::NewGapChunk {
676                    previous: Some(CId::new(0)),
677                    new: CId::new(1),
678                    next: None,
679                    gap: Gap { prev_token: "bleu d'auvergne".to_owned() },
680                },
681                // another items chunk
682                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
683                // new items on 0
684                Update::PushItems {
685                    at: Position::new(CId::new(2), 0),
686                    items: vec![make_test_event(r0, "yummy")],
687                },
688            ],
689        )
690        .await
691        .unwrap();
692
693        // Sanity check: both linked chunks can be reloaded.
694        assert!(lazy_loader::from_all_chunks::<3, _, _>(
695            self.load_all_chunks(linked_chunk_id0).await.unwrap()
696        )
697        .unwrap()
698        .is_some());
699        assert!(lazy_loader::from_all_chunks::<3, _, _>(
700            self.load_all_chunks(linked_chunk_id1).await.unwrap()
701        )
702        .unwrap()
703        .is_some());
704
705        // Clear the chunks.
706        self.clear_all_linked_chunks().await.unwrap();
707
708        // Both rooms now have no linked chunk.
709        assert!(lazy_loader::from_all_chunks::<3, _, _>(
710            self.load_all_chunks(linked_chunk_id0).await.unwrap()
711        )
712        .unwrap()
713        .is_none());
714        assert!(lazy_loader::from_all_chunks::<3, _, _>(
715            self.load_all_chunks(linked_chunk_id1).await.unwrap()
716        )
717        .unwrap()
718        .is_none());
719    }
720
721    async fn test_remove_room(&self) {
722        let r0 = room_id!("!r0:matrix.org");
723        let linked_chunk_id0 = LinkedChunkId::Room(r0);
724        let r1 = room_id!("!r1:matrix.org");
725        let linked_chunk_id1 = LinkedChunkId::Room(r1);
726
727        // Add updates to the first room.
728        self.handle_linked_chunk_updates(
729            linked_chunk_id0,
730            vec![
731                // new chunk
732                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
733                // new items on 0
734                Update::PushItems {
735                    at: Position::new(CId::new(0), 0),
736                    items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
737                },
738            ],
739        )
740        .await
741        .unwrap();
742
743        // Add updates to the second room.
744        self.handle_linked_chunk_updates(
745            linked_chunk_id1,
746            vec![
747                // new chunk
748                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
749                // new items on 0
750                Update::PushItems {
751                    at: Position::new(CId::new(0), 0),
752                    items: vec![make_test_event(r0, "yummy")],
753                },
754            ],
755        )
756        .await
757        .unwrap();
758
759        // Try to remove content from r0.
760        self.remove_room(r0).await.unwrap();
761
762        // Check that r0 doesn't have a linked chunk anymore.
763        let r0_linked_chunk = self.load_all_chunks(linked_chunk_id0).await.unwrap();
764        assert!(r0_linked_chunk.is_empty());
765
766        // Check that r1 is unaffected.
767        let r1_linked_chunk = self.load_all_chunks(linked_chunk_id1).await.unwrap();
768        assert!(!r1_linked_chunk.is_empty());
769    }
770
771    async fn test_filter_duplicated_events(&self) {
772        let room_id = room_id!("!r0:matrix.org");
773        let linked_chunk_id = LinkedChunkId::Room(room_id);
774        let another_room_id = room_id!("!r1:matrix.org");
775        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
776        let event = |msg: &str| make_test_event(room_id, msg);
777
778        let event_comte = event("comté");
779        let event_brigand = event("brigand du jorat");
780        let event_raclette = event("raclette");
781        let event_morbier = event("morbier");
782        let event_gruyere = event("gruyère");
783        let event_tome = event("tome");
784        let event_mont_dor = event("mont d'or");
785
786        self.handle_linked_chunk_updates(
787            linked_chunk_id,
788            vec![
789                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
790                Update::PushItems {
791                    at: Position::new(CId::new(0), 0),
792                    items: vec![event_comte.clone(), event_brigand.clone()],
793                },
794                Update::NewGapChunk {
795                    previous: Some(CId::new(0)),
796                    new: CId::new(1),
797                    next: None,
798                    gap: Gap { prev_token: "brillat-savarin".to_owned() },
799                },
800                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
801                Update::PushItems {
802                    at: Position::new(CId::new(2), 0),
803                    items: vec![event_morbier.clone(), event_mont_dor.clone()],
804                },
805            ],
806        )
807        .await
808        .unwrap();
809
810        // Add other events in another room, to ensure filtering take the `room_id` into
811        // account.
812        self.handle_linked_chunk_updates(
813            another_linked_chunk_id,
814            vec![
815                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
816                Update::PushItems {
817                    at: Position::new(CId::new(0), 0),
818                    items: vec![event_tome.clone()],
819                },
820            ],
821        )
822        .await
823        .unwrap();
824
825        let duplicated_events = self
826            .filter_duplicated_events(
827                linked_chunk_id,
828                vec![
829                    event_comte.event_id().unwrap().to_owned(),
830                    event_raclette.event_id().unwrap().to_owned(),
831                    event_morbier.event_id().unwrap().to_owned(),
832                    event_gruyere.event_id().unwrap().to_owned(),
833                    event_tome.event_id().unwrap().to_owned(),
834                    event_mont_dor.event_id().unwrap().to_owned(),
835                ],
836            )
837            .await
838            .unwrap();
839
840        assert_eq!(duplicated_events.len(), 3);
841        assert_eq!(
842            duplicated_events[0],
843            (event_comte.event_id().unwrap(), Position::new(CId::new(0), 0))
844        );
845        assert_eq!(
846            duplicated_events[1],
847            (event_morbier.event_id().unwrap(), Position::new(CId::new(2), 0))
848        );
849        assert_eq!(
850            duplicated_events[2],
851            (event_mont_dor.event_id().unwrap(), Position::new(CId::new(2), 1))
852        );
853    }
854
855    async fn test_find_event(&self) {
856        let room_id = room_id!("!r0:matrix.org");
857        let another_room_id = room_id!("!r1:matrix.org");
858        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
859        let event = |msg: &str| make_test_event(room_id, msg);
860
861        let event_comte = event("comté");
862        let event_gruyere = event("gruyère");
863
864        // Add one event in one room.
865        self.handle_linked_chunk_updates(
866            LinkedChunkId::Room(room_id),
867            vec![
868                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
869                Update::PushItems {
870                    at: Position::new(CId::new(0), 0),
871                    items: vec![event_comte.clone()],
872                },
873            ],
874        )
875        .await
876        .unwrap();
877
878        // Add another event in another room.
879        self.handle_linked_chunk_updates(
880            another_linked_chunk_id,
881            vec![
882                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
883                Update::PushItems {
884                    at: Position::new(CId::new(0), 0),
885                    items: vec![event_gruyere.clone()],
886                },
887            ],
888        )
889        .await
890        .unwrap();
891
892        // Now let's find the event.
893        let event = self
894            .find_event(room_id, event_comte.event_id().unwrap().as_ref())
895            .await
896            .expect("failed to query for finding an event")
897            .expect("failed to find an event");
898
899        assert_eq!(event.event_id(), event_comte.event_id());
900
901        // Now let's try to find an event that exists, but not in the expected room.
902        assert!(self
903            .find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
904            .await
905            .expect("failed to query for finding an event")
906            .is_none());
907
908        // Clearing the rooms also clears the event's storage.
909        self.clear_all_linked_chunks().await.expect("failed to clear all rooms chunks");
910        assert!(self
911            .find_event(room_id, event_comte.event_id().unwrap().as_ref())
912            .await
913            .expect("failed to query for finding an event")
914            .is_none());
915    }
916
917    async fn test_find_event_relations(&self) {
918        let room_id = room_id!("!r0:matrix.org");
919        let another_room_id = room_id!("!r1:matrix.org");
920
921        let f = EventFactory::new().room(room_id).sender(*ALICE);
922
923        // Create event and related events for the first room.
924        let eid1 = event_id!("$event1:matrix.org");
925        let e1 = f.text_msg("comter").event_id(eid1).into_event();
926
927        let edit_eid1 = event_id!("$edit_event1:matrix.org");
928        let edit_e1 = f
929            .text_msg("* comté")
930            .event_id(edit_eid1)
931            .edit(eid1, RoomMessageEventContentWithoutRelation::text_plain("comté"))
932            .into_event();
933
934        let reaction_eid1 = event_id!("$reaction_event1:matrix.org");
935        let reaction_e1 = f.reaction(eid1, "👍").event_id(reaction_eid1).into_event();
936
937        let eid2 = event_id!("$event2:matrix.org");
938        let e2 = f.text_msg("galette saucisse").event_id(eid2).into_event();
939
940        // Create events for the second room.
941        let f = f.room(another_room_id);
942
943        let eid3 = event_id!("$event3:matrix.org");
944        let e3 = f.text_msg("gruyère").event_id(eid3).into_event();
945
946        let reaction_eid3 = event_id!("$reaction_event3:matrix.org");
947        let reaction_e3 = f.reaction(eid3, "👍").event_id(reaction_eid3).into_event();
948
949        // Save All The Things!
950        self.save_event(room_id, e1).await.unwrap();
951        self.save_event(room_id, edit_e1).await.unwrap();
952        self.save_event(room_id, reaction_e1).await.unwrap();
953        self.save_event(room_id, e2).await.unwrap();
954        self.save_event(another_room_id, e3).await.unwrap();
955        self.save_event(another_room_id, reaction_e3).await.unwrap();
956
957        // Finding relations without a filter returns all of them.
958        let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
959        assert_eq!(relations.len(), 2);
960        assert!(relations.iter().any(|r| r.event_id().as_deref() == Some(edit_eid1)));
961        assert!(relations.iter().any(|r| r.event_id().as_deref() == Some(reaction_eid1)));
962
963        // Finding relations with a filter only returns a subset.
964        let relations = self
965            .find_event_relations(room_id, eid1, Some(&[RelationType::Replacement]))
966            .await
967            .unwrap();
968        assert_eq!(relations.len(), 1);
969        assert_eq!(relations[0].event_id().as_deref(), Some(edit_eid1));
970
971        let relations = self
972            .find_event_relations(
973                room_id,
974                eid1,
975                Some(&[RelationType::Replacement, RelationType::Annotation]),
976            )
977            .await
978            .unwrap();
979        assert_eq!(relations.len(), 2);
980        assert!(relations.iter().any(|r| r.event_id().as_deref() == Some(edit_eid1)));
981        assert!(relations.iter().any(|r| r.event_id().as_deref() == Some(reaction_eid1)));
982
983        // We can't find relations using the wrong room.
984        let relations = self
985            .find_event_relations(another_room_id, eid1, Some(&[RelationType::Replacement]))
986            .await
987            .unwrap();
988        assert!(relations.is_empty());
989    }
990
991    async fn test_save_event(&self) {
992        let room_id = room_id!("!r0:matrix.org");
993        let another_room_id = room_id!("!r1:matrix.org");
994
995        let event = |msg: &str| make_test_event(room_id, msg);
996        let event_comte = event("comté");
997        let event_gruyere = event("gruyère");
998
999        // Add one event in one room.
1000        self.save_event(room_id, event_comte.clone()).await.unwrap();
1001
1002        // Add another event in another room.
1003        self.save_event(another_room_id, event_gruyere.clone()).await.unwrap();
1004
1005        // Events can be found, when searched in their own rooms.
1006        let event = self
1007            .find_event(room_id, event_comte.event_id().unwrap().as_ref())
1008            .await
1009            .expect("failed to query for finding an event")
1010            .expect("failed to find an event");
1011        assert_eq!(event.event_id(), event_comte.event_id());
1012
1013        let event = self
1014            .find_event(another_room_id, event_gruyere.event_id().unwrap().as_ref())
1015            .await
1016            .expect("failed to query for finding an event")
1017            .expect("failed to find an event");
1018        assert_eq!(event.event_id(), event_gruyere.event_id());
1019
1020        // But they won't be returned when searching in the wrong room.
1021        assert!(self
1022            .find_event(another_room_id, event_comte.event_id().unwrap().as_ref())
1023            .await
1024            .expect("failed to query for finding an event")
1025            .is_none());
1026        assert!(self
1027            .find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
1028            .await
1029            .expect("failed to query for finding an event")
1030            .is_none());
1031    }
1032}
1033
1034/// Macro building to allow your `EventCacheStore` implementation to run the
1035/// entire tests suite locally.
1036///
1037/// You need to provide a `async fn get_event_cache_store() ->
1038/// EventCacheStoreResult<impl EventCacheStore>` providing a fresh event cache
1039/// store on the same level you invoke the macro.
1040///
1041/// ## Usage Example:
1042/// ```no_run
1043/// # use matrix_sdk_base::event_cache::store::{
1044/// #    EventCacheStore,
1045/// #    MemoryStore as MyStore,
1046/// #    Result as EventCacheStoreResult,
1047/// # };
1048///
1049/// #[cfg(test)]
1050/// mod tests {
1051///     use super::{EventCacheStore, EventCacheStoreResult, MyStore};
1052///
1053///     async fn get_event_cache_store(
1054///     ) -> EventCacheStoreResult<impl EventCacheStore> {
1055///         Ok(MyStore::new())
1056///     }
1057///
1058///     event_cache_store_integration_tests!();
1059/// }
1060/// ```
1061#[allow(unused_macros, unused_extern_crates)]
1062#[macro_export]
1063macro_rules! event_cache_store_integration_tests {
1064    () => {
1065        mod event_cache_store_integration_tests {
1066            use matrix_sdk_test::async_test;
1067            use $crate::event_cache::store::{
1068                EventCacheStoreIntegrationTests, IntoEventCacheStore,
1069            };
1070
1071            use super::get_event_cache_store;
1072
1073            #[async_test]
1074            async fn test_media_content() {
1075                let event_cache_store =
1076                    get_event_cache_store().await.unwrap().into_event_cache_store();
1077                event_cache_store.test_media_content().await;
1078            }
1079
1080            #[async_test]
1081            async fn test_replace_media_key() {
1082                let event_cache_store =
1083                    get_event_cache_store().await.unwrap().into_event_cache_store();
1084                event_cache_store.test_replace_media_key().await;
1085            }
1086
1087            #[async_test]
1088            async fn test_handle_updates_and_rebuild_linked_chunk() {
1089                let event_cache_store =
1090                    get_event_cache_store().await.unwrap().into_event_cache_store();
1091                event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
1092            }
1093
1094            #[async_test]
1095            async fn test_linked_chunk_incremental_loading() {
1096                let event_cache_store =
1097                    get_event_cache_store().await.unwrap().into_event_cache_store();
1098                event_cache_store.test_linked_chunk_incremental_loading().await;
1099            }
1100
1101            #[async_test]
1102            async fn test_rebuild_empty_linked_chunk() {
1103                let event_cache_store =
1104                    get_event_cache_store().await.unwrap().into_event_cache_store();
1105                event_cache_store.test_rebuild_empty_linked_chunk().await;
1106            }
1107
1108            #[async_test]
1109            async fn test_clear_all_linked_chunks() {
1110                let event_cache_store =
1111                    get_event_cache_store().await.unwrap().into_event_cache_store();
1112                event_cache_store.test_clear_all_linked_chunks().await;
1113            }
1114
1115            #[async_test]
1116            async fn test_remove_room() {
1117                let event_cache_store =
1118                    get_event_cache_store().await.unwrap().into_event_cache_store();
1119                event_cache_store.test_remove_room().await;
1120            }
1121
1122            #[async_test]
1123            async fn test_filter_duplicated_events() {
1124                let event_cache_store =
1125                    get_event_cache_store().await.unwrap().into_event_cache_store();
1126                event_cache_store.test_filter_duplicated_events().await;
1127            }
1128
1129            #[async_test]
1130            async fn test_find_event() {
1131                let event_cache_store =
1132                    get_event_cache_store().await.unwrap().into_event_cache_store();
1133                event_cache_store.test_find_event().await;
1134            }
1135
1136            #[async_test]
1137            async fn test_find_event_relations() {
1138                let event_cache_store =
1139                    get_event_cache_store().await.unwrap().into_event_cache_store();
1140                event_cache_store.test_find_event_relations().await;
1141            }
1142
1143            #[async_test]
1144            async fn test_save_event() {
1145                let event_cache_store =
1146                    get_event_cache_store().await.unwrap().into_event_cache_store();
1147                event_cache_store.test_save_event().await;
1148            }
1149        }
1150    };
1151}
1152
1153/// Macro generating tests for the event cache store, related to time (mostly
1154/// for the cross-process lock).
1155#[allow(unused_macros)]
1156#[macro_export]
1157macro_rules! event_cache_store_integration_tests_time {
1158    () => {
1159        #[cfg(not(target_family = "wasm"))]
1160        mod event_cache_store_integration_tests_time {
1161            use std::time::Duration;
1162
1163            use matrix_sdk_test::async_test;
1164            use $crate::event_cache::store::IntoEventCacheStore;
1165
1166            use super::get_event_cache_store;
1167
1168            #[async_test]
1169            async fn test_lease_locks() {
1170                let store = get_event_cache_store().await.unwrap().into_event_cache_store();
1171
1172                let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
1173                assert!(acquired0);
1174
1175                // Should extend the lease automatically (same holder).
1176                let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1177                assert!(acquired2);
1178
1179                // Should extend the lease automatically (same holder + time is ok).
1180                let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1181                assert!(acquired3);
1182
1183                // Another attempt at taking the lock should fail, because it's taken.
1184                let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1185                assert!(!acquired4);
1186
1187                // Even if we insist.
1188                let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1189                assert!(!acquired5);
1190
1191                // That's a nice test we got here, go take a little nap.
1192                tokio::time::sleep(Duration::from_millis(50)).await;
1193
1194                // Still too early.
1195                let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1196                assert!(!acquired55);
1197
1198                // Ok you can take another nap then.
1199                tokio::time::sleep(Duration::from_millis(250)).await;
1200
1201                // At some point, we do get the lock.
1202                let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
1203                assert!(acquired6);
1204
1205                tokio::time::sleep(Duration::from_millis(1)).await;
1206
1207                // The other gets it almost immediately too.
1208                let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
1209                assert!(acquired7);
1210
1211                tokio::time::sleep(Duration::from_millis(1)).await;
1212
1213                // But when we take a longer lease...
1214                let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1215                assert!(acquired8);
1216
1217                // It blocks the other user.
1218                let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1219                assert!(!acquired9);
1220
1221                // We can hold onto our lease.
1222                let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1223                assert!(acquired10);
1224            }
1225        }
1226    };
1227}