Skip to main content

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::{
45    super::{Gap, thread::ThreadInfo},
46    DEFAULT_CHUNK_CAPACITY, DynEventCacheStore,
47};
48use crate::read_receipts::ReadReceipts;
49
50/// Create a test event with all data filled, for testing that linked chunk
51/// correctly stores event data.
52///
53/// Keep in sync with [`check_test_event`].
54pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent {
55    make_test_event_with_event_id(room_id, content, None)
56}
57
58/// Create a `m.room.encrypted` test event with all data filled, for testing
59/// that linked chunk correctly stores event data for encrypted events.
60pub fn make_encrypted_test_event(room_id: &RoomId, session_id: &str) -> TimelineEvent {
61    let device_id = "DEVICEID";
62    let builder = EventFactory::new()
63        .encrypted("", "curve_key", device_id, session_id)
64        .room(room_id)
65        .sender(*ALICE);
66
67    let event = builder.into_raw();
68    let utd_info = UnableToDecryptInfo {
69        session_id: Some(session_id.to_owned()),
70        reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
71    };
72
73    TimelineEvent::from_utd(event, utd_info)
74}
75
76/// Same as [`make_test_event`], with an extra event id.
77pub fn make_test_event_with_event_id(
78    room_id: &RoomId,
79    content: &str,
80    event_id: Option<&EventId>,
81) -> TimelineEvent {
82    let encryption_info = Arc::new(EncryptionInfo {
83        sender: (*ALICE).into(),
84        sender_device: None,
85        forwarder: None,
86        algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
87            curve25519_key: "1337".to_owned(),
88            sender_claimed_keys: Default::default(),
89            session_id: Some("mysessionid9".to_owned()),
90        },
91        verification_state: VerificationState::Verified,
92    });
93
94    let mut builder = EventFactory::new().text_msg(content).room(room_id).sender(*ALICE);
95    if let Some(event_id) = event_id {
96        builder = builder.event_id(event_id);
97    }
98    let event = builder.into_raw();
99
100    TimelineEvent::from_decrypted(
101        DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info: None },
102        Some(vec![Action::Notify]),
103    )
104}
105
106/// Check that an event created with [`make_test_event`] contains the expected
107/// data.
108///
109/// Keep in sync with [`make_test_event`].
110#[track_caller]
111pub fn check_test_event(event: &TimelineEvent, text: &str) {
112    // Check push actions.
113    let actions = event.push_actions().unwrap();
114    assert_eq!(actions.len(), 1);
115    assert_matches!(&actions[0], Action::Notify);
116
117    // Check content.
118    assert_matches!(&event.kind, TimelineEventKind::Decrypted(d) => {
119        // Check encryption fields.
120        assert_eq!(d.encryption_info.sender, *ALICE);
121        assert_matches!(&d.encryption_info.algorithm_info, AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => {
122            assert_eq!(curve25519_key, "1337");
123        });
124
125        // Check event.
126        let deserialized = d.event.deserialize().unwrap();
127        assert_matches!(deserialized, AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(msg)) => {
128            assert_eq!(msg.as_original().unwrap().content.body(), text);
129        });
130    });
131}
132
133/// `EventCacheStore` integration tests.
134///
135/// This trait is not meant to be used directly, but will be used with the
136/// `event_cache_store_integration_tests!` macro.
137#[allow(async_fn_in_trait)]
138pub trait EventCacheStoreIntegrationTests {
139    /// Test handling updates to a linked chunk and reloading these updates from
140    /// the store.
141    async fn test_handle_updates_and_rebuild_linked_chunk(&self);
142
143    /// Test that the next and previous fields only reference chunks that
144    /// already exist in the store.
145    async fn test_linked_chunk_exists_before_referenced(&self);
146
147    /// Test that the same event can exist in a room's linked chunk and a
148    /// thread's linked chunk simultaneously.
149    async fn test_linked_chunk_allows_same_event_in_room_and_thread(&self);
150
151    /// Test loading the last chunk in a linked chunk from the store.
152    async fn test_load_last_chunk(&self);
153
154    /// Test that cycles are detected when loading the last chunk in a linked
155    /// chunk from the store.
156    async fn test_load_last_chunk_with_a_cycle(&self);
157
158    /// Test loading the previous chunk in a linked chunk from the store.
159    async fn test_load_previous_chunk(&self);
160
161    /// Test loading a linked chunk incrementally (chunk by chunk) from the
162    /// store.
163    async fn test_linked_chunk_incremental_loading(&self);
164
165    /// Test removing a chunk.
166    async fn test_linked_chunk_remove_chunk(&self);
167
168    /// Test replacing an item in a linked chunk.
169    async fn test_linked_chunk_replace_item(&self);
170
171    /// Test remove an item from a linked chunk.
172    async fn test_linked_chunk_remove_item(&self);
173
174    /// Test detaching last items from a linked chunk.
175    async fn test_linked_chunk_detach_last_items(&self);
176
177    /// Test that start reattach and end reattach items does nothing.
178    async fn test_linked_chunk_start_end_reattach_items(&self);
179
180    /// Test clearing a linked chunk.
181    async fn test_linked_chunk_clear(&self);
182
183    /// Test clearing a linked chunk and re-inserting a past event.
184    async fn test_linked_chunk_clear_and_reinsert(&self);
185
186    /// Test that rebuilding a linked chunk from an empty store doesn't return
187    /// anything.
188    async fn test_rebuild_empty_linked_chunk(&self);
189
190    /// Test that linked chunks are only accessible through their enclosing
191    /// room.
192    async fn test_linked_chunk_multiple_rooms(&self);
193
194    /// Test that loading a linked chunk's metadata works as intended.
195    async fn test_load_all_chunks_metadata(&self);
196
197    /// Test that loading and updating a `ThreadInfo` acts as expected.
198    async fn test_load_and_update_thread_info(&self);
199
200    /// Test that clearing all the rooms' events and linked chunks work.
201    async fn test_clear_all_events(&self);
202
203    /// Test that clearing a specific room events and linked chunks works.
204    async fn test_clear_all_events_for_specific_room(&self);
205
206    /// Test that filtering duplicated events works as expected.
207    async fn test_filter_duplicated_events(&self);
208
209    /// Test that filtering duplicated events works with an empty filter.
210    async fn test_filter_duplicate_events_no_events(&self);
211
212    /// Test that an event can be found or not.
213    async fn test_find_event(&self);
214
215    /// Test that an event can be found when it exists in both a room and a
216    /// thread in that room.
217    async fn test_find_event_when_event_in_room_and_thread(&self);
218
219    /// Test that finding event relations works as expected.
220    async fn test_find_event_relations(&self);
221
222    /// Test that find event relations works as expected when an event is both a
223    /// room and a thread in that room.
224    async fn test_find_event_relations_when_event_in_room_and_thread(&self);
225
226    /// Test that getting all events in a room works as expected.
227    async fn test_get_room_events(&self);
228
229    /// Test that getting events in a room of a certain type works as expected.
230    async fn test_get_room_events_filtered(&self);
231
232    /// Test that getting all events in a room works as expected when the event
233    /// is in both a room and thread in that room.
234    async fn test_get_room_events_with_event_in_room_and_thread(&self);
235
236    /// Test that saving an event works as expected.
237    async fn test_save_event(&self);
238
239    /// Test that saving an existing event updates it's contents in both room
240    /// and thread linked chunks.
241    async fn test_save_event_updates_event_in_room_and_thread(&self);
242
243    /// Test multiple things related to distinguishing a thread linked chunk
244    /// from a room linked chunk.
245    async fn test_thread_vs_room_linked_chunk(&self);
246}
247
248impl EventCacheStoreIntegrationTests for DynEventCacheStore {
249    async fn test_handle_updates_and_rebuild_linked_chunk(&self) {
250        let room_id = room_id!("!r0:matrix.org");
251        let linked_chunk_id = LinkedChunkId::Room(room_id);
252
253        self.handle_linked_chunk_updates(
254            linked_chunk_id,
255            vec![
256                // new chunk
257                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
258                // new items on 0
259                Update::PushItems {
260                    at: Position::new(CId::new(0), 0),
261                    items: vec![
262                        make_test_event(room_id, "hello"),
263                        make_test_event(room_id, "world"),
264                    ],
265                },
266                // a gap chunk
267                Update::NewGapChunk {
268                    previous: Some(CId::new(0)),
269                    new: CId::new(1),
270                    next: None,
271                    gap: Gap { token: "parmesan".to_owned() },
272                },
273                // another items chunk
274                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
275                // new items on 2
276                Update::PushItems {
277                    at: Position::new(CId::new(2), 0),
278                    items: vec![make_test_event(room_id, "sup")],
279                },
280            ],
281        )
282        .await
283        .unwrap();
284
285        // The linked chunk is correctly reloaded.
286        let lc = lazy_loader::from_all_chunks::<3, _, _>(
287            self.load_all_chunks(linked_chunk_id).await.unwrap(),
288        )
289        .unwrap()
290        .unwrap();
291
292        let mut chunks = lc.chunks();
293
294        {
295            let first = chunks.next().unwrap();
296            // Note: we can't assert the previous/next chunks, as these fields and their
297            // getters are private.
298            assert_eq!(first.identifier(), CId::new(0));
299
300            assert_matches!(first.content(), ChunkContent::Items(events) => {
301                assert_eq!(events.len(), 2);
302                check_test_event(&events[0], "hello");
303                check_test_event(&events[1], "world");
304            });
305        }
306
307        {
308            let second = chunks.next().unwrap();
309            assert_eq!(second.identifier(), CId::new(1));
310
311            assert_matches!(second.content(), ChunkContent::Gap(gap) => {
312                assert_eq!(gap.token, "parmesan");
313            });
314        }
315
316        {
317            let third = chunks.next().unwrap();
318            assert_eq!(third.identifier(), CId::new(2));
319
320            assert_matches!(third.content(), ChunkContent::Items(events) => {
321                assert_eq!(events.len(), 1);
322                check_test_event(&events[0], "sup");
323            });
324        }
325
326        assert!(chunks.next().is_none());
327    }
328
329    async fn test_linked_chunk_exists_before_referenced(&self) {
330        let room_id = *DEFAULT_TEST_ROOM_ID;
331        let linked_chunk_id = LinkedChunkId::Room(room_id);
332
333        // Fails to add the chunk because previous chunk is not in the self
334        self.handle_linked_chunk_updates(
335            linked_chunk_id,
336            vec![Update::NewItemsChunk {
337                previous: Some(CId::new(41)),
338                new: CId::new(42),
339                next: None,
340            }],
341        )
342        .await
343        .unwrap_err();
344
345        // Fails to add the chunk because next chunk is not in the self
346        self.handle_linked_chunk_updates(
347            linked_chunk_id,
348            vec![Update::NewItemsChunk {
349                previous: None,
350                new: CId::new(42),
351                next: Some(CId::new(43)),
352            }],
353        )
354        .await
355        .unwrap_err();
356
357        // Fails to add the chunk because previous chunk is not in the self
358        self.handle_linked_chunk_updates(
359            linked_chunk_id,
360            vec![Update::NewGapChunk {
361                previous: Some(CId::new(41)),
362                new: CId::new(42),
363                next: None,
364                gap: Gap { token: "gap".to_owned() },
365            }],
366        )
367        .await
368        .unwrap_err();
369
370        // Fails to add the chunk because next chunk is not in the self
371        self.handle_linked_chunk_updates(
372            linked_chunk_id,
373            vec![Update::NewGapChunk {
374                previous: None,
375                new: CId::new(42),
376                next: Some(CId::new(43)),
377                gap: Gap { token: "gap".to_owned() },
378            }],
379        )
380        .await
381        .unwrap_err();
382    }
383
384    async fn test_linked_chunk_allows_same_event_in_room_and_thread(&self) {
385        // This test verifies that the same event can appear in both a room's linked
386        // chunk and a thread's linked chunk. This is the real-world use case:
387        // a thread reply appears in both the main room timeline and the thread.
388
389        let room_id = *DEFAULT_TEST_ROOM_ID;
390        let thread_root = event_id!("$thread_root");
391
392        // Create an event that will be inserted into both the room and thread linked
393        // chunks.
394        let event_id = event_id!("$thread_reply");
395        let event = make_test_event_with_event_id(room_id, "thread reply", Some(event_id));
396
397        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
398        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
399
400        // Insert the event into the room's linked chunk.
401        self.handle_linked_chunk_updates(
402            room_linked_chunk_id,
403            vec![
404                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
405                Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
406            ],
407        )
408        .await
409        .unwrap();
410
411        // Insert the same event into the thread's linked chunk.
412        self.handle_linked_chunk_updates(
413            thread_linked_chunk_id,
414            vec![
415                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
416                Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event] },
417            ],
418        )
419        .await
420        .unwrap();
421
422        // Verify both entries exist by loading chunks from both linked chunk IDs.
423        let room_chunks = self.load_all_chunks(room_linked_chunk_id).await.unwrap();
424        let thread_chunks = self.load_all_chunks(thread_linked_chunk_id).await.unwrap();
425
426        assert_eq!(room_chunks.len(), 1);
427        assert_eq!(thread_chunks.len(), 1);
428
429        // Verify the event is in both.
430        assert_matches!(&room_chunks[0].content, ChunkContent::Items(events) => {
431            assert_eq!(events.len(), 1);
432            assert_eq!(events[0].event_id(), Some(event_id));
433        });
434        assert_matches!(&thread_chunks[0].content, ChunkContent::Items(events) => {
435            assert_eq!(events.len(), 1);
436            assert_eq!(events[0].event_id(), Some(event_id));
437        });
438    }
439
440    async fn test_load_all_chunks_metadata(&self) {
441        let room_id = room_id!("!r0:matrix.org");
442        let linked_chunk_id = LinkedChunkId::Room(room_id);
443
444        self.handle_linked_chunk_updates(
445            linked_chunk_id,
446            vec![
447                // new chunk
448                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
449                // new items on 0
450                Update::PushItems {
451                    at: Position::new(CId::new(0), 0),
452                    items: vec![
453                        make_test_event(room_id, "hello"),
454                        make_test_event(room_id, "world"),
455                    ],
456                },
457                // a gap chunk
458                Update::NewGapChunk {
459                    previous: Some(CId::new(0)),
460                    new: CId::new(1),
461                    next: None,
462                    gap: Gap { token: "parmesan".to_owned() },
463                },
464                // another items chunk
465                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
466                // new items on 2
467                Update::PushItems {
468                    at: Position::new(CId::new(2), 0),
469                    items: vec![make_test_event(room_id, "sup")],
470                },
471                // and an empty items chunk to finish
472                Update::NewItemsChunk { previous: Some(CId::new(2)), new: CId::new(3), next: None },
473            ],
474        )
475        .await
476        .unwrap();
477
478        let metas = self.load_all_chunks_metadata(linked_chunk_id).await.unwrap();
479        assert_eq!(metas.len(), 4);
480
481        // The first chunk has two items.
482        assert_eq!(metas[0].identifier, CId::new(0));
483        assert_eq!(metas[0].previous, None);
484        assert_eq!(metas[0].next, Some(CId::new(1)));
485        assert_eq!(metas[0].num_items, 2);
486
487        // The second chunk is a gap, so it has 0 items.
488        assert_eq!(metas[1].identifier, CId::new(1));
489        assert_eq!(metas[1].previous, Some(CId::new(0)));
490        assert_eq!(metas[1].next, Some(CId::new(2)));
491        assert_eq!(metas[1].num_items, 0);
492
493        // The third event chunk has one item.
494        assert_eq!(metas[2].identifier, CId::new(2));
495        assert_eq!(metas[2].previous, Some(CId::new(1)));
496        assert_eq!(metas[2].next, Some(CId::new(3)));
497        assert_eq!(metas[2].num_items, 1);
498
499        // The final event chunk is empty.
500        assert_eq!(metas[3].identifier, CId::new(3));
501        assert_eq!(metas[3].previous, Some(CId::new(2)));
502        assert_eq!(metas[3].next, None);
503        assert_eq!(metas[3].num_items, 0);
504    }
505
506    async fn test_load_last_chunk(&self) {
507        let room_id = room_id!("!r0:matrix.org");
508        let linked_chunk_id = LinkedChunkId::Room(room_id);
509        let event = |msg: &str| make_test_event(room_id, msg);
510
511        // Case #1: no last chunk.
512        {
513            let (last_chunk, chunk_identifier_generator) =
514                self.load_last_chunk(linked_chunk_id).await.unwrap();
515
516            assert!(last_chunk.is_none());
517            assert_eq!(chunk_identifier_generator.current(), 0);
518        }
519
520        // Case #2: only one chunk is present.
521        {
522            self.handle_linked_chunk_updates(
523                linked_chunk_id,
524                vec![
525                    Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
526                    Update::PushItems {
527                        at: Position::new(CId::new(42), 0),
528                        items: vec![event("saucisse de morteau"), event("comté")],
529                    },
530                ],
531            )
532            .await
533            .unwrap();
534
535            let (last_chunk, chunk_identifier_generator) =
536                self.load_last_chunk(linked_chunk_id).await.unwrap();
537
538            assert_matches!(last_chunk, Some(last_chunk) => {
539                assert_eq!(last_chunk.identifier, 42);
540                assert!(last_chunk.previous.is_none());
541                assert!(last_chunk.next.is_none());
542                assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
543                    assert_eq!(items.len(), 2);
544                    check_test_event(&items[0], "saucisse de morteau");
545                    check_test_event(&items[1], "comté");
546                });
547            });
548            assert_eq!(chunk_identifier_generator.current(), 42);
549        }
550
551        // Case #3: more chunks are present.
552        {
553            self.handle_linked_chunk_updates(
554                linked_chunk_id,
555                vec![
556                    Update::NewItemsChunk {
557                        previous: Some(CId::new(42)),
558                        new: CId::new(7),
559                        next: None,
560                    },
561                    Update::PushItems {
562                        at: Position::new(CId::new(7), 0),
563                        items: vec![event("fondue"), event("gruyère"), event("mont d'or")],
564                    },
565                ],
566            )
567            .await
568            .unwrap();
569
570            let (last_chunk, chunk_identifier_generator) =
571                self.load_last_chunk(linked_chunk_id).await.unwrap();
572
573            assert_matches!(last_chunk, Some(last_chunk) => {
574                assert_eq!(last_chunk.identifier, 7);
575                assert_matches!(last_chunk.previous, Some(previous) => {
576                    assert_eq!(previous, 42);
577                });
578                assert!(last_chunk.next.is_none());
579                assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
580                    assert_eq!(items.len(), 3);
581                    check_test_event(&items[0], "fondue");
582                    check_test_event(&items[1], "gruyère");
583                    check_test_event(&items[2], "mont d'or");
584                });
585            });
586            assert_eq!(chunk_identifier_generator.current(), 42);
587        }
588    }
589
590    async fn test_load_last_chunk_with_a_cycle(&self) {
591        let room_id = room_id!("!r0:matrix.org");
592        let linked_chunk_id = LinkedChunkId::Room(room_id);
593
594        self.handle_linked_chunk_updates(
595            linked_chunk_id,
596            vec![
597                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
598                Update::NewItemsChunk {
599                    // Because `previous` connects to chunk #0, it will create a cycle.
600                    // Chunk #0 will have a `next` set to chunk #1! Consequently, the last chunk
601                    // **does not exist**. We have to detect this cycle.
602                    previous: Some(CId::new(0)),
603                    new: CId::new(1),
604                    next: Some(CId::new(0)),
605                },
606            ],
607        )
608        .await
609        .unwrap();
610
611        self.load_last_chunk(linked_chunk_id).await.unwrap_err();
612    }
613
614    async fn test_load_previous_chunk(&self) {
615        let room_id = room_id!("!r0:matrix.org");
616        let linked_chunk_id = LinkedChunkId::Room(room_id);
617        let event = |msg: &str| make_test_event(room_id, msg);
618
619        // Case #1: no chunk at all, equivalent to having an nonexistent
620        // `before_chunk_identifier`.
621        {
622            let previous_chunk =
623                self.load_previous_chunk(linked_chunk_id, CId::new(153)).await.unwrap();
624
625            assert!(previous_chunk.is_none());
626        }
627
628        // Case #2: there is one chunk only: we request the previous on this
629        // one, it doesn't exist.
630        {
631            self.handle_linked_chunk_updates(
632                linked_chunk_id,
633                vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
634            )
635            .await
636            .unwrap();
637
638            let previous_chunk =
639                self.load_previous_chunk(linked_chunk_id, CId::new(42)).await.unwrap();
640
641            assert!(previous_chunk.is_none());
642        }
643
644        // Case #3: there are two chunks.
645        {
646            self.handle_linked_chunk_updates(
647                linked_chunk_id,
648                vec![
649                    // new chunk before the one that exists.
650                    Update::NewItemsChunk {
651                        previous: None,
652                        new: CId::new(7),
653                        next: Some(CId::new(42)),
654                    },
655                    Update::PushItems {
656                        at: Position::new(CId::new(7), 0),
657                        items: vec![event("brigand du jorat"), event("morbier")],
658                    },
659                ],
660            )
661            .await
662            .unwrap();
663
664            let previous_chunk =
665                self.load_previous_chunk(linked_chunk_id, CId::new(42)).await.unwrap();
666
667            assert_matches!(previous_chunk, Some(previous_chunk) => {
668                assert_eq!(previous_chunk.identifier, 7);
669                assert!(previous_chunk.previous.is_none());
670                assert_matches!(previous_chunk.next, Some(next) => {
671                    assert_eq!(next, 42);
672                });
673                assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
674                    assert_eq!(items.len(), 2);
675                    check_test_event(&items[0], "brigand du jorat");
676                    check_test_event(&items[1], "morbier");
677                });
678            });
679        }
680    }
681
682    async fn test_linked_chunk_incremental_loading(&self) {
683        let room_id = room_id!("!r0:matrix.org");
684        let linked_chunk_id = LinkedChunkId::Room(room_id);
685        let event = |msg: &str| make_test_event(room_id, msg);
686
687        // Load the last chunk, but none exists yet.
688        {
689            let (last_chunk, chunk_identifier_generator) =
690                self.load_last_chunk(linked_chunk_id).await.unwrap();
691
692            assert!(last_chunk.is_none());
693            assert_eq!(chunk_identifier_generator.current(), 0);
694        }
695
696        self.handle_linked_chunk_updates(
697            linked_chunk_id,
698            vec![
699                // new chunk for items
700                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
701                // new items on 0
702                Update::PushItems {
703                    at: Position::new(CId::new(0), 0),
704                    items: vec![event("a"), event("b")],
705                },
706                // new chunk for a gap
707                Update::NewGapChunk {
708                    previous: Some(CId::new(0)),
709                    new: CId::new(1),
710                    next: None,
711                    gap: Gap { token: "morbier".to_owned() },
712                },
713                // new chunk for items
714                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
715                // new items on 2
716                Update::PushItems {
717                    at: Position::new(CId::new(2), 0),
718                    items: vec![event("c"), event("d"), event("e")],
719                },
720            ],
721        )
722        .await
723        .unwrap();
724
725        // Load the last chunk.
726        let mut linked_chunk = {
727            let (last_chunk, chunk_identifier_generator) =
728                self.load_last_chunk(linked_chunk_id).await.unwrap();
729
730            assert_eq!(chunk_identifier_generator.current(), 2);
731
732            let linked_chunk = lazy_loader::from_last_chunk::<DEFAULT_CHUNK_CAPACITY, _, _>(
733                last_chunk,
734                chunk_identifier_generator,
735            )
736            .unwrap() // unwrap the `Result`
737            .unwrap(); // unwrap the `Option`
738
739            let mut rchunks = linked_chunk.rchunks();
740
741            // A unique chunk.
742            assert_matches!(rchunks.next(), Some(chunk) => {
743                assert_eq!(chunk.identifier(), 2);
744                assert_eq!(chunk.lazy_previous(), Some(CId::new(1)));
745
746                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
747                    assert_eq!(events.len(), 3);
748                    check_test_event(&events[0], "c");
749                    check_test_event(&events[1], "d");
750                    check_test_event(&events[2], "e");
751                });
752            });
753
754            assert!(rchunks.next().is_none());
755
756            linked_chunk
757        };
758
759        // Load the previous chunk: this is a gap.
760        {
761            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
762            let previous_chunk =
763                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap();
764
765            lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
766
767            let mut rchunks = linked_chunk.rchunks();
768
769            // The last chunk.
770            assert_matches!(rchunks.next(), Some(chunk) => {
771                assert_eq!(chunk.identifier(), 2);
772                assert!(chunk.lazy_previous().is_none());
773
774                // Already asserted, but let's be sure nothing breaks.
775                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
776                    assert_eq!(events.len(), 3);
777                    check_test_event(&events[0], "c");
778                    check_test_event(&events[1], "d");
779                    check_test_event(&events[2], "e");
780                });
781            });
782
783            // The new chunk.
784            assert_matches!(rchunks.next(), Some(chunk) => {
785                assert_eq!(chunk.identifier(), 1);
786                assert_eq!(chunk.lazy_previous(), Some(CId::new(0)));
787
788                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
789                    assert_eq!(gap.token, "morbier");
790                });
791            });
792
793            assert!(rchunks.next().is_none());
794        }
795
796        // Load the previous chunk: these are items.
797        {
798            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
799            let previous_chunk =
800                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap();
801
802            lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
803
804            let mut rchunks = linked_chunk.rchunks();
805
806            // The last chunk.
807            assert_matches!(rchunks.next(), Some(chunk) => {
808                assert_eq!(chunk.identifier(), 2);
809                assert!(chunk.lazy_previous().is_none());
810
811                // Already asserted, but let's be sure nothing breaks.
812                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
813                    assert_eq!(events.len(), 3);
814                    check_test_event(&events[0], "c");
815                    check_test_event(&events[1], "d");
816                    check_test_event(&events[2], "e");
817                });
818            });
819
820            // Its previous chunk.
821            assert_matches!(rchunks.next(), Some(chunk) => {
822                assert_eq!(chunk.identifier(), 1);
823                assert!(chunk.lazy_previous().is_none());
824
825                // Already asserted, but let's be sure nothing breaks.
826                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
827                    assert_eq!(gap.token, "morbier");
828                });
829            });
830
831            // The new chunk.
832            assert_matches!(rchunks.next(), Some(chunk) => {
833                assert_eq!(chunk.identifier(), 0);
834                assert!(chunk.lazy_previous().is_none());
835
836                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
837                    assert_eq!(events.len(), 2);
838                    check_test_event(&events[0], "a");
839                    check_test_event(&events[1], "b");
840                });
841            });
842
843            assert!(rchunks.next().is_none());
844        }
845
846        // Load the previous chunk: there is none.
847        {
848            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
849            let previous_chunk =
850                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap();
851
852            assert!(previous_chunk.is_none());
853        }
854
855        // One last check: a round of assert by using the forwards chunk iterator
856        // instead of the backwards chunk iterator.
857        {
858            let mut chunks = linked_chunk.chunks();
859
860            // The first chunk.
861            assert_matches!(chunks.next(), Some(chunk) => {
862                assert_eq!(chunk.identifier(), 0);
863                assert!(chunk.lazy_previous().is_none());
864
865                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
866                    assert_eq!(events.len(), 2);
867                    check_test_event(&events[0], "a");
868                    check_test_event(&events[1], "b");
869                });
870            });
871
872            // The second chunk.
873            assert_matches!(chunks.next(), Some(chunk) => {
874                assert_eq!(chunk.identifier(), 1);
875                assert!(chunk.lazy_previous().is_none());
876
877                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
878                    assert_eq!(gap.token, "morbier");
879                });
880            });
881
882            // The third and last chunk.
883            assert_matches!(chunks.next(), Some(chunk) => {
884                assert_eq!(chunk.identifier(), 2);
885                assert!(chunk.lazy_previous().is_none());
886
887                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
888                    assert_eq!(events.len(), 3);
889                    check_test_event(&events[0], "c");
890                    check_test_event(&events[1], "d");
891                    check_test_event(&events[2], "e");
892                });
893            });
894
895            assert!(chunks.next().is_none());
896        }
897    }
898
899    async fn test_linked_chunk_remove_chunk(&self) {
900        let room_id = &DEFAULT_TEST_ROOM_ID;
901        let linked_chunk_id = LinkedChunkId::Room(room_id);
902
903        self.handle_linked_chunk_updates(
904            linked_chunk_id,
905            vec![
906                Update::NewGapChunk {
907                    previous: None,
908                    new: CId::new(42),
909                    next: None,
910                    gap: Gap { token: "raclette".to_owned() },
911                },
912                Update::NewGapChunk {
913                    previous: Some(CId::new(42)),
914                    new: CId::new(43),
915                    next: None,
916                    gap: Gap { token: "fondue".to_owned() },
917                },
918                Update::NewGapChunk {
919                    previous: Some(CId::new(43)),
920                    new: CId::new(44),
921                    next: None,
922                    gap: Gap { token: "tartiflette".to_owned() },
923                },
924                Update::RemoveChunk(CId::new(43)),
925            ],
926        )
927        .await
928        .unwrap();
929
930        let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
931
932        assert_eq!(chunks.len(), 2);
933
934        // Chunks are ordered from smaller to bigger IDs.
935        let c = chunks.remove(0);
936        assert_eq!(c.identifier, CId::new(42));
937        assert_eq!(c.previous, None);
938        assert_eq!(c.next, Some(CId::new(44)));
939        assert_matches!(c.content, ChunkContent::Gap(gap) => {
940            assert_eq!(gap.token, "raclette");
941        });
942
943        let c = chunks.remove(0);
944        assert_eq!(c.identifier, CId::new(44));
945        assert_eq!(c.previous, Some(CId::new(42)));
946        assert_eq!(c.next, None);
947        assert_matches!(c.content, ChunkContent::Gap(gap) => {
948            assert_eq!(gap.token, "tartiflette");
949        });
950    }
951
952    async fn test_linked_chunk_replace_item(&self) {
953        let room_id = &DEFAULT_TEST_ROOM_ID;
954        let linked_chunk_id = LinkedChunkId::Room(room_id);
955        let event_id = event_id!("$world");
956
957        self.handle_linked_chunk_updates(
958            linked_chunk_id,
959            vec![
960                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
961                Update::PushItems {
962                    at: Position::new(CId::new(42), 0),
963                    items: vec![
964                        make_test_event(room_id, "hello"),
965                        make_test_event_with_event_id(room_id, "world", Some(event_id)),
966                    ],
967                },
968                Update::ReplaceItem {
969                    at: Position::new(CId::new(42), 1),
970                    item: make_test_event_with_event_id(room_id, "yolo", Some(event_id)),
971                },
972            ],
973        )
974        .await
975        .unwrap();
976
977        let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
978
979        assert_eq!(chunks.len(), 1);
980
981        let c = chunks.remove(0);
982        assert_eq!(c.identifier, CId::new(42));
983        assert_eq!(c.previous, None);
984        assert_eq!(c.next, None);
985        assert_matches!(c.content, ChunkContent::Items(events) => {
986            assert_eq!(events.len(), 2);
987            check_test_event(&events[0], "hello");
988            check_test_event(&events[1], "yolo");
989        });
990    }
991
992    async fn test_linked_chunk_remove_item(&self) {
993        let room_id = *DEFAULT_TEST_ROOM_ID;
994        let linked_chunk_id = LinkedChunkId::Room(room_id);
995
996        self.handle_linked_chunk_updates(
997            linked_chunk_id,
998            vec![
999                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1000                Update::PushItems {
1001                    at: Position::new(CId::new(42), 0),
1002                    items: vec![
1003                        make_test_event(room_id, "one"),
1004                        make_test_event(room_id, "two"),
1005                        make_test_event(room_id, "three"),
1006                        make_test_event(room_id, "four"),
1007                        make_test_event(room_id, "five"),
1008                        make_test_event(room_id, "six"),
1009                    ],
1010                },
1011                Update::RemoveItem { at: Position::new(CId::new(42), 2) /* "three" */ },
1012                // After removing an item, we need to ensure that the indices of all subsequent
1013                // items in the chunk have shifted down by one. We can ensure this by pushing
1014                // an item at the smallest index we expect to be unoccupied, and checking to see
1015                // whether the last item in the chunk was overwritten.
1016                //
1017                // For example, after removing the item at index 2, we should have 5 elements and
1018                // the smallest unoccupied index should be index 5. If we push an item to index 5,
1019                // it should not overwrite any existing elements - i.e., "six" - but should be
1020                // appended to the end of the chunk.
1021                Update::PushItems {
1022                    at: Position::new(CId::new(42), 5),
1023                    items: vec![make_test_event(room_id, "seven")],
1024                },
1025            ],
1026        )
1027        .await
1028        .unwrap();
1029
1030        let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
1031
1032        assert_eq!(chunks.len(), 1);
1033
1034        let c = chunks.remove(0);
1035        assert_eq!(c.identifier, CId::new(42));
1036        assert_eq!(c.previous, None);
1037        assert_eq!(c.next, None);
1038        assert_matches!(c.content, ChunkContent::Items(events) => {
1039            assert_eq!(events.len(), 6);
1040            check_test_event(&events[0], "one");
1041            check_test_event(&events[1], "two");
1042            check_test_event(&events[2], "four");
1043            check_test_event(&events[3], "five");
1044            check_test_event(&events[4], "six");
1045            check_test_event(&events[5], "seven");
1046        });
1047
1048        // The chunk metadata must agree on the number of items.
1049        let metas = self.load_all_chunks_metadata(linked_chunk_id).await.unwrap();
1050        assert_eq!(metas.len(), 1);
1051        assert_eq!(metas[0].num_items, 6);
1052    }
1053
1054    async fn test_linked_chunk_detach_last_items(&self) {
1055        let room_id = *DEFAULT_TEST_ROOM_ID;
1056        let linked_chunk_id = LinkedChunkId::Room(room_id);
1057
1058        self.handle_linked_chunk_updates(
1059            linked_chunk_id,
1060            vec![
1061                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1062                Update::PushItems {
1063                    at: Position::new(CId::new(42), 0),
1064                    items: vec![
1065                        make_test_event(room_id, "hello"),
1066                        make_test_event(room_id, "world"),
1067                        make_test_event(room_id, "howdy"),
1068                    ],
1069                },
1070                Update::DetachLastItems { at: Position::new(CId::new(42), 1) },
1071            ],
1072        )
1073        .await
1074        .unwrap();
1075
1076        let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
1077
1078        assert_eq!(chunks.len(), 1);
1079
1080        let c = chunks.remove(0);
1081        assert_eq!(c.identifier, CId::new(42));
1082        assert_eq!(c.previous, None);
1083        assert_eq!(c.next, None);
1084        assert_matches!(c.content, ChunkContent::Items(events) => {
1085            assert_eq!(events.len(), 1);
1086            check_test_event(&events[0], "hello");
1087        });
1088    }
1089
1090    async fn test_linked_chunk_start_end_reattach_items(&self) {
1091        let room_id = *DEFAULT_TEST_ROOM_ID;
1092        let linked_chunk_id = LinkedChunkId::Room(room_id);
1093
1094        // Same updates and checks as test_linked_chunk_push_items, but with extra
1095        // `StartReattachItems` and `EndReattachItems` updates, which must have no
1096        // effects.
1097        self.handle_linked_chunk_updates(
1098            linked_chunk_id,
1099            vec![
1100                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1101                Update::PushItems {
1102                    at: Position::new(CId::new(42), 0),
1103                    items: vec![
1104                        make_test_event(room_id, "hello"),
1105                        make_test_event(room_id, "world"),
1106                        make_test_event(room_id, "howdy"),
1107                    ],
1108                },
1109                Update::StartReattachItems,
1110                Update::EndReattachItems,
1111            ],
1112        )
1113        .await
1114        .unwrap();
1115
1116        let mut chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
1117
1118        assert_eq!(chunks.len(), 1);
1119
1120        let c = chunks.remove(0);
1121        assert_eq!(c.identifier, CId::new(42));
1122        assert_eq!(c.previous, None);
1123        assert_eq!(c.next, None);
1124        assert_matches!(c.content, ChunkContent::Items(events) => {
1125            assert_eq!(events.len(), 3);
1126            check_test_event(&events[0], "hello");
1127            check_test_event(&events[1], "world");
1128            check_test_event(&events[2], "howdy");
1129        });
1130    }
1131
1132    async fn test_linked_chunk_clear(&self) {
1133        let room_id = *DEFAULT_TEST_ROOM_ID;
1134        let linked_chunk_id = LinkedChunkId::Room(room_id);
1135        let event_0 = make_test_event(room_id, "hello");
1136        let event_1 = make_test_event(room_id, "world");
1137        let event_2 = make_test_event(room_id, "howdy");
1138
1139        self.handle_linked_chunk_updates(
1140            linked_chunk_id,
1141            vec![
1142                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1143                Update::NewGapChunk {
1144                    previous: Some(CId::new(42)),
1145                    new: CId::new(54),
1146                    next: None,
1147                    gap: Gap { token: "fondue".to_owned() },
1148                },
1149                Update::PushItems {
1150                    at: Position::new(CId::new(42), 0),
1151                    items: vec![event_0.clone(), event_1, event_2],
1152                },
1153                Update::Clear,
1154            ],
1155        )
1156        .await
1157        .unwrap();
1158
1159        let chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
1160        assert!(chunks.is_empty());
1161    }
1162
1163    async fn test_linked_chunk_clear_and_reinsert(&self) {
1164        let room_id = *DEFAULT_TEST_ROOM_ID;
1165        let linked_chunk_id = LinkedChunkId::Room(room_id);
1166        let event_0 = make_test_event(room_id, "hello");
1167        let event_1 = make_test_event(room_id, "world");
1168        let event_2 = make_test_event(room_id, "howdy");
1169
1170        self.handle_linked_chunk_updates(
1171            linked_chunk_id,
1172            vec![
1173                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1174                Update::NewGapChunk {
1175                    previous: Some(CId::new(42)),
1176                    new: CId::new(54),
1177                    next: None,
1178                    gap: Gap { token: "fondue".to_owned() },
1179                },
1180                Update::PushItems {
1181                    at: Position::new(CId::new(42), 0),
1182                    items: vec![event_0.clone(), event_1, event_2],
1183                },
1184                Update::Clear,
1185            ],
1186        )
1187        .await
1188        .unwrap();
1189
1190        let chunks = self.load_all_chunks(linked_chunk_id).await.unwrap();
1191        assert!(chunks.is_empty());
1192
1193        // It's okay to re-insert a past event.
1194        self.handle_linked_chunk_updates(
1195            linked_chunk_id,
1196            vec![
1197                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1198                Update::PushItems { at: Position::new(CId::new(42), 0), items: vec![event_0] },
1199            ],
1200        )
1201        .await
1202        .unwrap();
1203    }
1204
1205    async fn test_rebuild_empty_linked_chunk(&self) {
1206        // When I rebuild a linked chunk from an empty store, it's empty.
1207        let linked_chunk = lazy_loader::from_all_chunks::<3, _, _>(
1208            self.load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)).await.unwrap(),
1209        )
1210        .unwrap();
1211        assert!(linked_chunk.is_none());
1212    }
1213
1214    async fn test_linked_chunk_multiple_rooms(&self) {
1215        let room1 = room_id!("!realcheeselovers:raclette.fr");
1216        let linked_chunk_id1 = LinkedChunkId::Room(room1);
1217        let room2 = room_id!("!realcheeselovers:fondue.ch");
1218        let linked_chunk_id2 = LinkedChunkId::Room(room2);
1219
1220        // Check that applying updates to one room doesn't affect the others.
1221        // Use the same chunk identifier in both rooms to battle-test search.
1222
1223        self.handle_linked_chunk_updates(
1224            linked_chunk_id1,
1225            vec![
1226                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1227                Update::PushItems {
1228                    at: Position::new(CId::new(42), 0),
1229                    items: vec![
1230                        make_test_event(room1, "best cheese is raclette"),
1231                        make_test_event(room1, "obviously"),
1232                    ],
1233                },
1234            ],
1235        )
1236        .await
1237        .unwrap();
1238
1239        self.handle_linked_chunk_updates(
1240            linked_chunk_id2,
1241            vec![
1242                Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1243                Update::PushItems {
1244                    at: Position::new(CId::new(42), 0),
1245                    items: vec![make_test_event(room1, "beaufort is the best")],
1246                },
1247            ],
1248        )
1249        .await
1250        .unwrap();
1251
1252        // Check chunks from room 1.
1253        let mut chunks_room1 = self.load_all_chunks(linked_chunk_id1).await.unwrap();
1254        assert_eq!(chunks_room1.len(), 1);
1255
1256        let c = chunks_room1.remove(0);
1257        assert_matches!(c.content, ChunkContent::Items(events) => {
1258            assert_eq!(events.len(), 2);
1259            check_test_event(&events[0], "best cheese is raclette");
1260            check_test_event(&events[1], "obviously");
1261        });
1262
1263        // Check chunks from room 2.
1264        let mut chunks_room2 = self.load_all_chunks(linked_chunk_id2).await.unwrap();
1265        assert_eq!(chunks_room2.len(), 1);
1266
1267        let c = chunks_room2.remove(0);
1268        assert_matches!(c.content, ChunkContent::Items(events) => {
1269            assert_eq!(events.len(), 1);
1270            check_test_event(&events[0], "beaufort is the best");
1271        });
1272    }
1273
1274    async fn test_load_and_update_thread_info(&self) {
1275        let room_id = room_id!("!r0");
1276        let thread_id = event_id!("$t0");
1277
1278        // Load for the first time.
1279        //
1280        // We must get an empty `ThreadInfo`.
1281        let ThreadInfo { read_receipts } = self.load_thread_info(room_id, thread_id).await.unwrap();
1282        let ReadReceipts { num_unread, num_notifications, num_mentions, latest_active, pending } =
1283            read_receipts;
1284        assert_eq!(num_unread, 0);
1285        assert_eq!(num_notifications, 0);
1286        assert_eq!(num_mentions, 0);
1287        assert!(latest_active.is_none());
1288        assert!(pending.is_empty());
1289
1290        // Load for the second time.
1291        //
1292        // We must get the same empty `ThreadInfo`.
1293        let mut thread_info = self.load_thread_info(room_id, thread_id).await.unwrap();
1294        let ThreadInfo { read_receipts } = &thread_info;
1295        let ReadReceipts { num_unread, num_notifications, num_mentions, latest_active, pending } =
1296            read_receipts;
1297        assert_eq!(*num_unread, 0);
1298        assert_eq!(*num_notifications, 0);
1299        assert_eq!(*num_mentions, 0);
1300        assert!(latest_active.is_none());
1301        assert!(pending.is_empty());
1302
1303        // Update the `ThreadInfo`.
1304        thread_info.read_receipts.num_unread = 1;
1305        thread_info.read_receipts.num_notifications = 2;
1306        self.update_thread_info(room_id, thread_id, &thread_info).await.unwrap();
1307
1308        // Load for the third time.
1309        //
1310        // We must get the updated `ThreadInfo`.
1311        let ThreadInfo { read_receipts } = self.load_thread_info(room_id, thread_id).await.unwrap();
1312        let ReadReceipts { num_unread, num_notifications, num_mentions, latest_active, pending } =
1313            read_receipts;
1314        assert_eq!(num_unread, 1);
1315        assert_eq!(num_notifications, 2);
1316        assert_eq!(num_mentions, 0);
1317        assert!(latest_active.is_none());
1318        assert!(pending.is_empty());
1319    }
1320
1321    async fn test_clear_all_events(&self) {
1322        let linked_chunk_ids = [
1323            LinkedChunkId::Room(room_id!("!r0")),
1324            LinkedChunkId::Thread(room_id!("!r1"), event_id!("$r1_thread_root0")),
1325            LinkedChunkId::PinnedEvents(room_id!("!r2")),
1326            // `LinkedChunkId::EventFocused` are not persisted in the database, no need to test it.
1327        ];
1328
1329        // Create data for each `LinkedChunkId`.
1330        for linked_chunk_id in linked_chunk_ids {
1331            let room_id = linked_chunk_id.room_id();
1332
1333            // Assume the thread has been “remembered” correctly (this is done in
1334            // `ThreadEventCacheState::new`).
1335            if let LinkedChunkId::Thread(_, thread_id) = &linked_chunk_id {
1336                self.load_thread_info(room_id, thread_id).await.unwrap();
1337            }
1338
1339            self.handle_linked_chunk_updates(
1340                linked_chunk_id,
1341                vec![
1342                    // New chunk
1343                    Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1344                    // New items on 0.
1345                    Update::PushItems {
1346                        at: Position::new(CId::new(0), 0),
1347                        items: vec![
1348                            make_test_event(room_id, "foo"),
1349                            make_test_event(room_id, "bar"),
1350                            make_test_event(room_id, "baz"),
1351                        ],
1352                    },
1353                ],
1354            )
1355            .await
1356            .unwrap();
1357
1358            // Linked chunks all exist!
1359            assert!(
1360                lazy_loader::from_all_chunks::<3, _, _>(
1361                    self.load_all_chunks(linked_chunk_id).await.unwrap()
1362                )
1363                .unwrap()
1364                .is_some()
1365            );
1366
1367            // Events exist!
1368            assert_eq!(self.get_room_events(room_id, None, None).await.unwrap().len(), 3);
1369        }
1370
1371        // Clear all events!
1372        self.clear_all_events(None).await.unwrap();
1373
1374        // Check all data have been removed, forever.
1375        for linked_chunk_id in linked_chunk_ids {
1376            let room_id = linked_chunk_id.room_id();
1377
1378            // No more linked chunks!
1379            assert!(
1380                lazy_loader::from_all_chunks::<3, _, _>(
1381                    self.load_all_chunks(linked_chunk_id).await.unwrap()
1382                )
1383                .unwrap()
1384                .is_none()
1385            );
1386
1387            // No more events!
1388            assert!(self.get_room_events(room_id, None, None).await.unwrap().is_empty());
1389        }
1390    }
1391
1392    async fn test_clear_all_events_for_specific_room(&self) {
1393        let linked_chunk_ids_for_room_0 = [
1394            LinkedChunkId::Room(room_id!("!r0")),
1395            LinkedChunkId::Thread(room_id!("!r0"), event_id!("$r0_thread_root")),
1396            LinkedChunkId::PinnedEvents(room_id!("!r0")),
1397        ];
1398        let linked_chunk_ids_for_room_1 = [
1399            LinkedChunkId::Room(room_id!("!r1")),
1400            LinkedChunkId::Thread(room_id!("!r1"), event_id!("$r1_thread_root")),
1401            LinkedChunkId::PinnedEvents(room_id!("!r1")),
1402        ];
1403        let linked_chunk_ids_for_room_2 = [
1404            LinkedChunkId::Room(room_id!("!r2")),
1405            LinkedChunkId::Thread(room_id!("!r2"), event_id!("$r2_thread_root")),
1406            LinkedChunkId::PinnedEvents(room_id!("!r2")),
1407        ];
1408
1409        // Create data for each `LinkedChunkId`.
1410        for linked_chunk_id in linked_chunk_ids_for_room_0
1411            .iter()
1412            .chain(&linked_chunk_ids_for_room_1)
1413            .chain(&linked_chunk_ids_for_room_2)
1414        {
1415            let room_id = linked_chunk_id.room_id();
1416
1417            // Assume the thread has been “remembered” correctly (this is done in
1418            // `ThreadEventCacheState::new`).
1419            if let LinkedChunkId::Thread(_, thread_id) = &linked_chunk_id {
1420                self.load_thread_info(room_id, thread_id).await.unwrap();
1421            }
1422
1423            self.handle_linked_chunk_updates(
1424                *linked_chunk_id,
1425                vec![
1426                    // New chunk
1427                    Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1428                    // New items on 0.
1429                    Update::PushItems {
1430                        at: Position::new(CId::new(0), 0),
1431                        items: vec![
1432                            make_test_event(room_id, "foo"),
1433                            make_test_event(room_id, "bar"),
1434                            make_test_event(room_id, "baz"),
1435                        ],
1436                    },
1437                ],
1438            )
1439            .await
1440            .unwrap();
1441
1442            // Linked chunks all exist!
1443            assert!(
1444                lazy_loader::from_all_chunks::<3, _, _>(
1445                    self.load_all_chunks(*linked_chunk_id).await.unwrap()
1446                )
1447                .unwrap()
1448                .is_some()
1449            );
1450
1451            // Events exist!
1452            assert_eq!(self.get_room_events(room_id, None, None).await.unwrap().len(), 3);
1453        }
1454
1455        // Clear all events for room 1 **ONLY**!
1456        self.clear_all_events(Some(linked_chunk_ids_for_room_1[0].room_id())).await.unwrap();
1457
1458        // Check all data have been removed for room 1 **ONLY**, forever.
1459        for linked_chunk_id in linked_chunk_ids_for_room_1 {
1460            let room_id = linked_chunk_id.room_id();
1461
1462            // No more linked chunks!
1463            assert!(
1464                lazy_loader::from_all_chunks::<3, _, _>(
1465                    self.load_all_chunks(linked_chunk_id).await.unwrap()
1466                )
1467                .unwrap()
1468                .is_none()
1469            );
1470
1471            // No more events!
1472            assert!(self.get_room_events(room_id, None, None).await.unwrap().is_empty());
1473        }
1474
1475        // Check all the other data are untouched.
1476        for linked_chunk_id in
1477            linked_chunk_ids_for_room_0.iter().chain(&linked_chunk_ids_for_room_2)
1478        {
1479            let room_id = linked_chunk_id.room_id();
1480
1481            // Linked chunks all exist!
1482            assert!(
1483                lazy_loader::from_all_chunks::<3, _, _>(
1484                    self.load_all_chunks(*linked_chunk_id).await.unwrap()
1485                )
1486                .unwrap()
1487                .is_some()
1488            );
1489
1490            // Events exist!
1491            assert_eq!(self.get_room_events(room_id, None, None).await.unwrap().len(), 3);
1492        }
1493    }
1494
1495    async fn test_filter_duplicated_events(&self) {
1496        let room_id = room_id!("!r0:matrix.org");
1497        let linked_chunk_id = LinkedChunkId::Room(room_id);
1498        let another_room_id = room_id!("!r1:matrix.org");
1499        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
1500        let event = |msg: &str| make_test_event(room_id, msg);
1501
1502        let event_comte = event("comté");
1503        let event_brigand = event("brigand du jorat");
1504        let event_raclette = event("raclette");
1505        let event_morbier = event("morbier");
1506        let event_gruyere = event("gruyère");
1507        let event_tome = event("tome");
1508        let event_mont_dor = event("mont d'or");
1509
1510        self.handle_linked_chunk_updates(
1511            linked_chunk_id,
1512            vec![
1513                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1514                Update::PushItems {
1515                    at: Position::new(CId::new(0), 0),
1516                    items: vec![event_comte.clone(), event_brigand.clone()],
1517                },
1518                Update::NewGapChunk {
1519                    previous: Some(CId::new(0)),
1520                    new: CId::new(1),
1521                    next: None,
1522                    gap: Gap { token: "brillat-savarin".to_owned() },
1523                },
1524                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
1525                Update::PushItems {
1526                    at: Position::new(CId::new(2), 0),
1527                    items: vec![event_morbier.clone(), event_mont_dor.clone()],
1528                },
1529            ],
1530        )
1531        .await
1532        .unwrap();
1533
1534        // Add other events in another room, to ensure filtering take the `room_id` into
1535        // account.
1536        self.handle_linked_chunk_updates(
1537            another_linked_chunk_id,
1538            vec![
1539                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1540                Update::PushItems {
1541                    at: Position::new(CId::new(0), 0),
1542                    items: vec![event_tome.clone()],
1543                },
1544            ],
1545        )
1546        .await
1547        .unwrap();
1548
1549        let duplicated_events = BTreeMap::from_iter(
1550            self.filter_duplicated_events(
1551                linked_chunk_id,
1552                vec![
1553                    event_comte.event_id().unwrap().to_owned(),
1554                    event_raclette.event_id().unwrap().to_owned(),
1555                    event_morbier.event_id().unwrap().to_owned(),
1556                    event_gruyere.event_id().unwrap().to_owned(),
1557                    event_tome.event_id().unwrap().to_owned(),
1558                    event_mont_dor.event_id().unwrap().to_owned(),
1559                ],
1560            )
1561            .await
1562            .unwrap(),
1563        );
1564
1565        assert_eq!(duplicated_events.len(), 3);
1566
1567        assert_eq!(
1568            *duplicated_events.get(event_comte.event_id().unwrap()).unwrap(),
1569            Position::new(CId::new(0), 0)
1570        );
1571        assert_eq!(
1572            *duplicated_events.get(event_morbier.event_id().unwrap()).unwrap(),
1573            Position::new(CId::new(2), 0)
1574        );
1575        assert_eq!(
1576            *duplicated_events.get(event_mont_dor.event_id().unwrap()).unwrap(),
1577            Position::new(CId::new(2), 1)
1578        );
1579    }
1580
1581    async fn test_filter_duplicate_events_no_events(&self) {
1582        let room_id = *DEFAULT_TEST_ROOM_ID;
1583        let linked_chunk_id = LinkedChunkId::Room(room_id);
1584        let duplicates = self.filter_duplicated_events(linked_chunk_id, Vec::new()).await.unwrap();
1585        assert!(duplicates.is_empty());
1586    }
1587
1588    async fn test_find_event(&self) {
1589        let room_id = room_id!("!r0:matrix.org");
1590        let another_room_id = room_id!("!r1:matrix.org");
1591        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
1592        let event = |msg: &str| make_test_event(room_id, msg);
1593
1594        let event_comte = event("comté");
1595        let event_gruyere = event("gruyère");
1596
1597        // Add one event in one room.
1598        self.handle_linked_chunk_updates(
1599            LinkedChunkId::Room(room_id),
1600            vec![
1601                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1602                Update::PushItems {
1603                    at: Position::new(CId::new(0), 0),
1604                    items: vec![event_comte.clone()],
1605                },
1606            ],
1607        )
1608        .await
1609        .unwrap();
1610
1611        // Add another event in another room.
1612        self.handle_linked_chunk_updates(
1613            another_linked_chunk_id,
1614            vec![
1615                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1616                Update::PushItems {
1617                    at: Position::new(CId::new(0), 0),
1618                    items: vec![event_gruyere.clone()],
1619                },
1620            ],
1621        )
1622        .await
1623        .unwrap();
1624
1625        // Now let's find the event.
1626        let event = self
1627            .find_event(room_id, event_comte.event_id().unwrap())
1628            .await
1629            .expect("failed to query for finding an event")
1630            .expect("failed to find an event");
1631
1632        assert_eq!(event.event_id(), event_comte.event_id());
1633
1634        // Now let's try to find an event that exists, but not in the expected room.
1635        assert!(
1636            self.find_event(room_id, event_gruyere.event_id().unwrap())
1637                .await
1638                .expect("failed to query for finding an event")
1639                .is_none()
1640        );
1641
1642        // Clearing the rooms also clears the event's storage.
1643        self.clear_all_events(None).await.expect("failed to clear all rooms chunks");
1644        assert!(
1645            self.find_event(room_id, event_comte.event_id().unwrap())
1646                .await
1647                .expect("failed to query for finding an event")
1648                .is_none()
1649        );
1650    }
1651
1652    async fn test_find_event_when_event_in_room_and_thread(&self) {
1653        let room_id = *DEFAULT_TEST_ROOM_ID;
1654        let thread_root = event_id!("$thread_root");
1655
1656        // Create an event that will be only be inserted into the room
1657        let room_event_id = event_id!("$room_event");
1658        let room_event = make_test_event_with_event_id(room_id, "room event", Some(room_event_id));
1659
1660        // Create an event that will only be inserted into the thread
1661        let thread_event_id = event_id!("$thread_event");
1662        let thread_event =
1663            make_test_event_with_event_id(room_id, "thread event", Some(thread_event_id));
1664
1665        // Create an event that will be inserted into both the room and thread linked
1666        // chunks.
1667        let room_and_thread_event_id = event_id!("$room_and_thread");
1668        let room_and_thread_event = make_test_event_with_event_id(
1669            room_id,
1670            "room and thread",
1671            Some(room_and_thread_event_id),
1672        );
1673
1674        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
1675        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
1676
1677        // Insert the relevant events into the room's linked chunk.
1678        self.handle_linked_chunk_updates(
1679            room_linked_chunk_id,
1680            vec![
1681                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
1682                Update::PushItems {
1683                    at: Position::new(CId::new(1), 0),
1684                    items: vec![room_event, room_and_thread_event.clone()],
1685                },
1686            ],
1687        )
1688        .await
1689        .unwrap();
1690
1691        // Insert the relevant events into the thread's linked chunk.
1692        self.handle_linked_chunk_updates(
1693            thread_linked_chunk_id,
1694            vec![
1695                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
1696                Update::PushItems {
1697                    at: Position::new(CId::new(1), 0),
1698                    items: vec![thread_event, room_and_thread_event],
1699                },
1700            ],
1701        )
1702        .await
1703        .unwrap();
1704
1705        // Verify that event that is only in the room can be retrieved
1706        assert_matches!(self.find_event(room_id, room_event_id).await, Ok(Some(event)) => {
1707            assert_eq!(event.event_id().unwrap(), room_event_id)
1708        });
1709
1710        // Verify that the event that is only in the thread can be retrieved
1711        assert_matches!(self.find_event(room_id, thread_event_id).await, Ok(Some(event)) => {
1712            assert_eq!(event.event_id().unwrap(), thread_event_id)
1713        });
1714
1715        // Verify that event that is in both room and thread can be retrieved
1716        assert_matches!(self.find_event(room_id, room_and_thread_event_id).await, Ok(Some(event)) => {
1717            assert_eq!(event.event_id().unwrap(), room_and_thread_event_id);
1718        });
1719    }
1720
1721    async fn test_find_event_relations(&self) {
1722        let room_id = room_id!("!r0:matrix.org");
1723        let another_room_id = room_id!("!r1:matrix.org");
1724
1725        let f = EventFactory::new().room(room_id).sender(*ALICE);
1726
1727        // Create event and related events for the first room.
1728        let eid1 = event_id!("$event1:matrix.org");
1729        let e1 = f.text_msg("comter").event_id(eid1).into_event();
1730
1731        let edit_eid1 = event_id!("$edit_event1:matrix.org");
1732        let edit_e1 = f
1733            .text_msg("* comté")
1734            .event_id(edit_eid1)
1735            .edit(eid1, RoomMessageEventContentWithoutRelation::text_plain("comté"))
1736            .into_event();
1737
1738        let reaction_eid1 = event_id!("$reaction_event1:matrix.org");
1739        let reaction_e1 = f.reaction(eid1, "👍").event_id(reaction_eid1).into_event();
1740
1741        let eid2 = event_id!("$event2:matrix.org");
1742        let e2 = f.text_msg("galette saucisse").event_id(eid2).into_event();
1743
1744        // Create events for the second room.
1745        let f = f.room(another_room_id);
1746
1747        let eid3 = event_id!("$event3:matrix.org");
1748        let e3 = f.text_msg("gruyère").event_id(eid3).into_event();
1749
1750        let reaction_eid3 = event_id!("$reaction_event3:matrix.org");
1751        let reaction_e3 = f.reaction(eid3, "👍").event_id(reaction_eid3).into_event();
1752
1753        // Save All The Things!
1754        self.save_event(room_id, e1).await.unwrap();
1755        self.save_event(room_id, edit_e1).await.unwrap();
1756        self.save_event(room_id, reaction_e1.clone()).await.unwrap();
1757        self.save_event(room_id, e2).await.unwrap();
1758        self.save_event(another_room_id, e3).await.unwrap();
1759        self.save_event(another_room_id, reaction_e3).await.unwrap();
1760
1761        // Finding relations without a filter returns all of them.
1762        let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
1763        assert_eq!(relations.len(), 2);
1764        // The position is `None` for items outside the linked chunk.
1765        assert!(
1766            relations.iter().any(|(ev, pos)| ev.event_id() == Some(edit_eid1) && pos.is_none())
1767        );
1768        assert!(
1769            relations.iter().any(|(ev, pos)| ev.event_id() == Some(reaction_eid1) && pos.is_none())
1770        );
1771
1772        // Finding relations with a filter only returns a subset.
1773        let relations = self
1774            .find_event_relations(room_id, eid1, Some(&[RelationType::Replacement]))
1775            .await
1776            .unwrap();
1777        assert_eq!(relations.len(), 1);
1778        assert_eq!(relations[0].0.event_id(), Some(edit_eid1));
1779
1780        let relations = self
1781            .find_event_relations(
1782                room_id,
1783                eid1,
1784                Some(&[RelationType::Replacement, RelationType::Annotation]),
1785            )
1786            .await
1787            .unwrap();
1788        assert_eq!(relations.len(), 2);
1789        assert!(relations.iter().any(|r| r.0.event_id() == Some(edit_eid1)));
1790        assert!(relations.iter().any(|r| r.0.event_id() == Some(reaction_eid1)));
1791
1792        // We can't find relations using the wrong room.
1793        let relations = self
1794            .find_event_relations(another_room_id, eid1, Some(&[RelationType::Replacement]))
1795            .await
1796            .unwrap();
1797        assert!(relations.is_empty());
1798
1799        // But if an event exists in the linked chunk, we may have its position when
1800        // it's found as a relationship.
1801
1802        // Add reaction_e1 to the room's linked chunk.
1803        self.handle_linked_chunk_updates(
1804            LinkedChunkId::Room(room_id),
1805            vec![
1806                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1807                Update::PushItems { at: Position::new(CId::new(0), 0), items: vec![reaction_e1] },
1808            ],
1809        )
1810        .await
1811        .unwrap();
1812
1813        // When looking for aggregations to e1, we should have the position for
1814        // reaction_e1.
1815        let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
1816
1817        // The position is set for `reaction_eid1` now.
1818        assert!(relations.iter().any(|(ev, pos)| {
1819            ev.event_id() == Some(reaction_eid1) && *pos == Some(Position::new(CId::new(0), 0))
1820        }));
1821
1822        // But it's still not set for the other related events.
1823        assert!(
1824            relations.iter().any(|(ev, pos)| ev.event_id() == Some(edit_eid1) && pos.is_none())
1825        );
1826    }
1827
1828    async fn test_find_event_relations_when_event_in_room_and_thread(&self) {
1829        let room_id = *DEFAULT_TEST_ROOM_ID;
1830        let thread_root = event_id!("$thread_root");
1831
1832        // Create an event that will inserted into both the room and thread linked
1833        // chunks.
1834        let event_id = event_id!("$event");
1835        let event = make_test_event_with_event_id(room_id, "event", Some(event_id));
1836
1837        // Create an event that will only be inserted into the thread in order to help
1838        // distinguish between the room and thread linked chunks.
1839        let extra_thread_event_id = event_id!("$extra_thread_event");
1840        let extra_thread_event = make_test_event_with_event_id(
1841            room_id,
1842            "extra thread event",
1843            Some(extra_thread_event_id),
1844        );
1845
1846        // Create a reaction that will only be inserted into the room
1847        let room_reaction_id = event_id!("$room_reaction");
1848        let room_reaction = EventFactory::new()
1849            .room(room_id)
1850            .sender(*ALICE)
1851            .reaction(event_id, "room")
1852            .event_id(room_reaction_id)
1853            .into_event();
1854
1855        // Create a reaction that will only be inserted into the thread
1856        let thread_reaction_id = event_id!("$thread_reaction");
1857        let thread_reaction = EventFactory::new()
1858            .room(room_id)
1859            .sender(*ALICE)
1860            .reaction(event_id, "thread")
1861            .event_id(thread_reaction_id)
1862            .into_event();
1863
1864        // Create a reaction that will be inserted into both the room and thread linked
1865        // chunks.
1866        let room_and_thread_reaction_id = event_id!("$room_and_thread_reaction");
1867        let room_and_thread_reaction = EventFactory::new()
1868            .room(room_id)
1869            .sender(*ALICE)
1870            .reaction(event_id, "room and thread")
1871            .event_id(room_and_thread_reaction_id)
1872            .into_event();
1873
1874        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
1875        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
1876
1877        // Insert the relevant events into the room's linked chunk.
1878        self.handle_linked_chunk_updates(
1879            room_linked_chunk_id,
1880            vec![
1881                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
1882                Update::PushItems {
1883                    at: Position::new(CId::new(1), 0),
1884                    items: vec![event.clone(), room_reaction, room_and_thread_reaction.clone()],
1885                },
1886            ],
1887        )
1888        .await
1889        .unwrap();
1890
1891        // Insert the relevant events into the thread's linked chunk.
1892        self.handle_linked_chunk_updates(
1893            thread_linked_chunk_id,
1894            vec![
1895                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
1896                Update::PushItems {
1897                    at: Position::new(CId::new(1), 0),
1898                    items: vec![
1899                        event.clone(),
1900                        extra_thread_event,
1901                        thread_reaction,
1902                        room_and_thread_reaction,
1903                    ],
1904                },
1905            ],
1906        )
1907        .await
1908        .unwrap();
1909
1910        // Verify that only related events from the room are returned
1911        assert_matches!(self.find_event_relations(room_id, event_id, None).await, Ok(relations) => {
1912            assert_eq!(relations.len(), 3);
1913            // Verify that room reaction is in the list and associated with its
1914            // position in the room linked chunk.
1915            let room_relation = relations
1916                .iter()
1917                .find(|relation| relation.0.event_id().unwrap() == room_reaction_id)
1918                .unwrap();
1919            assert_matches!(room_relation, (_, Some(position)) => {
1920                assert_eq!(*position, Position::new(CId::new(1), 1));
1921            });
1922
1923            // Verify that thread reaction is in the list and not associated with a
1924            // position, as all positions are provided for the room linked chunk.
1925            let thread_relation = relations
1926                .iter()
1927                .find(|relation| relation.0.event_id().unwrap() == thread_reaction_id)
1928                .unwrap();
1929            assert_matches!(thread_relation, (_, None));
1930
1931            // Verify that room and thread reaction is in the list and associated
1932            // with its position in the room linked chunk, not the thread linked chunk.
1933            let room_and_thread_relation = relations
1934                .iter()
1935                .find(|relation| relation.0.event_id().unwrap() == room_and_thread_reaction_id)
1936                .unwrap();
1937            assert_matches!(room_and_thread_relation, (_, Some(position)) => {
1938                assert_eq!(*position, Position::new(CId::new(1), 2));
1939            });
1940        });
1941    }
1942
1943    async fn test_get_room_events(&self) {
1944        let room_id = room_id!("!r0:matrix.org");
1945        let another_room_id = room_id!("!r1:matrix.org");
1946        let linked_chunk_id = LinkedChunkId::Room(room_id);
1947        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
1948        let event = |msg: &str| make_test_event(room_id, msg);
1949
1950        let event_comte = event("comté");
1951        let event_gruyere = event("gruyère");
1952        let event_stilton = event("stilton");
1953
1954        // Add one event in one room.
1955        self.handle_linked_chunk_updates(
1956            linked_chunk_id,
1957            vec![
1958                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1959                Update::PushItems {
1960                    at: Position::new(CId::new(0), 0),
1961                    items: vec![event_comte.clone(), event_gruyere.clone()],
1962                },
1963            ],
1964        )
1965        .await
1966        .unwrap();
1967
1968        // Add an event in a different room.
1969        self.handle_linked_chunk_updates(
1970            another_linked_chunk_id,
1971            vec![
1972                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1973                Update::PushItems {
1974                    at: Position::new(CId::new(0), 0),
1975                    items: vec![event_stilton.clone()],
1976                },
1977            ],
1978        )
1979        .await
1980        .unwrap();
1981
1982        // Now let's find the events.
1983        let events = self
1984            .get_room_events(room_id, None, None)
1985            .await
1986            .expect("failed to query for room events");
1987
1988        assert_eq!(events.len(), 2);
1989
1990        let got_ids: Vec<_> =
1991            events.into_iter().map(|ev| ev.event_id().map(ToOwned::to_owned)).collect();
1992        let expected_ids = vec![
1993            event_comte.event_id().map(ToOwned::to_owned),
1994            event_gruyere.event_id().map(ToOwned::to_owned),
1995        ];
1996
1997        for expected in expected_ids {
1998            assert!(
1999                got_ids.contains(&expected),
2000                "Expected event {expected:?} not in got events: {got_ids:?}."
2001            );
2002        }
2003    }
2004
2005    async fn test_get_room_events_filtered(&self) {
2006        macro_rules! assert_expected_events {
2007            ($events:expr, [$($item:expr),* $(,)?]) => {{
2008                let got_ids: BTreeSet<_> = $events.into_iter().map(|ev| ev.event_id().map(ToOwned::to_owned)).flatten().collect();
2009                let expected_ids = BTreeSet::from([$($item.event_id().unwrap().to_owned()),*]);
2010
2011                assert_eq!(got_ids, expected_ids);
2012            }};
2013        }
2014
2015        let room_id = room_id!("!r0:matrix.org");
2016        let linked_chunk_id = LinkedChunkId::Room(room_id);
2017        let another_room_id = room_id!("!r1:matrix.org");
2018        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
2019
2020        let event = |session_id: &str| make_encrypted_test_event(room_id, session_id);
2021
2022        let first_event = event("session_1");
2023        let second_event = event("session_2");
2024        let third_event = event("session_3");
2025        let fourth_event = make_test_event(room_id, "It's a secret to everybody");
2026
2027        // Add one event in one room.
2028        self.handle_linked_chunk_updates(
2029            linked_chunk_id,
2030            vec![
2031                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2032                Update::PushItems {
2033                    at: Position::new(CId::new(0), 0),
2034                    items: vec![first_event.clone(), second_event.clone(), fourth_event.clone()],
2035                },
2036            ],
2037        )
2038        .await
2039        .unwrap();
2040
2041        // Add an event in a different room.
2042        self.handle_linked_chunk_updates(
2043            another_linked_chunk_id,
2044            vec![
2045                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2046                Update::PushItems {
2047                    at: Position::new(CId::new(0), 0),
2048                    items: vec![third_event.clone()],
2049                },
2050            ],
2051        )
2052        .await
2053        .unwrap();
2054
2055        // Now let's find all the encrypted events of the first room.
2056        let events = self
2057            .get_room_events(room_id, Some("m.room.encrypted"), None)
2058            .await
2059            .expect("failed to query for room events");
2060
2061        assert_eq!(events.len(), 2);
2062        assert_expected_events!(events, [first_event, second_event]);
2063
2064        // Now let's find all the encrypted events which were encrypted using the first
2065        // session ID.
2066        let events = self
2067            .get_room_events(room_id, Some("m.room.encrypted"), Some("session_1"))
2068            .await
2069            .expect("failed to query for room events");
2070
2071        assert_eq!(events.len(), 1);
2072        assert_expected_events!(events, [first_event]);
2073    }
2074
2075    async fn test_get_room_events_with_event_in_room_and_thread(&self) {
2076        let room_id = *DEFAULT_TEST_ROOM_ID;
2077        let thread_root = event_id!("$thread_root");
2078
2079        // Create an event that will be only be inserted into the room
2080        let room_event_id = event_id!("$room_event");
2081        let room_event = make_test_event_with_event_id(room_id, "room event", Some(room_event_id));
2082
2083        // Create an event that will only be inserted into the thread. This may not be a
2084        // sensible operation in practice, as threads seem to always exist in a
2085        // room, but let's test it anyway.
2086        let thread_event_id = event_id!("$thread_event");
2087        let thread_event =
2088            make_test_event_with_event_id(room_id, "thread event", Some(thread_event_id));
2089
2090        // Create an event that will be inserted into both the room and thread linked
2091        // chunks.
2092        let room_and_thread_event_id = event_id!("$room_and_thread");
2093        let room_and_thread_event = make_test_event_with_event_id(
2094            room_id,
2095            "room and thread",
2096            Some(room_and_thread_event_id),
2097        );
2098
2099        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
2100        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
2101
2102        // Insert the relevant events into the room's linked chunk.
2103        self.handle_linked_chunk_updates(
2104            room_linked_chunk_id,
2105            vec![
2106                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2107                Update::PushItems {
2108                    at: Position::new(CId::new(1), 0),
2109                    items: vec![room_event, room_and_thread_event.clone()],
2110                },
2111            ],
2112        )
2113        .await
2114        .unwrap();
2115
2116        // Insert the relevant events into the thread's linked chunk.
2117        self.handle_linked_chunk_updates(
2118            thread_linked_chunk_id,
2119            vec![
2120                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2121                Update::PushItems {
2122                    at: Position::new(CId::new(1), 0),
2123                    items: vec![thread_event, room_and_thread_event],
2124                },
2125            ],
2126        )
2127        .await
2128        .unwrap();
2129
2130        // Verify that all events can be retrieved and none are duplicated in the
2131        // returned list.
2132        let expected_event_ids =
2133            BTreeSet::from([room_event_id, thread_event_id, room_and_thread_event_id]);
2134        assert_matches!(self.get_room_events(room_id, None, None).await, Ok(events) => {
2135            assert_eq!(events.len(), 3);
2136            assert!(events.iter().all(|event| {
2137                expected_event_ids.contains(event.event_id().unwrap())
2138            }));
2139        });
2140    }
2141
2142    async fn test_save_event(&self) {
2143        let room_id = room_id!("!r0:matrix.org");
2144        let another_room_id = room_id!("!r1:matrix.org");
2145
2146        let event = |msg: &str| make_test_event(room_id, msg);
2147        let event_comte = event("comté");
2148        let event_gruyere = event("gruyère");
2149
2150        // Add one event in one room.
2151        self.save_event(room_id, event_comte.clone()).await.unwrap();
2152
2153        // Add another event in another room.
2154        self.save_event(another_room_id, event_gruyere.clone()).await.unwrap();
2155
2156        // Events can be found, when searched in their own rooms.
2157        let event = self
2158            .find_event(room_id, event_comte.event_id().unwrap())
2159            .await
2160            .expect("failed to query for finding an event")
2161            .expect("failed to find an event");
2162        assert_eq!(event.event_id(), event_comte.event_id());
2163
2164        let event = self
2165            .find_event(another_room_id, event_gruyere.event_id().unwrap())
2166            .await
2167            .expect("failed to query for finding an event")
2168            .expect("failed to find an event");
2169        assert_eq!(event.event_id(), event_gruyere.event_id());
2170
2171        // But they won't be returned when searching in the wrong room.
2172        assert!(
2173            self.find_event(another_room_id, event_comte.event_id().unwrap())
2174                .await
2175                .expect("failed to query for finding an event")
2176                .is_none()
2177        );
2178        assert!(
2179            self.find_event(room_id, event_gruyere.event_id().unwrap())
2180                .await
2181                .expect("failed to query for finding an event")
2182                .is_none()
2183        );
2184    }
2185
2186    async fn test_save_event_updates_event_in_room_and_thread(&self) {
2187        let room_id = *DEFAULT_TEST_ROOM_ID;
2188        let thread_root = event_id!("$thread_root");
2189
2190        // Create an event that will be inserted into both the room and thread linked
2191        // chunks.
2192        let event_id = event_id!("$event");
2193        let event = make_test_event_with_event_id(room_id, "event", Some(event_id));
2194
2195        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
2196        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
2197
2198        // Insert the relevant events into the room's linked chunk.
2199        self.handle_linked_chunk_updates(
2200            room_linked_chunk_id,
2201            vec![
2202                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2203                Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
2204            ],
2205        )
2206        .await
2207        .unwrap();
2208
2209        // Insert the relevant events into the thread's linked chunk.
2210        self.handle_linked_chunk_updates(
2211            thread_linked_chunk_id,
2212            vec![
2213                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2214                Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
2215            ],
2216        )
2217        .await
2218        .unwrap();
2219
2220        // Save updated version of original event, which should replace the content of
2221        // the existing event
2222        let updated_content = "updated content";
2223        let updated = make_test_event_with_event_id(room_id, updated_content, Some(event_id));
2224        self.save_event(room_id, updated).await.unwrap();
2225
2226        // Load all chunks from both room and thread
2227        let room_chunks = self.load_all_chunks(room_linked_chunk_id).await.unwrap();
2228        let thread_chunks = self.load_all_chunks(thread_linked_chunk_id).await.unwrap();
2229
2230        assert_eq!(room_chunks.len(), 1);
2231        assert_eq!(thread_chunks.len(), 1);
2232
2233        // Verify the event has been updated in both room and thread
2234        assert_matches!(&room_chunks[0].content, ChunkContent::Items(events) => {
2235            assert_eq!(events.len(), 1);
2236            assert_eq!(events[0].event_id(), Some(event_id));
2237            check_test_event(&events[0], updated_content);
2238        });
2239        assert_matches!(&thread_chunks[0].content, ChunkContent::Items(events) => {
2240            assert_eq!(events.len(), 1);
2241            assert_eq!(events[0].event_id(), Some(event_id));
2242            check_test_event(&events[0], updated_content);
2243        });
2244    }
2245
2246    async fn test_thread_vs_room_linked_chunk(&self) {
2247        let room_id = room_id!("!r0:matrix.org");
2248
2249        let event = |msg: &str| make_test_event(room_id, msg);
2250
2251        let thread1_ev = event("comté");
2252        let thread2_ev = event("gruyère");
2253        let thread2_ev2 = event("beaufort");
2254        let room_ev = event("brillat savarin triple crème");
2255
2256        let thread_root1 = event("thread1");
2257        let thread_root2 = event("thread2");
2258
2259        // Add one event in a thread linked chunk.
2260        self.handle_linked_chunk_updates(
2261            LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap()),
2262            vec![
2263                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2264                Update::PushItems {
2265                    at: Position::new(CId::new(0), 0),
2266                    items: vec![thread1_ev.clone()],
2267                },
2268            ],
2269        )
2270        .await
2271        .unwrap();
2272
2273        // Add one event in another thread linked chunk (same room).
2274        self.handle_linked_chunk_updates(
2275            LinkedChunkId::Thread(room_id, thread_root2.event_id().unwrap()),
2276            vec![
2277                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2278                Update::PushItems {
2279                    at: Position::new(CId::new(0), 0),
2280                    items: vec![thread2_ev.clone(), thread2_ev2.clone()],
2281                },
2282            ],
2283        )
2284        .await
2285        .unwrap();
2286
2287        // Add another event to the room linked chunk.
2288        self.handle_linked_chunk_updates(
2289            LinkedChunkId::Room(room_id),
2290            vec![
2291                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2292                Update::PushItems {
2293                    at: Position::new(CId::new(0), 0),
2294                    items: vec![room_ev.clone()],
2295                },
2296            ],
2297        )
2298        .await
2299        .unwrap();
2300
2301        // All the events can be found with `find_event()` for the room.
2302        self.find_event(room_id, thread2_ev.event_id().unwrap())
2303            .await
2304            .expect("failed to query for finding an event")
2305            .expect("failed to find thread1_ev");
2306
2307        self.find_event(room_id, thread2_ev.event_id().unwrap())
2308            .await
2309            .expect("failed to query for finding an event")
2310            .expect("failed to find thread2_ev");
2311
2312        self.find_event(room_id, thread2_ev2.event_id().unwrap())
2313            .await
2314            .expect("failed to query for finding an event")
2315            .expect("failed to find thread2_ev2");
2316
2317        self.find_event(room_id, room_ev.event_id().unwrap())
2318            .await
2319            .expect("failed to query for finding an event")
2320            .expect("failed to find room_ev");
2321
2322        // Finding duplicates operates based on the linked chunk id.
2323        let dups = self
2324            .filter_duplicated_events(
2325                LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap()),
2326                vec![
2327                    thread1_ev.event_id().unwrap().to_owned(),
2328                    room_ev.event_id().unwrap().to_owned(),
2329                ],
2330            )
2331            .await
2332            .unwrap();
2333        assert_eq!(dups.len(), 1);
2334        assert_eq!(dups[0].0, thread1_ev.event_id().unwrap());
2335
2336        // Loading all chunks operates based on the linked chunk id.
2337        let all_chunks = self
2338            .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root2.event_id().unwrap()))
2339            .await
2340            .unwrap();
2341        assert_eq!(all_chunks.len(), 1);
2342        assert_eq!(all_chunks[0].identifier, CId::new(0));
2343        assert_let!(ChunkContent::Items(observed_items) = all_chunks[0].content.clone());
2344        assert_eq!(observed_items.len(), 2);
2345        assert_eq!(observed_items[0].event_id(), thread2_ev.event_id());
2346        assert_eq!(observed_items[1].event_id(), thread2_ev2.event_id());
2347
2348        // Loading the metadata of all chunks operates based on the linked chunk
2349        // id.
2350        let metas = self
2351            .load_all_chunks_metadata(LinkedChunkId::Thread(
2352                room_id,
2353                thread_root2.event_id().unwrap(),
2354            ))
2355            .await
2356            .unwrap();
2357        assert_eq!(metas.len(), 1);
2358        assert_eq!(metas[0].identifier, CId::new(0));
2359        assert_eq!(metas[0].num_items, 2);
2360
2361        // Loading the last chunk operates based on the linked chunk id.
2362        let (last_chunk, _chunk_identifier_generator) = self
2363            .load_last_chunk(LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap()))
2364            .await
2365            .unwrap();
2366        let last_chunk = last_chunk.unwrap();
2367        assert_eq!(last_chunk.identifier, CId::new(0));
2368        assert_let!(ChunkContent::Items(observed_items) = last_chunk.content);
2369        assert_eq!(observed_items.len(), 1);
2370        assert_eq!(observed_items[0].event_id(), thread1_ev.event_id());
2371    }
2372}
2373
2374/// Macro building to allow your `EventCacheStore` implementation to run the
2375/// entire tests suite locally.
2376///
2377/// You need to provide a `async fn get_event_cache_store() ->
2378/// EventCacheStoreResult<impl EventCacheStore>` providing a fresh event cache
2379/// store on the same level you invoke the macro.
2380///
2381/// ## Usage Example:
2382/// ```no_run
2383/// # use matrix_sdk_base::event_cache::store::{
2384/// #    EventCacheStore,
2385/// #    MemoryStore as MyStore,
2386/// #    Result as EventCacheStoreResult,
2387/// # };
2388///
2389/// #[cfg(test)]
2390/// mod tests {
2391///     use super::{EventCacheStore, EventCacheStoreResult, MyStore};
2392///
2393///     async fn get_event_cache_store()
2394///     -> EventCacheStoreResult<impl EventCacheStore> {
2395///         Ok(MyStore::new())
2396///     }
2397///
2398///     event_cache_store_integration_tests!();
2399/// }
2400/// ```
2401#[allow(unused_macros, unused_extern_crates)]
2402#[macro_export]
2403macro_rules! event_cache_store_integration_tests {
2404    () => {
2405        mod event_cache_store_integration_tests {
2406            use matrix_sdk_test::async_test;
2407            use $crate::event_cache::store::{
2408                EventCacheStoreIntegrationTests, IntoEventCacheStore,
2409            };
2410
2411            use super::get_event_cache_store;
2412
2413            #[async_test]
2414            async fn test_handle_updates_and_rebuild_linked_chunk() {
2415                let event_cache_store =
2416                    get_event_cache_store().await.unwrap().into_event_cache_store();
2417                event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
2418            }
2419
2420            #[async_test]
2421            async fn test_linked_chunk_exists_before_referenced() {
2422                let event_cache_store =
2423                    get_event_cache_store().await.unwrap().into_event_cache_store();
2424                event_cache_store.test_linked_chunk_exists_before_referenced().await;
2425            }
2426
2427            #[async_test]
2428            async fn test_linked_chunk_allow_same_event_in_room_and_thread() {
2429                let event_cache_store =
2430                    get_event_cache_store().await.unwrap().into_event_cache_store();
2431                event_cache_store.test_linked_chunk_allows_same_event_in_room_and_thread().await;
2432            }
2433
2434            #[async_test]
2435            async fn test_load_last_chunk() {
2436                let event_cache_store =
2437                    get_event_cache_store().await.unwrap().into_event_cache_store();
2438                event_cache_store.test_load_last_chunk().await;
2439            }
2440
2441            #[async_test]
2442            async fn test_load_last_chunk_with_a_cycle() {
2443                let event_cache_store =
2444                    get_event_cache_store().await.unwrap().into_event_cache_store();
2445                event_cache_store.test_load_last_chunk_with_a_cycle().await;
2446            }
2447
2448            #[async_test]
2449            async fn test_load_previous_chunk() {
2450                let event_cache_store =
2451                    get_event_cache_store().await.unwrap().into_event_cache_store();
2452                event_cache_store.test_load_previous_chunk().await;
2453            }
2454
2455            #[async_test]
2456            async fn test_linked_chunk_incremental_loading() {
2457                let event_cache_store =
2458                    get_event_cache_store().await.unwrap().into_event_cache_store();
2459                event_cache_store.test_linked_chunk_incremental_loading().await;
2460            }
2461
2462            #[async_test]
2463            async fn test_linked_chunk_remove_chunk() {
2464                let event_cache_store =
2465                    get_event_cache_store().await.unwrap().into_event_cache_store();
2466                event_cache_store.test_linked_chunk_remove_chunk().await;
2467            }
2468
2469            #[async_test]
2470            async fn test_linked_chunk_replace_item() {
2471                let event_cache_store =
2472                    get_event_cache_store().await.unwrap().into_event_cache_store();
2473                event_cache_store.test_linked_chunk_replace_item().await;
2474            }
2475
2476            #[async_test]
2477            async fn test_linked_chunk_remove_item() {
2478                let event_cache_store =
2479                    get_event_cache_store().await.unwrap().into_event_cache_store();
2480                event_cache_store.test_linked_chunk_remove_item().await;
2481            }
2482
2483            #[async_test]
2484            async fn test_linked_chunk_detach_last_items() {
2485                let event_cache_store =
2486                    get_event_cache_store().await.unwrap().into_event_cache_store();
2487                event_cache_store.test_linked_chunk_detach_last_items().await;
2488            }
2489
2490            #[async_test]
2491            async fn test_linked_chunk_start_end_reattach_items() {
2492                let event_cache_store =
2493                    get_event_cache_store().await.unwrap().into_event_cache_store();
2494                event_cache_store.test_linked_chunk_start_end_reattach_items().await;
2495            }
2496
2497            #[async_test]
2498            async fn test_linked_chunk_clear() {
2499                let event_cache_store =
2500                    get_event_cache_store().await.unwrap().into_event_cache_store();
2501                event_cache_store.test_linked_chunk_clear().await;
2502            }
2503
2504            #[async_test]
2505            async fn test_linked_chunk_clear_and_reinsert() {
2506                let event_cache_store =
2507                    get_event_cache_store().await.unwrap().into_event_cache_store();
2508                event_cache_store.test_linked_chunk_clear_and_reinsert().await;
2509            }
2510
2511            #[async_test]
2512            async fn test_rebuild_empty_linked_chunk() {
2513                let event_cache_store =
2514                    get_event_cache_store().await.unwrap().into_event_cache_store();
2515                event_cache_store.test_rebuild_empty_linked_chunk().await;
2516            }
2517
2518            #[async_test]
2519            async fn test_linked_chunk_multiple_rooms() {
2520                let event_cache_store =
2521                    get_event_cache_store().await.unwrap().into_event_cache_store();
2522                event_cache_store.test_linked_chunk_multiple_rooms().await;
2523            }
2524
2525            #[async_test]
2526            async fn test_load_all_chunks_metadata() {
2527                let event_cache_store =
2528                    get_event_cache_store().await.unwrap().into_event_cache_store();
2529                event_cache_store.test_load_all_chunks_metadata().await;
2530            }
2531
2532            #[async_test]
2533            async fn test_load_and_update_thread_info() {
2534                let event_cache_store =
2535                    get_event_cache_store().await.unwrap().into_event_cache_store();
2536                event_cache_store.test_load_and_update_thread_info().await;
2537            }
2538
2539            #[async_test]
2540            async fn test_clear_all_events() {
2541                let event_cache_store =
2542                    get_event_cache_store().await.unwrap().into_event_cache_store();
2543                event_cache_store.test_clear_all_events().await;
2544            }
2545
2546            #[async_test]
2547            async fn test_clear_all_events_for_specific_room() {
2548                let event_cache_store =
2549                    get_event_cache_store().await.unwrap().into_event_cache_store();
2550                event_cache_store.test_clear_all_events_for_specific_room().await;
2551            }
2552
2553            #[async_test]
2554            async fn test_filter_duplicated_events() {
2555                let event_cache_store =
2556                    get_event_cache_store().await.unwrap().into_event_cache_store();
2557                event_cache_store.test_filter_duplicated_events().await;
2558            }
2559
2560            #[async_test]
2561            async fn test_filter_duplicate_events_no_events() {
2562                let event_cache_store =
2563                    get_event_cache_store().await.unwrap().into_event_cache_store();
2564                event_cache_store.test_filter_duplicate_events_no_events().await;
2565            }
2566
2567            #[async_test]
2568            async fn test_find_event() {
2569                let event_cache_store =
2570                    get_event_cache_store().await.unwrap().into_event_cache_store();
2571                event_cache_store.test_find_event().await;
2572            }
2573
2574            #[async_test]
2575            async fn test_find_event_when_event_in_room_and_thread() {
2576                let event_cache_store =
2577                    get_event_cache_store().await.unwrap().into_event_cache_store();
2578                event_cache_store.test_find_event_when_event_in_room_and_thread().await;
2579            }
2580
2581            #[async_test]
2582            async fn test_find_event_relations() {
2583                let event_cache_store =
2584                    get_event_cache_store().await.unwrap().into_event_cache_store();
2585                event_cache_store.test_find_event_relations().await;
2586            }
2587
2588            #[async_test]
2589            async fn test_find_event_relations_when_event_in_room_and_thread() {
2590                let event_cache_store =
2591                    get_event_cache_store().await.unwrap().into_event_cache_store();
2592                event_cache_store.test_find_event_relations_when_event_in_room_and_thread().await;
2593            }
2594
2595            #[async_test]
2596            async fn test_get_room_events() {
2597                let event_cache_store =
2598                    get_event_cache_store().await.unwrap().into_event_cache_store();
2599                event_cache_store.test_get_room_events().await;
2600            }
2601
2602            #[async_test]
2603            async fn test_get_room_events_filtered() {
2604                let event_cache_store =
2605                    get_event_cache_store().await.unwrap().into_event_cache_store();
2606                event_cache_store.test_get_room_events_filtered().await;
2607            }
2608
2609            #[async_test]
2610            async fn test_get_room_events_with_event_in_room_and_thread() {
2611                let event_cache_store =
2612                    get_event_cache_store().await.unwrap().into_event_cache_store();
2613                event_cache_store.test_get_room_events_with_event_in_room_and_thread().await;
2614            }
2615
2616            #[async_test]
2617            async fn test_save_event() {
2618                let event_cache_store =
2619                    get_event_cache_store().await.unwrap().into_event_cache_store();
2620                event_cache_store.test_save_event().await;
2621            }
2622
2623            #[async_test]
2624            async fn test_save_event_updates_event_in_room_and_thread() {
2625                let event_cache_store =
2626                    get_event_cache_store().await.unwrap().into_event_cache_store();
2627                event_cache_store.test_save_event_updates_event_in_room_and_thread().await;
2628            }
2629
2630            #[async_test]
2631            async fn test_thread_vs_room_linked_chunk() {
2632                let event_cache_store =
2633                    get_event_cache_store().await.unwrap().into_event_cache_store();
2634                event_cache_store.test_thread_vs_room_linked_chunk().await;
2635            }
2636        }
2637    };
2638}
2639
2640/// Macro generating tests for the event cache store, related to time (mostly
2641/// for the cross-process lock).
2642#[allow(unused_macros)]
2643#[macro_export]
2644macro_rules! event_cache_store_integration_tests_time {
2645    () => {
2646        mod event_cache_store_integration_tests_time {
2647            use std::time::Duration;
2648
2649            #[cfg(all(target_family = "wasm", target_os = "unknown"))]
2650            use gloo_timers::future::sleep;
2651            use matrix_sdk_test::async_test;
2652            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2653            use tokio::time::sleep;
2654            use $crate::event_cache::store::IntoEventCacheStore;
2655
2656            use super::get_event_cache_store;
2657
2658            #[async_test]
2659            async fn test_lease_locks() {
2660                let store = get_event_cache_store().await.unwrap().into_event_cache_store();
2661
2662                let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
2663                assert_eq!(acquired0, Some(1)); // first lock generation
2664
2665                // Should extend the lease automatically (same holder).
2666                let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
2667                assert_eq!(acquired2, Some(1)); // same lock generation
2668
2669                // Should extend the lease automatically (same holder + time is ok).
2670                let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
2671                assert_eq!(acquired3, Some(1)); // same lock generation
2672
2673                // Another attempt at taking the lock should fail, because it's taken.
2674                let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2675                assert!(acquired4.is_none()); // not acquired
2676
2677                // Even if we insist.
2678                let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2679                assert!(acquired5.is_none()); // not acquired
2680
2681                // That's a nice test we got here, go take a little nap.
2682                sleep(Duration::from_millis(50)).await;
2683
2684                // Still too early.
2685                let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2686                assert!(acquired55.is_none()); // not acquired
2687
2688                // Ok you can take another nap then.
2689                sleep(Duration::from_millis(250)).await;
2690
2691                // At some point, we do get the lock.
2692                let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
2693                assert_eq!(acquired6, Some(2)); // new lock generation!
2694
2695                sleep(Duration::from_millis(1)).await;
2696
2697                // The other gets it almost immediately too.
2698                let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
2699                assert_eq!(acquired7, Some(3)); // new lock generation!
2700
2701                sleep(Duration::from_millis(1)).await;
2702
2703                // But when we take a longer lease…
2704                let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2705                assert_eq!(acquired8, Some(4)); // new lock generation!
2706
2707                // It blocks the other user.
2708                let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
2709                assert!(acquired9.is_none()); // not acquired
2710
2711                // We can hold onto our lease.
2712                let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2713                assert_eq!(acquired10, Some(4)); // same lock generation
2714            }
2715        }
2716    };
2717}