matrix_sdk_base/event_cache/store/
integration_tests.rs

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