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