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