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, events::room::MediaSource, mxc_uri,
29    push::Action, room_id, uint, RoomId,
30};
31
32use super::{media::IgnoreMediaRetentionPolicy, DynEventCacheStore};
33use crate::{
34    event_cache::{store::DEFAULT_CHUNK_CAPACITY, Gap},
35    media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings},
36};
37
38/// Create a test event with all data filled, for testing that linked chunk
39/// correctly stores event data.
40///
41/// Keep in sync with [`check_test_event`].
42pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent {
43    let encryption_info = EncryptionInfo {
44        sender: (*ALICE).into(),
45        sender_device: None,
46        algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
47            curve25519_key: "1337".to_owned(),
48            sender_claimed_keys: Default::default(),
49        },
50        verification_state: VerificationState::Verified,
51    };
52
53    let event = EventFactory::new()
54        .text_msg(content)
55        .room(room_id)
56        .sender(*ALICE)
57        .into_raw_timeline()
58        .cast();
59
60    TimelineEvent {
61        kind: TimelineEventKind::Decrypted(DecryptedRoomEvent {
62            event,
63            encryption_info,
64            unsigned_encryption_info: None,
65        }),
66        push_actions: Some(vec![Action::Notify]),
67    }
68}
69
70/// Check that an event created with [`make_test_event`] contains the expected
71/// data.
72///
73/// Keep in sync with [`make_test_event`].
74#[track_caller]
75pub fn check_test_event(event: &TimelineEvent, text: &str) {
76    // Check push actions.
77    let actions = event.push_actions.as_ref().unwrap();
78    assert_eq!(actions.len(), 1);
79    assert_matches!(&actions[0], Action::Notify);
80
81    // Check content.
82    assert_matches!(&event.kind, TimelineEventKind::Decrypted(d) => {
83        // Check encryption fields.
84        assert_eq!(d.encryption_info.sender, *ALICE);
85        assert_matches!(&d.encryption_info.algorithm_info, AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => {
86            assert_eq!(curve25519_key, "1337");
87        });
88
89        // Check event.
90        let deserialized = d.event.deserialize().unwrap();
91        assert_matches!(deserialized, ruma::events::AnyMessageLikeEvent::RoomMessage(msg) => {
92            assert_eq!(msg.as_original().unwrap().content.body(), text);
93        });
94    });
95}
96
97/// `EventCacheStore` integration tests.
98///
99/// This trait is not meant to be used directly, but will be used with the
100/// `event_cache_store_integration_tests!` macro.
101#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
102#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
103pub trait EventCacheStoreIntegrationTests {
104    /// Test media content storage.
105    async fn test_media_content(&self);
106
107    /// Test replacing a MXID.
108    async fn test_replace_media_key(&self);
109
110    /// Test handling updates to a linked chunk and reloading these updates from
111    /// the store.
112    async fn test_handle_updates_and_rebuild_linked_chunk(&self);
113
114    /// Test loading a linked chunk incrementally (chunk by chunk) from the
115    /// store.
116    async fn test_linked_chunk_incremental_loading(&self);
117
118    /// Test that rebuilding a linked chunk from an empty store doesn't return
119    /// anything.
120    async fn test_rebuild_empty_linked_chunk(&self);
121
122    /// Test that clear all the rooms' linked chunks works.
123    async fn test_clear_all_rooms_chunks(&self);
124
125    /// Test that removing a room from storage empties all associated data.
126    async fn test_remove_room(&self);
127
128    /// Test that filtering duplicated events works as expected.
129    async fn test_filter_duplicated_events(&self);
130}
131
132#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
133#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
134impl EventCacheStoreIntegrationTests for DynEventCacheStore {
135    async fn test_media_content(&self) {
136        let uri = mxc_uri!("mxc://localhost/media");
137        let request_file = MediaRequestParameters {
138            source: MediaSource::Plain(uri.to_owned()),
139            format: MediaFormat::File,
140        };
141        let request_thumbnail = MediaRequestParameters {
142            source: MediaSource::Plain(uri.to_owned()),
143            format: MediaFormat::Thumbnail(MediaThumbnailSettings::with_method(
144                Method::Crop,
145                uint!(100),
146                uint!(100),
147            )),
148        };
149
150        let other_uri = mxc_uri!("mxc://localhost/media-other");
151        let request_other_file = MediaRequestParameters {
152            source: MediaSource::Plain(other_uri.to_owned()),
153            format: MediaFormat::File,
154        };
155
156        let content: Vec<u8> = "hello".into();
157        let thumbnail_content: Vec<u8> = "world".into();
158        let other_content: Vec<u8> = "foo".into();
159
160        // Media isn't present in the cache.
161        assert!(
162            self.get_media_content(&request_file).await.unwrap().is_none(),
163            "unexpected media found"
164        );
165        assert!(
166            self.get_media_content(&request_thumbnail).await.unwrap().is_none(),
167            "media not found"
168        );
169
170        // Let's add the media.
171        self.add_media_content(&request_file, content.clone(), IgnoreMediaRetentionPolicy::No)
172            .await
173            .expect("adding media failed");
174
175        // Media is present in the cache.
176        assert_eq!(
177            self.get_media_content(&request_file).await.unwrap().as_ref(),
178            Some(&content),
179            "media not found though added"
180        );
181        assert_eq!(
182            self.get_media_content_for_uri(uri).await.unwrap().as_ref(),
183            Some(&content),
184            "media not found by URI though added"
185        );
186
187        // Let's remove the media.
188        self.remove_media_content(&request_file).await.expect("removing media failed");
189
190        // Media isn't present in the cache.
191        assert!(
192            self.get_media_content(&request_file).await.unwrap().is_none(),
193            "media still there after removing"
194        );
195        assert!(
196            self.get_media_content_for_uri(uri).await.unwrap().is_none(),
197            "media still found by URI after removing"
198        );
199
200        // Let's add the media again.
201        self.add_media_content(&request_file, content.clone(), IgnoreMediaRetentionPolicy::No)
202            .await
203            .expect("adding media again failed");
204
205        assert_eq!(
206            self.get_media_content(&request_file).await.unwrap().as_ref(),
207            Some(&content),
208            "media not found after adding again"
209        );
210
211        // Let's add the thumbnail media.
212        self.add_media_content(
213            &request_thumbnail,
214            thumbnail_content.clone(),
215            IgnoreMediaRetentionPolicy::No,
216        )
217        .await
218        .expect("adding thumbnail failed");
219
220        // Media's thumbnail is present.
221        assert_eq!(
222            self.get_media_content(&request_thumbnail).await.unwrap().as_ref(),
223            Some(&thumbnail_content),
224            "thumbnail not found"
225        );
226
227        // We get a file with the URI, we don't know which one.
228        assert!(
229            self.get_media_content_for_uri(uri).await.unwrap().is_some(),
230            "media not found by URI though two where added"
231        );
232
233        // Let's add another media with a different URI.
234        self.add_media_content(
235            &request_other_file,
236            other_content.clone(),
237            IgnoreMediaRetentionPolicy::No,
238        )
239        .await
240        .expect("adding other media failed");
241
242        // Other file is present.
243        assert_eq!(
244            self.get_media_content(&request_other_file).await.unwrap().as_ref(),
245            Some(&other_content),
246            "other file not found"
247        );
248        assert_eq!(
249            self.get_media_content_for_uri(other_uri).await.unwrap().as_ref(),
250            Some(&other_content),
251            "other file not found by URI"
252        );
253
254        // Let's remove media based on URI.
255        self.remove_media_content_for_uri(uri).await.expect("removing all media for uri failed");
256
257        assert!(
258            self.get_media_content(&request_file).await.unwrap().is_none(),
259            "media wasn't removed"
260        );
261        assert!(
262            self.get_media_content(&request_thumbnail).await.unwrap().is_none(),
263            "thumbnail wasn't removed"
264        );
265        assert!(
266            self.get_media_content(&request_other_file).await.unwrap().is_some(),
267            "other media was removed"
268        );
269        assert!(
270            self.get_media_content_for_uri(uri).await.unwrap().is_none(),
271            "media found by URI wasn't removed"
272        );
273        assert!(
274            self.get_media_content_for_uri(other_uri).await.unwrap().is_some(),
275            "other media found by URI was removed"
276        );
277    }
278
279    async fn test_replace_media_key(&self) {
280        let uri = mxc_uri!("mxc://sendqueue.local/tr4n-s4ct-10n1-d");
281        let req = MediaRequestParameters {
282            source: MediaSource::Plain(uri.to_owned()),
283            format: MediaFormat::File,
284        };
285
286        let content = "hello".as_bytes().to_owned();
287
288        // Media isn't present in the cache.
289        assert!(self.get_media_content(&req).await.unwrap().is_none(), "unexpected media found");
290
291        // Add the media.
292        self.add_media_content(&req, content.clone(), IgnoreMediaRetentionPolicy::No)
293            .await
294            .expect("adding media failed");
295
296        // Sanity-check: media is found after adding it.
297        assert_eq!(self.get_media_content(&req).await.unwrap().unwrap(), b"hello");
298
299        // Replacing a media request works.
300        let new_uri = mxc_uri!("mxc://matrix.org/tr4n-s4ct-10n1-d");
301        let new_req = MediaRequestParameters {
302            source: MediaSource::Plain(new_uri.to_owned()),
303            format: MediaFormat::File,
304        };
305        self.replace_media_key(&req, &new_req)
306            .await
307            .expect("replacing the media request key failed");
308
309        // Finding with the previous request doesn't work anymore.
310        assert!(
311            self.get_media_content(&req).await.unwrap().is_none(),
312            "unexpected media found with the old key"
313        );
314
315        // Finding with the new request does work.
316        assert_eq!(self.get_media_content(&new_req).await.unwrap().unwrap(), b"hello");
317    }
318
319    async fn test_handle_updates_and_rebuild_linked_chunk(&self) {
320        let room_id = room_id!("!r0:matrix.org");
321
322        self.handle_linked_chunk_updates(
323            room_id,
324            vec![
325                // new chunk
326                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
327                // new items on 0
328                Update::PushItems {
329                    at: Position::new(CId::new(0), 0),
330                    items: vec![
331                        make_test_event(room_id, "hello"),
332                        make_test_event(room_id, "world"),
333                    ],
334                },
335                // a gap chunk
336                Update::NewGapChunk {
337                    previous: Some(CId::new(0)),
338                    new: CId::new(1),
339                    next: None,
340                    gap: Gap { prev_token: "parmesan".to_owned() },
341                },
342                // another items chunk
343                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
344                // new items on 2
345                Update::PushItems {
346                    at: Position::new(CId::new(2), 0),
347                    items: vec![make_test_event(room_id, "sup")],
348                },
349            ],
350        )
351        .await
352        .unwrap();
353
354        // The linked chunk is correctly reloaded.
355        let lc =
356            lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(room_id).await.unwrap())
357                .unwrap()
358                .unwrap();
359
360        let mut chunks = lc.chunks();
361
362        {
363            let first = chunks.next().unwrap();
364            // Note: we can't assert the previous/next chunks, as these fields and their
365            // getters are private.
366            assert_eq!(first.identifier(), CId::new(0));
367
368            assert_matches!(first.content(), ChunkContent::Items(events) => {
369                assert_eq!(events.len(), 2);
370                check_test_event(&events[0], "hello");
371                check_test_event(&events[1], "world");
372            });
373        }
374
375        {
376            let second = chunks.next().unwrap();
377            assert_eq!(second.identifier(), CId::new(1));
378
379            assert_matches!(second.content(), ChunkContent::Gap(gap) => {
380                assert_eq!(gap.prev_token, "parmesan");
381            });
382        }
383
384        {
385            let third = chunks.next().unwrap();
386            assert_eq!(third.identifier(), CId::new(2));
387
388            assert_matches!(third.content(), ChunkContent::Items(events) => {
389                assert_eq!(events.len(), 1);
390                check_test_event(&events[0], "sup");
391            });
392        }
393
394        assert!(chunks.next().is_none());
395    }
396
397    async fn test_linked_chunk_incremental_loading(&self) {
398        let room_id = room_id!("!r0:matrix.org");
399        let event = |msg: &str| make_test_event(room_id, msg);
400
401        // Load the last chunk, but none exists yet.
402        {
403            let (last_chunk, chunk_identifier_generator) =
404                self.load_last_chunk(room_id).await.unwrap();
405
406            assert!(last_chunk.is_none());
407            assert_eq!(chunk_identifier_generator.current(), 0);
408        }
409
410        self.handle_linked_chunk_updates(
411            room_id,
412            vec![
413                // new chunk for items
414                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
415                // new items on 0
416                Update::PushItems {
417                    at: Position::new(CId::new(0), 0),
418                    items: vec![event("a"), event("b")],
419                },
420                // new chunk for a gap
421                Update::NewGapChunk {
422                    previous: Some(CId::new(0)),
423                    new: CId::new(1),
424                    next: None,
425                    gap: Gap { prev_token: "morbier".to_owned() },
426                },
427                // new chunk for items
428                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
429                // new items on 2
430                Update::PushItems {
431                    at: Position::new(CId::new(2), 0),
432                    items: vec![event("c"), event("d"), event("e")],
433                },
434            ],
435        )
436        .await
437        .unwrap();
438
439        // Load the last chunk.
440        let mut linked_chunk = {
441            let (last_chunk, chunk_identifier_generator) =
442                self.load_last_chunk(room_id).await.unwrap();
443
444            assert_eq!(chunk_identifier_generator.current(), 2);
445
446            let linked_chunk = lazy_loader::from_last_chunk::<DEFAULT_CHUNK_CAPACITY, _, _>(
447                last_chunk,
448                chunk_identifier_generator,
449            )
450            .unwrap() // unwrap the `Result`
451            .unwrap(); // unwrap the `Option`
452
453            let mut rchunks = linked_chunk.rchunks();
454
455            // A unique chunk.
456            assert_matches!(rchunks.next(), Some(chunk) => {
457                assert_eq!(chunk.identifier(), 2);
458
459                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
460                    assert_eq!(events.len(), 3);
461                    check_test_event(&events[0], "c");
462                    check_test_event(&events[1], "d");
463                    check_test_event(&events[2], "e");
464                });
465            });
466
467            assert!(rchunks.next().is_none());
468
469            linked_chunk
470        };
471
472        // Load the previous chunk: this is a gap.
473        {
474            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
475            let mut previous_chunk =
476                self.load_previous_chunk(room_id, first_chunk).await.unwrap().unwrap();
477
478            // Pretend it's the first chunk.
479            previous_chunk.previous = None;
480
481            let _ = lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
482
483            let mut rchunks = linked_chunk.rchunks();
484
485            // The last chunk.
486            assert_matches!(rchunks.next(), Some(chunk) => {
487                assert_eq!(chunk.identifier(), 2);
488
489                // Already asserted, but let's be sure nothing breaks.
490                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
491                    assert_eq!(events.len(), 3);
492                    check_test_event(&events[0], "c");
493                    check_test_event(&events[1], "d");
494                    check_test_event(&events[2], "e");
495                });
496            });
497
498            // The new chunk.
499            assert_matches!(rchunks.next(), Some(chunk) => {
500                assert_eq!(chunk.identifier(), 1);
501
502                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
503                    assert_eq!(gap.prev_token, "morbier");
504                });
505            });
506
507            assert!(rchunks.next().is_none());
508        }
509
510        // Load the previous chunk: these are items.
511        {
512            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
513            let previous_chunk =
514                self.load_previous_chunk(room_id, first_chunk).await.unwrap().unwrap();
515
516            let _ = lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
517
518            let mut rchunks = linked_chunk.rchunks();
519
520            // The last chunk.
521            assert_matches!(rchunks.next(), Some(chunk) => {
522                assert_eq!(chunk.identifier(), 2);
523
524                // Already asserted, but let's be sure nothing breaks.
525                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
526                    assert_eq!(events.len(), 3);
527                    check_test_event(&events[0], "c");
528                    check_test_event(&events[1], "d");
529                    check_test_event(&events[2], "e");
530                });
531            });
532
533            // Its previous chunk.
534            assert_matches!(rchunks.next(), Some(chunk) => {
535                assert_eq!(chunk.identifier(), 1);
536
537                // Already asserted, but let's be sure nothing breaks.
538                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
539                    assert_eq!(gap.prev_token, "morbier");
540                });
541            });
542
543            // The new chunk.
544            assert_matches!(rchunks.next(), Some(chunk) => {
545                assert_eq!(chunk.identifier(), 0);
546
547                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
548                    assert_eq!(events.len(), 2);
549                    check_test_event(&events[0], "a");
550                    check_test_event(&events[1], "b");
551                });
552            });
553
554            assert!(rchunks.next().is_none());
555        }
556
557        // Load the previous chunk: there is none.
558        {
559            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
560            let previous_chunk = self.load_previous_chunk(room_id, first_chunk).await.unwrap();
561
562            assert!(previous_chunk.is_none());
563        }
564
565        // One last check: a round of assert by using the forwards chunk iterator
566        // instead of the backwards chunk iterator.
567        {
568            let mut chunks = linked_chunk.chunks();
569
570            // The first chunk.
571            assert_matches!(chunks.next(), Some(chunk) => {
572                assert_eq!(chunk.identifier(), 0);
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            // The second chunk.
582            assert_matches!(chunks.next(), Some(chunk) => {
583                assert_eq!(chunk.identifier(), 1);
584
585                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
586                    assert_eq!(gap.prev_token, "morbier");
587                });
588            });
589
590            // The third and last chunk.
591            assert_matches!(chunks.next(), Some(chunk) => {
592                assert_eq!(chunk.identifier(), 2);
593
594                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
595                    assert_eq!(events.len(), 3);
596                    check_test_event(&events[0], "c");
597                    check_test_event(&events[1], "d");
598                    check_test_event(&events[2], "e");
599                });
600            });
601
602            assert!(chunks.next().is_none());
603        }
604    }
605
606    async fn test_rebuild_empty_linked_chunk(&self) {
607        // When I rebuild a linked chunk from an empty store, it's empty.
608        let linked_chunk = lazy_loader::from_all_chunks::<3, _, _>(
609            self.load_all_chunks(&DEFAULT_TEST_ROOM_ID).await.unwrap(),
610        )
611        .unwrap();
612        assert!(linked_chunk.is_none());
613    }
614
615    async fn test_clear_all_rooms_chunks(&self) {
616        let r0 = room_id!("!r0:matrix.org");
617        let r1 = room_id!("!r1:matrix.org");
618
619        // Add updates for the first room.
620        self.handle_linked_chunk_updates(
621            r0,
622            vec![
623                // new chunk
624                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
625                // new items on 0
626                Update::PushItems {
627                    at: Position::new(CId::new(0), 0),
628                    items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
629                },
630            ],
631        )
632        .await
633        .unwrap();
634
635        // Add updates for the second room.
636        self.handle_linked_chunk_updates(
637            r1,
638            vec![
639                // Empty items chunk.
640                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
641                // a gap chunk
642                Update::NewGapChunk {
643                    previous: Some(CId::new(0)),
644                    new: CId::new(1),
645                    next: None,
646                    gap: Gap { prev_token: "bleu d'auvergne".to_owned() },
647                },
648                // another items chunk
649                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
650                // new items on 0
651                Update::PushItems {
652                    at: Position::new(CId::new(2), 0),
653                    items: vec![make_test_event(r0, "yummy")],
654                },
655            ],
656        )
657        .await
658        .unwrap();
659
660        // Sanity check: both linked chunks can be reloaded.
661        assert!(lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(r0).await.unwrap())
662            .unwrap()
663            .is_some());
664        assert!(lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(r1).await.unwrap())
665            .unwrap()
666            .is_some());
667
668        // Clear the chunks.
669        self.clear_all_rooms_chunks().await.unwrap();
670
671        // Both rooms now have no linked chunk.
672        assert!(lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(r0).await.unwrap())
673            .unwrap()
674            .is_none());
675        assert!(lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(r1).await.unwrap())
676            .unwrap()
677            .is_none());
678    }
679
680    async fn test_remove_room(&self) {
681        let r0 = room_id!("!r0:matrix.org");
682        let r1 = room_id!("!r1:matrix.org");
683
684        // Add updates to the first room.
685        self.handle_linked_chunk_updates(
686            r0,
687            vec![
688                // new chunk
689                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
690                // new items on 0
691                Update::PushItems {
692                    at: Position::new(CId::new(0), 0),
693                    items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
694                },
695            ],
696        )
697        .await
698        .unwrap();
699
700        // Add updates to the second room.
701        self.handle_linked_chunk_updates(
702            r1,
703            vec![
704                // new chunk
705                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
706                // new items on 0
707                Update::PushItems {
708                    at: Position::new(CId::new(0), 0),
709                    items: vec![make_test_event(r0, "yummy")],
710                },
711            ],
712        )
713        .await
714        .unwrap();
715
716        // Try to remove content from r0.
717        self.remove_room(r0).await.unwrap();
718
719        // Check that r0 doesn't have a linked chunk anymore.
720        let r0_linked_chunk = self.load_all_chunks(r0).await.unwrap();
721        assert!(r0_linked_chunk.is_empty());
722
723        // Check that r1 is unaffected.
724        let r1_linked_chunk = self.load_all_chunks(r1).await.unwrap();
725        assert!(!r1_linked_chunk.is_empty());
726    }
727
728    async fn test_filter_duplicated_events(&self) {
729        let room_id = room_id!("!r0:matrix.org");
730        let another_room_id = room_id!("!r1:matrix.org");
731        let event = |msg: &str| make_test_event(room_id, msg);
732
733        let event_comte = event("comté");
734        let event_brigand = event("brigand du jorat");
735        let event_raclette = event("raclette");
736        let event_morbier = event("morbier");
737        let event_gruyere = event("gruyère");
738        let event_tome = event("tome");
739        let event_mont_dor = event("mont d'or");
740
741        self.handle_linked_chunk_updates(
742            room_id,
743            vec![
744                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
745                Update::PushItems {
746                    at: Position::new(CId::new(0), 0),
747                    items: vec![event_comte.clone(), event_brigand.clone()],
748                },
749                Update::NewGapChunk {
750                    previous: Some(CId::new(0)),
751                    new: CId::new(1),
752                    next: None,
753                    gap: Gap { prev_token: "brillat-savarin".to_owned() },
754                },
755                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
756                Update::PushItems {
757                    at: Position::new(CId::new(2), 0),
758                    items: vec![event_morbier.clone(), event_mont_dor.clone()],
759                },
760            ],
761        )
762        .await
763        .unwrap();
764
765        // Add other events in another room, to ensure filtering take the `room_id` into
766        // account.
767        self.handle_linked_chunk_updates(
768            another_room_id,
769            vec![
770                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
771                Update::PushItems {
772                    at: Position::new(CId::new(0), 0),
773                    items: vec![event_tome.clone()],
774                },
775            ],
776        )
777        .await
778        .unwrap();
779
780        let duplicated_events = self
781            .filter_duplicated_events(
782                room_id,
783                vec![
784                    event_comte.event_id().unwrap().to_owned(),
785                    event_raclette.event_id().unwrap().to_owned(),
786                    event_morbier.event_id().unwrap().to_owned(),
787                    event_gruyere.event_id().unwrap().to_owned(),
788                    event_tome.event_id().unwrap().to_owned(),
789                    event_mont_dor.event_id().unwrap().to_owned(),
790                ],
791            )
792            .await
793            .unwrap();
794
795        assert_eq!(duplicated_events.len(), 3);
796        assert_eq!(duplicated_events[0], event_comte.event_id().unwrap());
797        assert_eq!(duplicated_events[1], event_morbier.event_id().unwrap());
798        assert_eq!(duplicated_events[2], event_mont_dor.event_id().unwrap());
799    }
800}
801
802/// Macro building to allow your `EventCacheStore` implementation to run the
803/// entire tests suite locally.
804///
805/// You need to provide a `async fn get_event_cache_store() ->
806/// EventCacheStoreResult<impl EventCacheStore>` providing a fresh event cache
807/// store on the same level you invoke the macro.
808///
809/// ## Usage Example:
810/// ```no_run
811/// # use matrix_sdk_base::event_cache::store::{
812/// #    EventCacheStore,
813/// #    MemoryStore as MyStore,
814/// #    Result as EventCacheStoreResult,
815/// # };
816///
817/// #[cfg(test)]
818/// mod tests {
819///     use super::{EventCacheStore, EventCacheStoreResult, MyStore};
820///
821///     async fn get_event_cache_store(
822///     ) -> EventCacheStoreResult<impl EventCacheStore> {
823///         Ok(MyStore::new())
824///     }
825///
826///     event_cache_store_integration_tests!();
827/// }
828/// ```
829#[allow(unused_macros, unused_extern_crates)]
830#[macro_export]
831macro_rules! event_cache_store_integration_tests {
832    () => {
833        mod event_cache_store_integration_tests {
834            use matrix_sdk_test::async_test;
835            use $crate::event_cache::store::{
836                EventCacheStoreIntegrationTests, IntoEventCacheStore,
837            };
838
839            use super::get_event_cache_store;
840
841            #[async_test]
842            async fn test_media_content() {
843                let event_cache_store =
844                    get_event_cache_store().await.unwrap().into_event_cache_store();
845                event_cache_store.test_media_content().await;
846            }
847
848            #[async_test]
849            async fn test_replace_media_key() {
850                let event_cache_store =
851                    get_event_cache_store().await.unwrap().into_event_cache_store();
852                event_cache_store.test_replace_media_key().await;
853            }
854
855            #[async_test]
856            async fn test_handle_updates_and_rebuild_linked_chunk() {
857                let event_cache_store =
858                    get_event_cache_store().await.unwrap().into_event_cache_store();
859                event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
860            }
861
862            #[async_test]
863            async fn test_linked_chunk_incremental_loading() {
864                let event_cache_store =
865                    get_event_cache_store().await.unwrap().into_event_cache_store();
866                event_cache_store.test_linked_chunk_incremental_loading().await;
867            }
868
869            #[async_test]
870            async fn test_rebuild_empty_linked_chunk() {
871                let event_cache_store =
872                    get_event_cache_store().await.unwrap().into_event_cache_store();
873                event_cache_store.test_rebuild_empty_linked_chunk().await;
874            }
875
876            #[async_test]
877            async fn test_clear_all_rooms_chunks() {
878                let event_cache_store =
879                    get_event_cache_store().await.unwrap().into_event_cache_store();
880                event_cache_store.test_clear_all_rooms_chunks().await;
881            }
882
883            #[async_test]
884            async fn test_remove_room() {
885                let event_cache_store =
886                    get_event_cache_store().await.unwrap().into_event_cache_store();
887                event_cache_store.test_remove_room().await;
888            }
889
890            #[async_test]
891            async fn test_filter_duplicated_events() {
892                let event_cache_store =
893                    get_event_cache_store().await.unwrap().into_event_cache_store();
894                event_cache_store.test_filter_duplicated_events().await;
895            }
896        }
897    };
898}
899
900/// Macro generating tests for the event cache store, related to time (mostly
901/// for the cross-process lock).
902#[allow(unused_macros)]
903#[macro_export]
904macro_rules! event_cache_store_integration_tests_time {
905    () => {
906        #[cfg(not(target_arch = "wasm32"))]
907        mod event_cache_store_integration_tests_time {
908            use std::time::Duration;
909
910            use matrix_sdk_test::async_test;
911            use $crate::event_cache::store::IntoEventCacheStore;
912
913            use super::get_event_cache_store;
914
915            #[async_test]
916            async fn test_lease_locks() {
917                let store = get_event_cache_store().await.unwrap().into_event_cache_store();
918
919                let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
920                assert!(acquired0);
921
922                // Should extend the lease automatically (same holder).
923                let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
924                assert!(acquired2);
925
926                // Should extend the lease automatically (same holder + time is ok).
927                let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
928                assert!(acquired3);
929
930                // Another attempt at taking the lock should fail, because it's taken.
931                let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
932                assert!(!acquired4);
933
934                // Even if we insist.
935                let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
936                assert!(!acquired5);
937
938                // That's a nice test we got here, go take a little nap.
939                tokio::time::sleep(Duration::from_millis(50)).await;
940
941                // Still too early.
942                let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
943                assert!(!acquired55);
944
945                // Ok you can take another nap then.
946                tokio::time::sleep(Duration::from_millis(250)).await;
947
948                // At some point, we do get the lock.
949                let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
950                assert!(acquired6);
951
952                tokio::time::sleep(Duration::from_millis(1)).await;
953
954                // The other gets it almost immediately too.
955                let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
956                assert!(acquired7);
957
958                tokio::time::sleep(Duration::from_millis(1)).await;
959
960                // But when we take a longer lease...
961                let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
962                assert!(acquired8);
963
964                // It blocks the other user.
965                let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
966                assert!(!acquired9);
967
968                // We can hold onto our lease.
969                let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
970                assert!(acquired10);
971            }
972        }
973    };
974}