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